Describe the bug
For properties defined as required but nullable (foo: string | null), the generated C# serialization code can emit the property twice during serialization when using JsonPatch. The generated pattern writes either the value or null for the property before Patch.WriteTo(writer) runs, so when the patch also contains that same property path, the patched value is emitted again, resulting in duplicate JSON properties.
The property serialization fallback should only execute when the patch does not already contain the property path. In practice, the null/value branch for a required nullable property should be gated on !Patch.Contains("$.param") so patched values are the single source of output for that property.
Reproduction
Relevant generated code pattern:
if (Optional.IsDefined(Foo) && !Patch.Contains("$.foo"u8))
{
writer.WritePropertyName("foo"u8);
writer.WriteStringValue(Foo);
}
else
{
writer.WriteNull("foo"u8);
}
This else branch still writes "foo": null even when Patch.Contains("$.foo") is true. Later, Patch.WriteTo(writer) emits the patched foo value, producing duplicate foo properties.
Expected behavior:
- When
Patch.Contains("$.foo") is true, neither the value branch nor the null fallback should write foo directly.
- The generated serialization should instead let
Patch.WriteTo(writer) emit the property exactly once.
Likely generator location:
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs
Related generated/test files showing merge-patch patterns:
packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteMultiplePrimitiveProperties.cs
A minimal repro is a model with a required nullable property such as foo: string | null, where the property is included in Patch and serialization is generated using the current C# client model generator.
Checklist
Describe the bug
For properties defined as required but nullable (
foo: string | null), the generated C# serialization code can emit the property twice during serialization when usingJsonPatch. The generated pattern writes either the value ornullfor the property beforePatch.WriteTo(writer)runs, so when the patch also contains that same property path, the patched value is emitted again, resulting in duplicate JSON properties.The property serialization fallback should only execute when the patch does not already contain the property path. In practice, the null/value branch for a required nullable property should be gated on
!Patch.Contains("$.param")so patched values are the single source of output for that property.Reproduction
Relevant generated code pattern:
This else branch still writes
"foo": nulleven whenPatch.Contains("$.foo")is true. Later,Patch.WriteTo(writer)emits the patchedfoovalue, producing duplicatefooproperties.Expected behavior:
Patch.Contains("$.foo")is true, neither the value branch nor the null fallback should writefoodirectly.Patch.WriteTo(writer)emit the property exactly once.Likely generator location:
packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.csRelated generated/test files showing merge-patch patterns:
packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cspackages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteMultiplePrimitiveProperties.csA minimal repro is a model with a required nullable property such as
foo: string | null, where the property is included inPatchand serialization is generated using the current C# client model generator.Checklist