From 64682b3b88a3f99c7c1906d1ad8f3470a619d6c4 Mon Sep 17 00:00:00 2001 From: "Jiao Di (MSFT)" Date: Tue, 3 Feb 2026 17:02:13 +0800 Subject: [PATCH] fix --- .../src/modular/helpers/operationHelpers.ts | 28 ++++++++++++++++-- .../serialization/buildSerializerFunction.ts | 23 +++++++++++++-- .../flatten-property/src/index.d.ts | 29 +++++++++++++++++++ .../modelFlatten.spec.ts | 19 ++++++++++++ .../serialization/readonlyFlattenModel.md | 17 ++++------- 5 files changed, 99 insertions(+), 17 deletions(-) diff --git a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts index 0f914fe19d..c0385a27a9 100644 --- a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts @@ -1514,6 +1514,12 @@ function getSerializationExpressionForFlatten( !isReadOnly(p) && !isMetadata(context.program, p.__raw!) ); + + // If all properties are readonly, don't serialize this flatten property at all + if (validProps.length === 0) { + return "undefined"; + } + const optionalPrefix = property.optional ? `${resolveReference(SerializationHelpers.areAllPropsUndefined)}(${propertyPath}, [${validProps .map((p) => `"${p.name}"`) @@ -1618,7 +1624,9 @@ export function getRequestModelMapping( propertyPath, overrides, enableFlatten - ).map(([name, value]) => `"${name}": ${value}`); + ) + .filter(([_name, value]) => value !== "undefined") + .map(([name, value]) => `"${name}": ${value}`); } function getPropertySerializedName( @@ -1684,8 +1692,24 @@ export function getResponseMapping( const propertyName = normalizeModelPropertyName(context, property); if (deserializeFunctionName) { if (isSupportedFlatten) { + // Check if all properties of the flattened type are readonly + const flattenedProps = getAllProperties( + context, + property.type, + getAllAncestors(property.type) + ).filter( + (p) => p.kind === "property" && !isMetadata(context.program, p.__raw!) + ); + const allPropsReadonly = flattenedProps.every((p) => isReadOnly(p)); + + // For flatten properties in responses: + // - If all properties are readonly, they're flattened at the parent level + // - Otherwise, they're nested under the property name + const flattenPath = allPropsReadonly + ? propertyPath || "item" + : restValue; props.push( - `...${nullOrUndefinedPrefix}${deserializeFunctionName}(${restValue})` + `...${nullOrUndefinedPrefix}${deserializeFunctionName}(${flattenPath})` ); } else { props.push( diff --git a/packages/typespec-ts/src/modular/serialization/buildSerializerFunction.ts b/packages/typespec-ts/src/modular/serialization/buildSerializerFunction.ts index 79eae42f16..4c0ddeac7c 100644 --- a/packages/typespec-ts/src/modular/serialization/buildSerializerFunction.ts +++ b/packages/typespec-ts/src/modular/serialization/buildSerializerFunction.ts @@ -507,9 +507,26 @@ function buildModelTypeSerializer( return ${serializeContent} `); } else { - output.push(` - return item; - `); + // For flatten properties, if all properties are readonly, return empty object + // Otherwise, return the item itself + if (options.flatten) { + // Change parameter name to _item to indicate it's intentionally unused + serializerFunction.parameters = [ + { + name: "_item", + type: options.flatten + ? resolveReference(refkey(options.flatten.baseModel)) + : resolveReference(refkey(type)) + } + ]; + output.push(` + return {}; + `); + } else { + output.push(` + return item; + `); + } } serializerFunction.statements = output; } diff --git a/packages/typespec-ts/test/azureModularIntegration/generated/azure/client-generator-core/flatten-property/src/index.d.ts b/packages/typespec-ts/test/azureModularIntegration/generated/azure/client-generator-core/flatten-property/src/index.d.ts index 64fd799fc6..7af3981259 100644 --- a/packages/typespec-ts/test/azureModularIntegration/generated/azure/client-generator-core/flatten-property/src/index.d.ts +++ b/packages/typespec-ts/test/azureModularIntegration/generated/azure/client-generator-core/flatten-property/src/index.d.ts @@ -23,6 +23,8 @@ export declare class FlattenPropertyClient { private _client; readonly pipeline: Pipeline; constructor(options?: FlattenPropertyClientOptionalParams); + putFlattenReadOnlyModel(body: Solution, options?: PutFlattenReadOnlyModelOptionalParams): Promise; + putFlattenUnknownModel(input: FlattenUnknownModel, options?: PutFlattenUnknownModelOptionalParams): Promise; putNestedFlattenModel(input: NestedFlattenModel, options?: PutNestedFlattenModelOptionalParams): Promise; putFlattenModel(input: FlattenModel, options?: PutFlattenModelOptionalParams): Promise; } @@ -30,6 +32,11 @@ export declare class FlattenPropertyClient { export declare interface FlattenPropertyClientOptionalParams extends ClientOptions { } +export declare interface FlattenUnknownModel { + name: string; + properties?: any; +} + export declare interface NestedFlattenModel { name: string; summary: string; @@ -39,7 +46,29 @@ export declare interface NestedFlattenModel { export declare interface PutFlattenModelOptionalParams extends OperationOptions { } +export declare interface PutFlattenReadOnlyModelOptionalParams extends OperationOptions { +} + +export declare interface PutFlattenUnknownModelOptionalParams extends OperationOptions { +} + export declare interface PutNestedFlattenModelOptionalParams extends OperationOptions { } +export declare interface Solution { + name: string; + readonly solutionId?: string; + readonly title?: string; + readonly content?: string; + readonly solutionIdPropertiesOptionalSolutionId?: string; + readonly titlePropertiesOptionalTitle?: string; + readonly contentPropertiesOptionalContent?: string; +} + +export declare interface SolutionProperties { + readonly solutionId?: string; + readonly title?: string; + readonly content?: string; +} + export { } diff --git a/packages/typespec-ts/test/azureModularIntegration/modelFlatten.spec.ts b/packages/typespec-ts/test/azureModularIntegration/modelFlatten.spec.ts index 81c01ab753..69b93bfb53 100644 --- a/packages/typespec-ts/test/azureModularIntegration/modelFlatten.spec.ts +++ b/packages/typespec-ts/test/azureModularIntegration/modelFlatten.spec.ts @@ -35,4 +35,23 @@ describe("Property Flatten Client", () => { assert.strictEqual(result.properties.description, "foo"); assert.strictEqual(result.properties.age, 1); }); + + it("Update and receive model with unknown properties flattening", async () => { + const result = await client.putFlattenUnknownModel({ + name: "foo" + }); + assert.strictEqual(result.name, "test"); + assert.strictEqual(result.properties?.key1, "value1"); + assert.strictEqual(result.properties?.key2, "value2"); + }); + + it("Update and receive model with read-only properties flattening", async () => { + const result = await client.putFlattenReadOnlyModel({ + name: "foo" + }); + assert.strictEqual(result.name, "foo"); + assert.strictEqual(result.solutionId, "solution1"); + assert.strictEqual(result.title, "Solution Title"); + assert.strictEqual(result.content, "Solution Content"); + }); }); diff --git a/packages/typespec-ts/test/modularUnit/scenarios/models/serialization/readonlyFlattenModel.md b/packages/typespec-ts/test/modularUnit/scenarios/models/serialization/readonlyFlattenModel.md index 1c0f541a59..624af97b15 100644 --- a/packages/typespec-ts/test/modularUnit/scenarios/models/serialization/readonlyFlattenModel.md +++ b/packages/typespec-ts/test/modularUnit/scenarios/models/serialization/readonlyFlattenModel.md @@ -35,8 +35,6 @@ needTCGC: true ## Models ```ts models -import { areAllPropsUndefined } from "../static-helpers/serialization/check-prop-undefined.js"; - /** * This file contains only generated model types and their (de)serializers. * Disable the following rules for internal models with '_' prefix and deserializers which require 'any' for raw JSON input. @@ -54,12 +52,7 @@ export interface Solution { } export function solutionSerializer(item: Solution): any { - return { - properties: _solutionPropertiesSerializer(item), - propertiesOptional: areAllPropsUndefined(item, []) - ? undefined - : _solutionPropertiesOptionalSerializer(item), - }; + return item; } /** model interface SolutionProperties */ @@ -73,11 +66,11 @@ export function solutionPropertiesSerializer(item: SolutionProperties): any { return item; } -export function _solutionPropertiesSerializer(item: Solution): any { - return item; +export function _solutionPropertiesSerializer(_item: Solution): any { + return {}; } -export function _solutionPropertiesOptionalSerializer(item: Solution): any { - return item; +export function _solutionPropertiesOptionalSerializer(_item: Solution): any { + return {}; } ```