Skip to content

Commit 2b1e18b

Browse files
fix(http-client-csharp): gate required nullable null fallback on patch check
Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com>
1 parent f41b190 commit 2b1e18b

5 files changed

Lines changed: 102 additions & 2 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/http-client-csharp"
5+
---
6+
7+
Avoid emitting duplicate output for required nullable properties during patch serialization. The null fallback for a required nullable property is now gated on `!Patch.Contains(...)` so patched values remain the single source of output for that property.

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2558,10 +2558,17 @@ private MethodBodyStatement CreateConditionalSerializationStatement(
25582558
_utf8JsonWriterSnippet.WriteNull(serializedName));
25592559
}
25602560

2561+
// When the property path is already present in the patch, the patched value is
2562+
// emitted by Patch.WriteTo(writer), so the null fallback must be gated on the patch
2563+
// check to avoid writing the property twice.
2564+
MethodBodyStatement writeNullStatement = patchCheck != null
2565+
? new IfStatement(patchCheck) { _utf8JsonWriterSnippet.WriteNull(serializedName) }
2566+
: _utf8JsonWriterSnippet.WriteNull(serializedName);
2567+
25612568
return new IfElseStatement(
25622569
condition,
25632570
writePropertySerializationStatement,
2564-
_utf8JsonWriterSnippet.WriteNull(serializedName));
2571+
writeNullStatement);
25652572
}
25662573

25672574
if (shouldCheckJsonPath)

packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,33 @@ public void WriteMultiplePrimitiveProperties()
531531
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
532532
}
533533

534+
[Test]
535+
public void WriteRequiredNullableProperty()
536+
{
537+
var inputModel = InputFactory.Model(
538+
"dynamicModel",
539+
isDynamicModel: true,
540+
properties:
541+
[
542+
InputFactory.Property("foo", new InputNullableType(InputPrimitiveType.String), isRequired: true)
543+
]);
544+
545+
MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]);
546+
var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider;
547+
548+
Assert.IsNotNull(model);
549+
Assert.IsTrue(model!.IsDynamicModel);
550+
var serialization = model.SerializationProviders.SingleOrDefault();
551+
Assert.IsNotNull(serialization);
552+
553+
var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider(
554+
serialization!,
555+
name => name is "JsonModelWriteCore" or "Write"));
556+
557+
var file = writer.Write();
558+
Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content);
559+
}
560+
534561
[Test]
535562
public void WriteModelPropertyType()
536563
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// <auto-generated/>
2+
3+
#nullable disable
4+
5+
using System;
6+
using System.ClientModel.Primitives;
7+
using System.Text.Json;
8+
using Sample.Models;
9+
10+
namespace Sample
11+
{
12+
public partial class DynamicModel
13+
{
14+
global::System.BinaryData global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.DynamicModel>.Write(global::System.ClientModel.Primitives.ModelReaderWriterOptions options) => this.PersistableModelWriteCore(options);
15+
16+
void global::System.ClientModel.Primitives.IJsonModel<global::Sample.Models.DynamicModel>.Write(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
17+
{
18+
#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates.
19+
if (Patch.Contains("$"u8))
20+
{
21+
writer.WriteRawValue(Patch.GetJson("$"u8));
22+
return;
23+
}
24+
#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates.
25+
26+
writer.WriteStartObject();
27+
this.JsonModelWriteCore(writer, options);
28+
writer.WriteEndObject();
29+
}
30+
31+
protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options)
32+
{
33+
string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel<global::Sample.Models.DynamicModel>)this).GetFormatFromOptions(options) : options.Format;
34+
if ((format != "J"))
35+
{
36+
throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format.");
37+
}
38+
#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates.
39+
if ((global::Sample.Optional.IsDefined(Foo) && !Patch.Contains("$.foo"u8)))
40+
{
41+
writer.WritePropertyName("foo"u8);
42+
writer.WriteStringValue(Foo);
43+
}
44+
else
45+
{
46+
if (!Patch.Contains("$.foo"u8))
47+
{
48+
writer.WriteNull("foo"u8);
49+
}
50+
}
51+
52+
Patch.WriteTo(writer);
53+
#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates.
54+
}
55+
}
56+
}

packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit
223223
}
224224
else
225225
{
226-
writer.WriteNull("requiredNullableDictionary"u8);
226+
if (!Patch.Contains("$.requiredNullableDictionary"u8))
227+
{
228+
writer.WriteNull("requiredNullableDictionary"u8);
229+
}
227230
}
228231
if (!Patch.Contains("$.primitiveDictionary"u8))
229232
{

0 commit comments

Comments
 (0)