From 144a463d2de9072f13b3f0e693349be696c67d6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:57:07 +0000 Subject: [PATCH 01/30] Initial plan From ef43b16d4b895e60ecc731bbf93790b7c7608b97 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 17:07:53 +0000 Subject: [PATCH 02/30] fix(http-client-csharp): skip irrelevant indexed patch path checks Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 22 +++++-- .../Sample_TypeSpec/DynamicModelTests.cs | 60 +++++++++++++++++++ ...PropagateModelListPropertyHelperMethods.cs | 3 +- .../WriteArrayProperties.cs | 9 ++- .../WriteNestedArrayDictionaryProperties.cs | 9 ++- .../WriteNestedArrayDynamicModelProperties.cs | 9 ++- .../WriteNestedArrayPrimitiveProperties.cs | 9 ++- .../WriteReadOnlySpanProperty.cs | 3 +- ...redCollectionDoesNotDuplicatePatchedKey.cs | 3 +- .../Models/DynamicModel.Serialization.cs | 24 +++++--- .../NullableDynamicModel.Serialization.cs | 18 ++++-- 11 files changed, 136 insertions(+), 33 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index c1ff197fcf8..b07e26c20c8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -110,10 +110,16 @@ private MethodBodyStatement CreateListSerializationWithPatch( var indexDeclaration = Declare("i", out var indexVar); var allIndices = new List(parentIndices) { indexVar }; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var patchIsRemovedCondition = patchSnippet.IsRemoved( + // The prefix overload includes indexed descendants, unlike an exact-path Contains check. + var hasPatchDeclaration = Declare( + "hasPatch", + typeof(bool), + patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName.Split('.')[0])), + out var hasPatch); + var patchIsRemovedCondition = hasPatch.As().And(patchSnippet.IsRemoved( Utf8Snippets.GetBytes( new FormattableStringExpression(jsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) - .As())); + .As()))); // Handle model types with their own patch property if (ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(type, out var provider) && @@ -158,6 +164,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( return new[] { _utf8JsonWriterSnippet.WriteStartArray(), + hasPatchDeclaration, forStatement, writeToPatchStatement, _utf8JsonWriterSnippet.WriteEndArray() @@ -605,10 +612,16 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) { isActive = item.Equal(Null).Or(isActive); } + var serializedName = GetJsonSerializedName(property.WireInfo!); + var hasPatchDeclaration = Declare( + "hasPatch", + typeof(bool), + _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName.Split('.')[0])), + out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( - BuildJsonPathForElement(GetJsonSerializedName(property.WireInfo!), [indexVar]), + BuildJsonPathForElement(serializedName, [indexVar]), [indexVar]).As()); - isActive = Not(_jsonPatchProperty!.As().IsRemoved(itemPath)).And(isActive); + isActive = Not(hasPatch).Or(Not(_jsonPatchProperty!.As().IsRemoved(itemPath))).And(isActive); var forStatement = new ForStatement( indexDeclaration.Assign(Literal(0)), indexVar.LessThan(((ValueExpression)property).Property(lengthPropertyName)), @@ -626,6 +639,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) { YieldBreak() }, + hasPatchDeclaration, forStatement }; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs index 8021aa4c547..ee75c25c910 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Buffers; using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; @@ -369,6 +370,65 @@ public void JsonPatchRemove_NullDynamicListElementSnapshot(bool onlyNull) Assert.That(Encoding.UTF8.GetString(json), Is.EqualTo(onlyNull ? "[]" : """[{"bar":"present"},null]""")); } + [TestCase(false)] + [TestCase(true)] + public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(bool unrelatedPatch) + { + var model = new NullableDynamicModel + { + Children = new AnotherDynamicModel[256] + }; +#pragma warning disable SCME0001 + if (unrelatedPatch) + { + model.Patch.Set("$.unrelated"u8, 1); + } +#pragma warning restore SCME0001 + + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer); + var jsonModel = (IJsonModel)model; + jsonModel.Write(writer, ModelReaderWriterOptions.Json); + writer.Flush(); + buffer.Clear(); + writer.Reset(buffer); + + long before = GC.GetAllocatedBytesForCurrentThread(); + jsonModel.Write(writer, ModelReaderWriterOptions.Json); + writer.Flush(); + long allocated = GC.GetAllocatedBytesForCurrentThread() - before; + + Assert.That(allocated, Is.LessThan(1024), "Indexed patch paths must not allocate for each unpatched element."); + using var document = JsonDocument.Parse(buffer.WrittenMemory); + Assert.That(document.RootElement.GetProperty("children").GetArrayLength(), Is.EqualTo(256)); + } + + [TestCase(false)] + [TestCase(true)] + public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(bool unrelatedPatch) + { + var removed = new AnotherDynamicModel("removed"); + var model = new NullableDynamicModel + { + Children = [null, removed, new AnotherDynamicModel("present")] + }; + +#pragma warning disable SCME0001 + removed.Patch.Remove("$"u8); + if (unrelatedPatch) + { + model.Patch.Set("$.unrelated"u8, 1); + } + Assert.That(model.Patch.Contains("$"u8, "children"u8), Is.False); + var snapshot = model.Patch.GetJson("$.children"u8); +#pragma warning restore SCME0001 + + Assert.That(Encoding.UTF8.GetString(snapshot), Is.EqualTo("""[null,{"bar":"present"}]""")); + var data = ModelReaderWriter.Write(model, ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default); + using var document = JsonDocument.Parse(data); + Assert.That(document.RootElement.GetProperty("children").GetRawText(), Is.EqualTo("""[null,{"bar":"present"}]""")); + } + private static NullableDynamicModel CreateModelWithRemovedDynamicListElements(string propertyName, bool onlyNull) { var items = onlyNull ? "[null]" : """[null,{"bar":"present"},null]"""; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/PropagateModelListPropertyHelperMethods.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/PropagateModelListPropertyHelperMethods.cs index dc9e907c4a9..ba367387537 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/PropagateModelListPropertyHelperMethods.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/PropagateModelListPropertyHelperMethods.cs @@ -30,9 +30,10 @@ private bool TryResolveP1Array(out global::System.ClientModel.Primitives.JsonPat { yield break; } + bool hasPatch = Patch.Contains("$"u8, "p1"u8); for (int i = 0; (i < P1.Count); i++) { - if ((!Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.p1[{i}]")) && ((P1[i] == null) || !P1[i].Patch.IsRemoved("$"u8)))) + if (((!hasPatch || !Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.p1[{i}]"))) && ((P1[i] == null) || !P1[i].Patch.IsRemoved("$"u8)))) { yield return P1[i]; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteArrayProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteArrayProperties.cs index 567364334e1..207d475d1c0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteArrayProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteArrayProperties.cs @@ -49,9 +49,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("cats"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "cats"u8); for (int i = 0; (i < Cats.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.cats[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.cats[{i}]")))) { continue; } @@ -72,9 +73,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("names"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "names"u8); for (int i = 0; (i < Names.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.names[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.names[{i}]")))) { continue; } @@ -100,9 +102,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("optionalNames"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "optionalNames"u8); for (int i = 0; (i < OptionalNames.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.optionalNames[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.optionalNames[{i}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index 3ffca3dc201..f3c36c61481 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -49,9 +49,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedArray"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i = 0; (i < PropertyWithNestedArray.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")))) { continue; } @@ -61,9 +62,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); + bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]"))) + if ((hasPatch0 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) { continue; } @@ -73,9 +75,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); + bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"))) + if ((hasPatch1 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs index 58f44014717..5b20e01d13d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs @@ -49,9 +49,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedArray"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i = 0; (i < PropertyWithNestedArray.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")))) { continue; } @@ -61,9 +62,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); + bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]"))) + if ((hasPatch0 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) { continue; } @@ -73,9 +75,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); + bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if ((Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")) || ((PropertyWithNestedArray[i][i0][i1] != null) && PropertyWithNestedArray[i][i0][i1].Patch.IsRemoved("$"u8)))) + if (((hasPatch1 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"))) || ((PropertyWithNestedArray[i][i0][i1] != null) && PropertyWithNestedArray[i][i0][i1].Patch.IsRemoved("$"u8)))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs index 0db787e69fd..bbbb0586dfa 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs @@ -49,9 +49,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedArray"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i = 0; (i < PropertyWithNestedArray.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")))) { continue; } @@ -61,9 +62,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); + bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]"))) + if ((hasPatch0 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) { continue; } @@ -73,9 +75,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); + bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"))) + if ((hasPatch1 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteReadOnlySpanProperty.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteReadOnlySpanProperty.cs index 0b53a13e9e4..7443ddf7dd5 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteReadOnlySpanProperty.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteReadOnlySpanProperty.cs @@ -49,9 +49,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("someSpan"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "someSpan"u8); for (int i = 0; (i < SomeSpan.Span.Length); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.someSpan[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.someSpan[{i}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteRequiredCollectionDoesNotDuplicatePatchedKey.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteRequiredCollectionDoesNotDuplicatePatchedKey.cs index 4e6fcc67213..9ee1bdb8742 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteRequiredCollectionDoesNotDuplicatePatchedKey.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteRequiredCollectionDoesNotDuplicatePatchedKey.cs @@ -10,9 +10,10 @@ { writer.WritePropertyName("tools"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "tools"u8); for (int i = 0; (i < Tools.Count); i++) { - if (Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.tools[{i}]"))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.tools[{i}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs index f8fdffb9ba7..27e072713e6 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs @@ -133,9 +133,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("optionalNullableList"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "optionalNullableList"u8); for (int i = 0; i < OptionalNullableList.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.optionalNullableList[{i}]"))) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.optionalNullableList[{i}]"))) { continue; } @@ -156,9 +157,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("requiredNullableList"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "requiredNullableList"u8); for (int i = 0; i < RequiredNullableList.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.requiredNullableList[{i}]"))) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.requiredNullableList[{i}]"))) { continue; } @@ -267,9 +269,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("listFoo"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "listFoo"u8); for (int i = 0; i < ListFoo.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listFoo[{i}]")) || ListFoo[i] != null && ListFoo[i].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listFoo[{i}]")) || ListFoo[i] != null && ListFoo[i].Patch.IsRemoved("$"u8)) { continue; } @@ -290,9 +293,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("listOfListFoo"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "listOfListFoo"u8); for (int i = 0; i < ListOfListFoo.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]"))) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]"))) { continue; } @@ -302,9 +306,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); + bool hasPatch0 = Patch.Contains("$"u8, "listOfListFoo"u8); for (int i0 = 0; i0 < ListOfListFoo[i].Count; i0++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}][{i0}]")) || ListOfListFoo[i][i0] != null && ListOfListFoo[i][i0].Patch.IsRemoved("$"u8)) + if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}][{i0}]")) || ListOfListFoo[i][i0] != null && ListOfListFoo[i][i0].Patch.IsRemoved("$"u8)) { continue; } @@ -415,9 +420,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); for (int i = 0; i < item.Value.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } @@ -443,9 +449,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("listOfDictionaryFoo"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "listOfDictionaryFoo"u8); for (int i = 0; i < ListOfDictionaryFoo.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"))) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"))) { continue; } @@ -1130,9 +1137,10 @@ private IEnumerable ActiveListFoo() { yield break; } + bool hasPatch = Patch.Contains("$"u8, "listFoo"u8); for (int i = 0; i < ListFoo.Count; i++) { - if (!Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listFoo[{i}]")) && (ListFoo[i] == null || !ListFoo[i].Patch.IsRemoved("$"u8))) + if ((!hasPatch || !Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listFoo[{i}]"))) && (ListFoo[i] == null || !ListFoo[i].Patch.IsRemoved("$"u8))) { yield return ListFoo[i]; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs index c4c251836ac..43814cb472f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs @@ -100,9 +100,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("children"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "children"u8); for (int i = 0; i < Children.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.children[{i}]")) || Children[i] != null && Children[i].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.children[{i}]")) || Children[i] != null && Children[i].Patch.IsRemoved("$"u8)) { continue; } @@ -148,9 +149,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("nestedChildren"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "nestedChildren"u8); for (int i = 0; i < NestedChildren.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]"))) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]"))) { continue; } @@ -160,9 +162,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); + bool hasPatch0 = Patch.Contains("$"u8, "nestedChildren"u8); for (int i0 = 0; i0 < NestedChildren[i].Count; i0++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}][{i0}]")) || NestedChildren[i][i0] != null && NestedChildren[i][i0].Patch.IsRemoved("$"u8)) + if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}][{i0}]")) || NestedChildren[i][i0] != null && NestedChildren[i][i0].Patch.IsRemoved("$"u8)) { continue; } @@ -248,9 +251,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); for (int i = 0; i < item.Value.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } @@ -276,9 +280,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("listOfDictionaries"u8); writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "listOfDictionaries"u8); for (int i = 0; i < ListOfDictionaries.Count; i++) { - if (Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"))) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"))) { continue; } @@ -885,9 +890,10 @@ private IEnumerable ActiveChildren() { yield break; } + bool hasPatch = Patch.Contains("$"u8, "children"u8); for (int i = 0; i < Children.Count; i++) { - if (!Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.children[{i}]")) && (Children[i] == null || !Children[i].Patch.IsRemoved("$"u8))) + if ((!hasPatch || !Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.children[{i}]"))) && (Children[i] == null || !Children[i].Patch.IsRemoved("$"u8))) { yield return Children[i]; } From fd88953721358f25ad5a0b9e4ca9a99acc38ca2d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:59:22 +0000 Subject: [PATCH 03/30] fix(http-client-csharp): guard nested collection patch path allocations Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 32 ++-- .../Sample_TypeSpec/DynamicModelTests.cs | 104 +++++++++++-- .../WriteDictionaryProperties.cs | 48 ++++-- .../WriteNestedArrayDictionaryProperties.cs | 26 +++- .../WriteNestedArrayDynamicModelProperties.cs | 10 +- .../WriteNestedArrayPrimitiveProperties.cs | 10 +- .../WriteNestedDictDynamicModelProperties.cs | 48 ++++-- .../WriteNestedDictPrimitiveProperties.cs | 48 ++++-- .../Models/DynamicModel.Serialization.cs | 142 +++++++++++++----- .../NullableDynamicModel.Serialization.cs | 94 +++++++++--- 10 files changed, 430 insertions(+), 132 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index b07e26c20c8..192ee171174 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -33,6 +33,11 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( ValueExpression jsonPath = parentIndices.Count > 0 ? Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, [.. parentIndices]).As()) : LiteralU8($"$.{serializedName}"); + var hasPatchDeclaration = Declare( + "hasPatch", + typeof(bool), + patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName.Split('.')[0])), + out var hasPatch); var foreachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression keyValuePair); @@ -48,23 +53,25 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( Utf8Snippets.GetBytes(keyValuePair.Key.Invoke("AsSpan"), bufferVar), out var bytesWrittenVar); var patchContainsKey = patchSnippet.Contains(jsonPath, Utf8Snippets.GetBytes(keyValuePair.Key.As())); - var patchContainsNet8Declaration = Declare( + var patchContainsDeclaration = Declare( "patchContains", typeof(bool), + False, + out var patchContains); + var patchContainsNet8Assignment = patchContains.Assign( new TernaryConditionalExpression( bytesWrittenVar.Equal(Int(BufferSize)), patchContainsKey, patchSnippet.Contains( jsonPath, - ReadOnlySpanSnippets.Slice(bufferVar, Int(0), bytesWrittenVar))), - out var patchContainsNet8Var); + ReadOnlySpanSnippets.Slice(bufferVar, Int(0), bytesWrittenVar)))).Terminate(); List childIndices = keyValuePair.ValueType.IsCollection ? [.. parentIndices, keyValuePair.Key] : parentIndices; // Process key-value pair if patch doesn't contain it - var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) + var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContains)) { _utf8JsonWriterSnippet.WritePropertyName(keyValuePair.Key), CreateElementSerializationWithPatch( @@ -78,21 +85,21 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( "NET8_0_OR_GREATER", - new MethodBodyStatement[] { bytesWrittenDeclaration, patchContainsNet8Declaration }, - new DeclarationExpression(new VariableExpression(patchContainsNet8Var.Type, patchContainsNet8Var.Declaration)) - .Assign(patchContainsKey) - .Terminate()); + new MethodBodyStatement[] { bytesWrittenDeclaration, patchContainsNet8Assignment }, + patchContains.Assign(patchContainsKey).Terminate()); - foreachStatement.Add(innerIfElseProcessorStatement); + foreachStatement.Add(patchContainsDeclaration); + foreachStatement.Add(new IfStatement(hasPatch) { innerIfElseProcessorStatement }); foreachStatement.Add(ifPatchDoesNotContainStatement); return new[] { _utf8JsonWriterSnippet.WriteStartObject(), + hasPatchDeclaration, new IfElsePreprocessorStatement("NET8_0_OR_GREATER", bufferDeclaration), foreachStatement, MethodBodyStatement.EmptyLine, - patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate(), + new IfStatement(hasPatch) { patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate() }, _utf8JsonWriterSnippet.WriteEndObject(), }; } @@ -159,7 +166,10 @@ private MethodBodyStatement CreateListSerializationWithPatch( var writeToPatchStatement = parentIndices.Count == 0 ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() - : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, parentIndices).As())).Terminate(); + : new IfStatement(hasPatch) + { + patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, parentIndices).As())).Terminate() + }; return new[] { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs index ee75c25c910..066c64dbf58 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs @@ -370,13 +370,39 @@ public void JsonPatchRemove_NullDynamicListElementSnapshot(bool onlyNull) Assert.That(Encoding.UTF8.GetString(json), Is.EqualTo(onlyNull ? "[]" : """[{"bar":"present"},null]""")); } - [TestCase(false)] - [TestCase(true)] - public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(bool unrelatedPatch) + [TestCase("children", false)] + [TestCase("children", true)] + [TestCase("nestedChildren", false)] + [TestCase("nestedChildren", true)] + [TestCase("childDictionary", false)] + [TestCase("childDictionary", true)] + [TestCase("nestedChildDictionary", false)] + [TestCase("nestedChildDictionary", true)] + [TestCase("dictionaryChildren", false)] + [TestCase("dictionaryChildren", true)] + [TestCase("listOfDictionaries", false)] + [TestCase("listOfDictionaries", true)] + public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(string propertyName, bool unrelatedPatch) { - var model = new NullableDynamicModel + var dictionary = Enumerable.Range(0, 256).ToDictionary(i => $"key{i}", _ => (AnotherDynamicModel)null!); + var model = propertyName switch { - Children = new AnotherDynamicModel[256] + "children" => new NullableDynamicModel { Children = new AnotherDynamicModel[256] }, + "nestedChildren" => new NullableDynamicModel + { + NestedChildren = Enumerable.Repeat>(Array.Empty(), 256).ToArray() + }, + "childDictionary" => new NullableDynamicModel { ChildDictionary = dictionary }, + "nestedChildDictionary" => new NullableDynamicModel + { + NestedChildDictionary = new Dictionary> { ["key"] = dictionary } + }, + "dictionaryChildren" => new NullableDynamicModel + { + DictionaryChildren = Enumerable.Range(0, 256).ToDictionary(i => $"key{i}", _ => (IList)Array.Empty()) + }, + "listOfDictionaries" => new NullableDynamicModel { ListOfDictionaries = [dictionary] }, + _ => throw new ArgumentOutOfRangeException(nameof(propertyName)) }; #pragma warning disable SCME0001 if (unrelatedPatch) @@ -400,17 +426,55 @@ public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(bool unr Assert.That(allocated, Is.LessThan(1024), "Indexed patch paths must not allocate for each unpatched element."); using var document = JsonDocument.Parse(buffer.WrittenMemory); - Assert.That(document.RootElement.GetProperty("children").GetArrayLength(), Is.EqualTo(256)); + var collection = document.RootElement.GetProperty(propertyName); + collection = propertyName switch + { + "nestedChildDictionary" => collection.GetProperty("key"), + "listOfDictionaries" => collection[0], + _ => collection + }; + Assert.That(collection.ValueKind == JsonValueKind.Array ? collection.GetArrayLength() : collection.EnumerateObject().Count(), Is.EqualTo(256)); } - [TestCase(false)] - [TestCase(true)] - public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(bool unrelatedPatch) + [TestCase("childDictionary", """{"removed":null,"present":null}""", "$.childDictionary.removed", "$.childDictionary.added", """{"present":null,"added":{"bar":"added"}}""")] + [TestCase("listOfDictionaries", """[{"removed":null,"present":null}]""", "$.listOfDictionaries[0].removed", "$.listOfDictionaries[0].added", """[{"present":null,"added":{"bar":"added"}}]""")] + public void JsonModelWrite_DictionaryPatchesArePreserved(string propertyName, string value, string removedPath, string addedPath, string expected) + { + var model = ModelReaderWriter.Read( + BinaryData.FromString($$"""{"{{propertyName}}":{{value}}}"""), + ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default)!; + +#pragma warning disable SCME0001 + model.Patch.Remove(Encoding.UTF8.GetBytes(removedPath)); + model.Patch.Set(Encoding.UTF8.GetBytes(addedPath), """{"bar":"added"}"""u8); + Assert.That(model.Patch.Contains(Encoding.UTF8.GetBytes($"$.{propertyName}")), Is.False); + Assert.That(model.Patch.Contains("$"u8, Encoding.UTF8.GetBytes(propertyName)), Is.True); +#pragma warning restore SCME0001 + + var data = ModelReaderWriter.Write(model, ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default); + using var document = JsonDocument.Parse(data); + Assert.That(document.RootElement.GetProperty(propertyName).GetRawText(), Is.EqualTo(expected)); + } + + [TestCase("children", false)] + [TestCase("children", true)] + [TestCase("nestedChildren", false)] + [TestCase("nestedChildren", true)] + [TestCase("dictionaryChildren", false)] + [TestCase("dictionaryChildren", true)] + public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(string propertyName, bool unrelatedPatch) { var removed = new AnotherDynamicModel("removed"); - var model = new NullableDynamicModel + IList items = [null!, removed, new AnotherDynamicModel("present")]; + var model = propertyName switch { - Children = [null, removed, new AnotherDynamicModel("present")] + "children" => new NullableDynamicModel { Children = items }, + "nestedChildren" => new NullableDynamicModel { NestedChildren = [items] }, + "dictionaryChildren" => new NullableDynamicModel + { + DictionaryChildren = new Dictionary> { ["key"] = items } + }, + _ => throw new ArgumentOutOfRangeException(nameof(propertyName)) }; #pragma warning disable SCME0001 @@ -419,14 +483,24 @@ public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(bool unrelate { model.Patch.Set("$.unrelated"u8, 1); } - Assert.That(model.Patch.Contains("$"u8, "children"u8), Is.False); - var snapshot = model.Patch.GetJson("$.children"u8); + Assert.That(model.Patch.Contains("$"u8, Encoding.UTF8.GetBytes(propertyName)), Is.False); + if (propertyName == "children") + { + var snapshot = model.Patch.GetJson("$.children"u8); + Assert.That(Encoding.UTF8.GetString(snapshot), Is.EqualTo("""[null,{"bar":"present"}]""")); + } #pragma warning restore SCME0001 - Assert.That(Encoding.UTF8.GetString(snapshot), Is.EqualTo("""[null,{"bar":"present"}]""")); var data = ModelReaderWriter.Write(model, ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default); using var document = JsonDocument.Parse(data); - Assert.That(document.RootElement.GetProperty("children").GetRawText(), Is.EqualTo("""[null,{"bar":"present"}]""")); + var collection = document.RootElement.GetProperty(propertyName); + var children = propertyName switch + { + "nestedChildren" => collection[0], + "dictionaryChildren" => collection.GetProperty("key"), + _ => collection + }; + Assert.That(children.GetRawText(), Is.EqualTo("""[null,{"bar":"present"}]""")); } private static NullableDynamicModel CreateModelWithRemovedDynamicListElements(string propertyName, bool onlyNull) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs index 3e786fd1bea..3636e298ab0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs @@ -41,17 +41,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("cats"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "cats"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in Cats) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.cats"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.cats"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -59,24 +64,32 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - Patch.WriteTo(writer, "$.cats"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.cats"u8); + } writer.WriteEndObject(); } if (!Patch.Contains("$.names"u8)) { writer.WritePropertyName("names"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "names"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in Names) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.names"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.names"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -89,24 +102,32 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - Patch.WriteTo(writer, "$.names"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.names"u8); + } writer.WriteEndObject(); } if ((global::Sample.Optional.IsCollectionDefined(OptionalNames) && !Patch.Contains("$.optionalNames"u8))) { writer.WritePropertyName("optionalNames"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "optionalNames"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in OptionalNames) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNames"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNames"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -119,7 +140,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - Patch.WriteTo(writer, "$.optionalNames"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.optionalNames"u8); + } writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index f3c36c61481..529da5ddc72 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -88,17 +88,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); + bool hasPatch2 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PropertyWithNestedArray[i][i0][i1]) { + bool patchContains = false; + if (hasPatch2) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -111,13 +116,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")); + if (hasPatch2) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")); + } writer.WriteEndObject(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + if (hasPatch1) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + } writer.WriteEndArray(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs index 5b20e01d13d..d10f71bea2d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs @@ -84,10 +84,16 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } writer.WriteObjectValue(PropertyWithNestedArray[i][i0][i1], options); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + if (hasPatch1) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + } writer.WriteEndArray(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs index bbbb0586dfa..c497b08f698 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs @@ -89,10 +89,16 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } writer.WriteStringValue(PropertyWithNestedArray[i][i0][i1]); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + if (hasPatch1) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + } writer.WriteEndArray(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index e2997e91593..9ba1d267dae 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -41,17 +41,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PropertyWithNestedDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -61,17 +66,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); + bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { + bool patchContains0 = false; + if (hasPatch0) + { #if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); #else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); + patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); #endif + } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -81,17 +91,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); + bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; #endif foreach (var item1 in item0.Value) { + bool patchContains1 = false; + if (hasPatch1) + { #if NET8_0_OR_GREATER - int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); - bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); + patchContains1 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten)); #else - bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); + patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); #endif + } if (!patchContains1) { writer.WritePropertyName(item1.Key); @@ -99,17 +114,26 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); + if (hasPatch1) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); + } writer.WriteEndObject(); } } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); + if (hasPatch0) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); + } writer.WriteEndObject(); } } - Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); + } writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index 0dc4862a482..b29536cceb8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -41,17 +41,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PropertyWithNestedDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -61,17 +66,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); + bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { + bool patchContains0 = false; + if (hasPatch0) + { #if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); #else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); + patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); #endif + } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -81,17 +91,22 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); + bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; #endif foreach (var item1 in item0.Value) { + bool patchContains1 = false; + if (hasPatch1) + { #if NET8_0_OR_GREATER - int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); - bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); + patchContains1 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten)); #else - bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); + patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); #endif + } if (!patchContains1) { writer.WritePropertyName(item1.Key); @@ -104,17 +119,26 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); + if (hasPatch1) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); + } writer.WriteEndObject(); } } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); + if (hasPatch0) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); + } writer.WriteEndObject(); } } - Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); + } writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs index 27e072713e6..3afd5923446 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs @@ -177,17 +177,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("optionalNullableDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "optionalNullableDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in OptionalNullableDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNullableDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNullableDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.optionalNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.optionalNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -195,24 +200,32 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, "$.optionalNullableDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.optionalNullableDictionary"u8); + } writer.WriteEndObject(); } if (Optional.IsCollectionDefined(RequiredNullableDictionary) && !Patch.Contains("$.requiredNullableDictionary"u8)) { writer.WritePropertyName("requiredNullableDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "requiredNullableDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in RequiredNullableDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.requiredNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.requiredNullableDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.requiredNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.requiredNullableDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.requiredNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.requiredNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -220,7 +233,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, "$.requiredNullableDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.requiredNullableDictionary"u8); + } writer.WriteEndObject(); } else if (!Patch.Contains("$.requiredNullableDictionary"u8)) @@ -231,17 +247,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("primitiveDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "primitiveDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PrimitiveDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.primitiveDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.primitiveDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.primitiveDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.primitiveDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.primitiveDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.primitiveDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -249,7 +270,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, "$.primitiveDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.primitiveDictionary"u8); + } writer.WriteEndObject(); } if (!Patch.Contains("$.foo"u8)) @@ -315,7 +339,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } writer.WriteObjectValue(ListOfListFoo[i][i0], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.listOfListFoo"u8); @@ -325,17 +352,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("dictionaryFoo"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryFoo) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryFoo"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.dictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -343,24 +375,32 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, "$.dictionaryFoo"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.dictionaryFoo"u8); + } writer.WriteEndObject(); } if (!Patch.Contains("$.dictionaryOfDictionaryFoo"u8)) { writer.WritePropertyName("dictionaryOfDictionaryFoo"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryOfDictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryOfDictionaryFoo) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryOfDictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryOfDictionaryFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryOfDictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryOfDictionaryFoo"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryOfDictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.dictionaryOfDictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -370,17 +410,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); + bool hasPatch0 = Patch.Contains("$"u8, "dictionaryOfDictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { + bool patchContains0 = false; + if (hasPatch0) + { #if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); #else - bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); + patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); #endif + } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -388,29 +433,40 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]")); + } writer.WriteEndObject(); } } - Patch.WriteTo(writer, "$.dictionaryOfDictionaryFoo"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.dictionaryOfDictionaryFoo"u8); + } writer.WriteEndObject(); } if (!Patch.Contains("$.dictionaryListFoo"u8)) { writer.WritePropertyName("dictionaryListFoo"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryListFoo) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryListFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryListFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryListFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryListFoo"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryListFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.dictionaryListFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -420,21 +476,27 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); + bool hasPatch0 = Patch.Contains("$"u8, "dictionaryListFoo"u8); for (int i = 0; i < item.Value.Count; i++) { - if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } writer.WriteObjectValue(item.Value[i], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"]")); + } writer.WriteEndArray(); } } - Patch.WriteTo(writer, "$.dictionaryListFoo"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.dictionaryListFoo"u8); + } writer.WriteEndObject(); } if (Patch.Contains("$.listOfDictionaryFoo"u8)) @@ -462,17 +524,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); + bool hasPatch0 = Patch.Contains("$"u8, "listOfDictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in ListOfDictionaryFoo[i]) { + bool patchContains = false; + if (hasPatch0) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -480,7 +547,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]")); + } writer.WriteEndObject(); } Patch.WriteTo(writer, "$.listOfDictionaryFoo"u8); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs index 43814cb472f..9a09b42c019 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs @@ -116,17 +116,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("childDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "childDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in ChildDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.childDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.childDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.childDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.childDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.childDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.childDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -134,7 +139,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, "$.childDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.childDictionary"u8); + } writer.WriteEndObject(); } if (Patch.Contains("$.nestedChildren"u8)) @@ -171,7 +179,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } writer.WriteObjectValue(NestedChildren[i][i0], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.nestedChildren"u8); @@ -181,17 +192,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("nestedChildDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "nestedChildDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in NestedChildDictionary) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.nestedChildDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.nestedChildDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.nestedChildDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.nestedChildDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.nestedChildDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.nestedChildDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -201,17 +217,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); + bool hasPatch0 = Patch.Contains("$"u8, "nestedChildDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { + bool patchContains0 = false; + if (hasPatch0) + { #if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); #else - bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); + patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); #endif + } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -219,29 +240,40 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]")); + } writer.WriteEndObject(); } } - Patch.WriteTo(writer, "$.nestedChildDictionary"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.nestedChildDictionary"u8); + } writer.WriteEndObject(); } if (Optional.IsCollectionDefined(DictionaryChildren) && !Patch.Contains("$.dictionaryChildren"u8)) { writer.WritePropertyName("dictionaryChildren"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryChildren) { + bool patchContains = false; + if (hasPatch) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryChildren"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryChildren"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryChildren"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryChildren"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryChildren"u8, Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains("$.dictionaryChildren"u8, Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -251,21 +283,27 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); + bool hasPatch0 = Patch.Contains("$"u8, "dictionaryChildren"u8); for (int i = 0; i < item.Value.Count; i++) { - if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } writer.WriteObjectValue(item.Value[i], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"]")); + } writer.WriteEndArray(); } } - Patch.WriteTo(writer, "$.dictionaryChildren"u8); + if (hasPatch) + { + Patch.WriteTo(writer, "$.dictionaryChildren"u8); + } writer.WriteEndObject(); } if (Patch.Contains("$.listOfDictionaries"u8)) @@ -293,17 +331,22 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); + bool hasPatch0 = Patch.Contains("$"u8, "listOfDictionaries"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in ListOfDictionaries[i]) { + bool patchContains = false; + if (hasPatch0) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), Encoding.UTF8.GetBytes(item.Key)); + patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), Encoding.UTF8.GetBytes(item.Key)); #endif + } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -311,7 +354,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]")); + if (hasPatch0) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]")); + } writer.WriteEndObject(); } Patch.WriteTo(writer, "$.listOfDictionaries"u8); From b2836939eeccdb07ccb63c64d5e841be406318bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:12:35 +0000 Subject: [PATCH 04/30] revert(http-client-csharp): undo nested collection patch guard follow-up Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 32 ++-- .../Sample_TypeSpec/DynamicModelTests.cs | 104 ++----------- .../WriteDictionaryProperties.cs | 48 ++---- .../WriteNestedArrayDictionaryProperties.cs | 26 +--- .../WriteNestedArrayDynamicModelProperties.cs | 10 +- .../WriteNestedArrayPrimitiveProperties.cs | 10 +- .../WriteNestedDictDynamicModelProperties.cs | 48 ++---- .../WriteNestedDictPrimitiveProperties.cs | 48 ++---- .../Models/DynamicModel.Serialization.cs | 142 +++++------------- .../NullableDynamicModel.Serialization.cs | 94 +++--------- 10 files changed, 132 insertions(+), 430 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 192ee171174..b07e26c20c8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -33,11 +33,6 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( ValueExpression jsonPath = parentIndices.Count > 0 ? Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, [.. parentIndices]).As()) : LiteralU8($"$.{serializedName}"); - var hasPatchDeclaration = Declare( - "hasPatch", - typeof(bool), - patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName.Split('.')[0])), - out var hasPatch); var foreachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression keyValuePair); @@ -53,25 +48,23 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( Utf8Snippets.GetBytes(keyValuePair.Key.Invoke("AsSpan"), bufferVar), out var bytesWrittenVar); var patchContainsKey = patchSnippet.Contains(jsonPath, Utf8Snippets.GetBytes(keyValuePair.Key.As())); - var patchContainsDeclaration = Declare( + var patchContainsNet8Declaration = Declare( "patchContains", typeof(bool), - False, - out var patchContains); - var patchContainsNet8Assignment = patchContains.Assign( new TernaryConditionalExpression( bytesWrittenVar.Equal(Int(BufferSize)), patchContainsKey, patchSnippet.Contains( jsonPath, - ReadOnlySpanSnippets.Slice(bufferVar, Int(0), bytesWrittenVar)))).Terminate(); + ReadOnlySpanSnippets.Slice(bufferVar, Int(0), bytesWrittenVar))), + out var patchContainsNet8Var); List childIndices = keyValuePair.ValueType.IsCollection ? [.. parentIndices, keyValuePair.Key] : parentIndices; // Process key-value pair if patch doesn't contain it - var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContains)) + var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) { _utf8JsonWriterSnippet.WritePropertyName(keyValuePair.Key), CreateElementSerializationWithPatch( @@ -85,21 +78,21 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( "NET8_0_OR_GREATER", - new MethodBodyStatement[] { bytesWrittenDeclaration, patchContainsNet8Assignment }, - patchContains.Assign(patchContainsKey).Terminate()); + new MethodBodyStatement[] { bytesWrittenDeclaration, patchContainsNet8Declaration }, + new DeclarationExpression(new VariableExpression(patchContainsNet8Var.Type, patchContainsNet8Var.Declaration)) + .Assign(patchContainsKey) + .Terminate()); - foreachStatement.Add(patchContainsDeclaration); - foreachStatement.Add(new IfStatement(hasPatch) { innerIfElseProcessorStatement }); + foreachStatement.Add(innerIfElseProcessorStatement); foreachStatement.Add(ifPatchDoesNotContainStatement); return new[] { _utf8JsonWriterSnippet.WriteStartObject(), - hasPatchDeclaration, new IfElsePreprocessorStatement("NET8_0_OR_GREATER", bufferDeclaration), foreachStatement, MethodBodyStatement.EmptyLine, - new IfStatement(hasPatch) { patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate() }, + patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate(), _utf8JsonWriterSnippet.WriteEndObject(), }; } @@ -166,10 +159,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var writeToPatchStatement = parentIndices.Count == 0 ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() - : new IfStatement(hasPatch) - { - patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, parentIndices).As())).Terminate() - }; + : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, parentIndices).As())).Terminate(); return new[] { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs index 066c64dbf58..ee75c25c910 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs @@ -370,39 +370,13 @@ public void JsonPatchRemove_NullDynamicListElementSnapshot(bool onlyNull) Assert.That(Encoding.UTF8.GetString(json), Is.EqualTo(onlyNull ? "[]" : """[{"bar":"present"},null]""")); } - [TestCase("children", false)] - [TestCase("children", true)] - [TestCase("nestedChildren", false)] - [TestCase("nestedChildren", true)] - [TestCase("childDictionary", false)] - [TestCase("childDictionary", true)] - [TestCase("nestedChildDictionary", false)] - [TestCase("nestedChildDictionary", true)] - [TestCase("dictionaryChildren", false)] - [TestCase("dictionaryChildren", true)] - [TestCase("listOfDictionaries", false)] - [TestCase("listOfDictionaries", true)] - public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(string propertyName, bool unrelatedPatch) + [TestCase(false)] + [TestCase(true)] + public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(bool unrelatedPatch) { - var dictionary = Enumerable.Range(0, 256).ToDictionary(i => $"key{i}", _ => (AnotherDynamicModel)null!); - var model = propertyName switch + var model = new NullableDynamicModel { - "children" => new NullableDynamicModel { Children = new AnotherDynamicModel[256] }, - "nestedChildren" => new NullableDynamicModel - { - NestedChildren = Enumerable.Repeat>(Array.Empty(), 256).ToArray() - }, - "childDictionary" => new NullableDynamicModel { ChildDictionary = dictionary }, - "nestedChildDictionary" => new NullableDynamicModel - { - NestedChildDictionary = new Dictionary> { ["key"] = dictionary } - }, - "dictionaryChildren" => new NullableDynamicModel - { - DictionaryChildren = Enumerable.Range(0, 256).ToDictionary(i => $"key{i}", _ => (IList)Array.Empty()) - }, - "listOfDictionaries" => new NullableDynamicModel { ListOfDictionaries = [dictionary] }, - _ => throw new ArgumentOutOfRangeException(nameof(propertyName)) + Children = new AnotherDynamicModel[256] }; #pragma warning disable SCME0001 if (unrelatedPatch) @@ -426,55 +400,17 @@ public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(string p Assert.That(allocated, Is.LessThan(1024), "Indexed patch paths must not allocate for each unpatched element."); using var document = JsonDocument.Parse(buffer.WrittenMemory); - var collection = document.RootElement.GetProperty(propertyName); - collection = propertyName switch - { - "nestedChildDictionary" => collection.GetProperty("key"), - "listOfDictionaries" => collection[0], - _ => collection - }; - Assert.That(collection.ValueKind == JsonValueKind.Array ? collection.GetArrayLength() : collection.EnumerateObject().Count(), Is.EqualTo(256)); + Assert.That(document.RootElement.GetProperty("children").GetArrayLength(), Is.EqualTo(256)); } - [TestCase("childDictionary", """{"removed":null,"present":null}""", "$.childDictionary.removed", "$.childDictionary.added", """{"present":null,"added":{"bar":"added"}}""")] - [TestCase("listOfDictionaries", """[{"removed":null,"present":null}]""", "$.listOfDictionaries[0].removed", "$.listOfDictionaries[0].added", """[{"present":null,"added":{"bar":"added"}}]""")] - public void JsonModelWrite_DictionaryPatchesArePreserved(string propertyName, string value, string removedPath, string addedPath, string expected) - { - var model = ModelReaderWriter.Read( - BinaryData.FromString($$"""{"{{propertyName}}":{{value}}}"""), - ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default)!; - -#pragma warning disable SCME0001 - model.Patch.Remove(Encoding.UTF8.GetBytes(removedPath)); - model.Patch.Set(Encoding.UTF8.GetBytes(addedPath), """{"bar":"added"}"""u8); - Assert.That(model.Patch.Contains(Encoding.UTF8.GetBytes($"$.{propertyName}")), Is.False); - Assert.That(model.Patch.Contains("$"u8, Encoding.UTF8.GetBytes(propertyName)), Is.True); -#pragma warning restore SCME0001 - - var data = ModelReaderWriter.Write(model, ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default); - using var document = JsonDocument.Parse(data); - Assert.That(document.RootElement.GetProperty(propertyName).GetRawText(), Is.EqualTo(expected)); - } - - [TestCase("children", false)] - [TestCase("children", true)] - [TestCase("nestedChildren", false)] - [TestCase("nestedChildren", true)] - [TestCase("dictionaryChildren", false)] - [TestCase("dictionaryChildren", true)] - public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(string propertyName, bool unrelatedPatch) + [TestCase(false)] + [TestCase(true)] + public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(bool unrelatedPatch) { var removed = new AnotherDynamicModel("removed"); - IList items = [null!, removed, new AnotherDynamicModel("present")]; - var model = propertyName switch + var model = new NullableDynamicModel { - "children" => new NullableDynamicModel { Children = items }, - "nestedChildren" => new NullableDynamicModel { NestedChildren = [items] }, - "dictionaryChildren" => new NullableDynamicModel - { - DictionaryChildren = new Dictionary> { ["key"] = items } - }, - _ => throw new ArgumentOutOfRangeException(nameof(propertyName)) + Children = [null, removed, new AnotherDynamicModel("present")] }; #pragma warning disable SCME0001 @@ -483,24 +419,14 @@ public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(string proper { model.Patch.Set("$.unrelated"u8, 1); } - Assert.That(model.Patch.Contains("$"u8, Encoding.UTF8.GetBytes(propertyName)), Is.False); - if (propertyName == "children") - { - var snapshot = model.Patch.GetJson("$.children"u8); - Assert.That(Encoding.UTF8.GetString(snapshot), Is.EqualTo("""[null,{"bar":"present"}]""")); - } + Assert.That(model.Patch.Contains("$"u8, "children"u8), Is.False); + var snapshot = model.Patch.GetJson("$.children"u8); #pragma warning restore SCME0001 + Assert.That(Encoding.UTF8.GetString(snapshot), Is.EqualTo("""[null,{"bar":"present"}]""")); var data = ModelReaderWriter.Write(model, ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default); using var document = JsonDocument.Parse(data); - var collection = document.RootElement.GetProperty(propertyName); - var children = propertyName switch - { - "nestedChildren" => collection[0], - "dictionaryChildren" => collection.GetProperty("key"), - _ => collection - }; - Assert.That(children.GetRawText(), Is.EqualTo("""[null,{"bar":"present"}]""")); + Assert.That(document.RootElement.GetProperty("children").GetRawText(), Is.EqualTo("""[null,{"bar":"present"}]""")); } private static NullableDynamicModel CreateModelWithRemovedDynamicListElements(string propertyName, bool onlyNull) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs index 3636e298ab0..3e786fd1bea 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs @@ -41,22 +41,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("cats"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "cats"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in Cats) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.cats"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.cats"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -64,32 +59,24 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.cats"u8); - } + Patch.WriteTo(writer, "$.cats"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.names"u8)) { writer.WritePropertyName("names"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "names"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in Names) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.names"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.names"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -102,32 +89,24 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.names"u8); - } + Patch.WriteTo(writer, "$.names"u8); writer.WriteEndObject(); } if ((global::Sample.Optional.IsCollectionDefined(OptionalNames) && !Patch.Contains("$.optionalNames"u8))) { writer.WritePropertyName("optionalNames"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "optionalNames"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in OptionalNames) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNames"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNames"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -140,10 +119,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.optionalNames"u8); - } + Patch.WriteTo(writer, "$.optionalNames"u8); writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index 529da5ddc72..f3c36c61481 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -88,22 +88,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - bool hasPatch2 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PropertyWithNestedArray[i][i0][i1]) { - bool patchContains = false; - if (hasPatch2) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -116,22 +111,13 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - if (hasPatch2) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")); writer.WriteEndObject(); } - if (hasPatch1) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); writer.WriteEndArray(); } - if (hasPatch0) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs index d10f71bea2d..5b20e01d13d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs @@ -84,16 +84,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } writer.WriteObjectValue(PropertyWithNestedArray[i][i0][i1], options); } - if (hasPatch1) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); writer.WriteEndArray(); } - if (hasPatch0) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs index c497b08f698..bbbb0586dfa 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs @@ -89,16 +89,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } writer.WriteStringValue(PropertyWithNestedArray[i][i0][i1]); } - if (hasPatch1) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); writer.WriteEndArray(); } - if (hasPatch0) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index 9ba1d267dae..e2997e91593 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -41,22 +41,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PropertyWithNestedDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -66,22 +61,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { - bool patchContains0 = false; - if (hasPatch0) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); #endif - } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -91,22 +81,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; #endif foreach (var item1 in item0.Value) { - bool patchContains1 = false; - if (hasPatch1) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); - patchContains1 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten)); + int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); + bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); #else - patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); + bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); #endif - } if (!patchContains1) { writer.WritePropertyName(item1.Key); @@ -114,26 +99,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - if (hasPatch1) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); writer.WriteEndObject(); } } - if (hasPatch0) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); writer.WriteEndObject(); } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); - } + Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index b29536cceb8..0dc4862a482 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -41,22 +41,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PropertyWithNestedDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -66,22 +61,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { - bool patchContains0 = false; - if (hasPatch0) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); #endif - } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -91,22 +81,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; #endif foreach (var item1 in item0.Value) { - bool patchContains1 = false; - if (hasPatch1) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); - patchContains1 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten)); + int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); + bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); #else - patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); + bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); #endif - } if (!patchContains1) { writer.WritePropertyName(item1.Key); @@ -119,26 +104,17 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } } - if (hasPatch1) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); writer.WriteEndObject(); } } - if (hasPatch0) - { - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); - } + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); writer.WriteEndObject(); } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); - } + Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs index 3afd5923446..27e072713e6 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs @@ -177,22 +177,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("optionalNullableDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "optionalNullableDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in OptionalNullableDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNullableDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNullableDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.optionalNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.optionalNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -200,32 +195,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.optionalNullableDictionary"u8); - } + Patch.WriteTo(writer, "$.optionalNullableDictionary"u8); writer.WriteEndObject(); } if (Optional.IsCollectionDefined(RequiredNullableDictionary) && !Patch.Contains("$.requiredNullableDictionary"u8)) { writer.WritePropertyName("requiredNullableDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "requiredNullableDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in RequiredNullableDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.requiredNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.requiredNullableDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.requiredNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.requiredNullableDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.requiredNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.requiredNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -233,10 +220,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.requiredNullableDictionary"u8); - } + Patch.WriteTo(writer, "$.requiredNullableDictionary"u8); writer.WriteEndObject(); } else if (!Patch.Contains("$.requiredNullableDictionary"u8)) @@ -247,22 +231,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("primitiveDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "primitiveDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in PrimitiveDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.primitiveDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.primitiveDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.primitiveDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.primitiveDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.primitiveDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.primitiveDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -270,10 +249,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.primitiveDictionary"u8); - } + Patch.WriteTo(writer, "$.primitiveDictionary"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.foo"u8)) @@ -339,10 +315,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } writer.WriteObjectValue(ListOfListFoo[i][i0], options); } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]")); writer.WriteEndArray(); } Patch.WriteTo(writer, "$.listOfListFoo"u8); @@ -352,22 +325,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("dictionaryFoo"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryFoo) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryFoo"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.dictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -375,32 +343,24 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.dictionaryFoo"u8); - } + Patch.WriteTo(writer, "$.dictionaryFoo"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.dictionaryOfDictionaryFoo"u8)) { writer.WritePropertyName("dictionaryOfDictionaryFoo"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryOfDictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryOfDictionaryFoo) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryOfDictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryOfDictionaryFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryOfDictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryOfDictionaryFoo"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.dictionaryOfDictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryOfDictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -410,22 +370,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); - bool hasPatch0 = Patch.Contains("$"u8, "dictionaryOfDictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { - bool patchContains0 = false; - if (hasPatch0) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); + bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); #endif - } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -433,40 +388,29 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]")); writer.WriteEndObject(); } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.dictionaryOfDictionaryFoo"u8); - } + Patch.WriteTo(writer, "$.dictionaryOfDictionaryFoo"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.dictionaryListFoo"u8)) { writer.WritePropertyName("dictionaryListFoo"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryListFoo) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryListFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryListFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryListFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryListFoo"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.dictionaryListFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryListFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -476,27 +420,21 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "dictionaryListFoo"u8); + bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); for (int i = 0; i < item.Value.Count; i++) { - if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } writer.WriteObjectValue(item.Value[i], options); } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"]")); writer.WriteEndArray(); } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.dictionaryListFoo"u8); - } + Patch.WriteTo(writer, "$.dictionaryListFoo"u8); writer.WriteEndObject(); } if (Patch.Contains("$.listOfDictionaryFoo"u8)) @@ -524,22 +462,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); - bool hasPatch0 = Patch.Contains("$"u8, "listOfDictionaryFoo"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in ListOfDictionaryFoo[i]) { - bool patchContains = false; - if (hasPatch0) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -547,10 +480,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]")); writer.WriteEndObject(); } Patch.WriteTo(writer, "$.listOfDictionaryFoo"u8); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs index 9a09b42c019..43814cb472f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs @@ -116,22 +116,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("childDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "childDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in ChildDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.childDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.childDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.childDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.childDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.childDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.childDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -139,10 +134,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.childDictionary"u8); - } + Patch.WriteTo(writer, "$.childDictionary"u8); writer.WriteEndObject(); } if (Patch.Contains("$.nestedChildren"u8)) @@ -179,10 +171,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } writer.WriteObjectValue(NestedChildren[i][i0], options); } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]")); writer.WriteEndArray(); } Patch.WriteTo(writer, "$.nestedChildren"u8); @@ -192,22 +181,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("nestedChildDictionary"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "nestedChildDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in NestedChildDictionary) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.nestedChildDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.nestedChildDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.nestedChildDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.nestedChildDictionary"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.nestedChildDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.nestedChildDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -217,22 +201,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); - bool hasPatch0 = Patch.Contains("$"u8, "nestedChildDictionary"u8); #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; #endif foreach (var item0 in item.Value) { - bool patchContains0 = false; - if (hasPatch0) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - patchContains0 = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); + bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); #endif - } if (!patchContains0) { writer.WritePropertyName(item0.Key); @@ -240,40 +219,29 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]")); writer.WriteEndObject(); } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.nestedChildDictionary"u8); - } + Patch.WriteTo(writer, "$.nestedChildDictionary"u8); writer.WriteEndObject(); } if (Optional.IsCollectionDefined(DictionaryChildren) && !Patch.Contains("$.dictionaryChildren"u8)) { writer.WritePropertyName("dictionaryChildren"u8); writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in DictionaryChildren) { - bool patchContains = false; - if (hasPatch) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryChildren"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryChildren"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryChildren"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryChildren"u8, buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains("$.dictionaryChildren"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryChildren"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -283,27 +251,21 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "dictionaryChildren"u8); + bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); for (int i = 0; i < item.Value.Count; i++) { - if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } writer.WriteObjectValue(item.Value[i], options); } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"]")); writer.WriteEndArray(); } } - if (hasPatch) - { - Patch.WriteTo(writer, "$.dictionaryChildren"u8); - } + Patch.WriteTo(writer, "$.dictionaryChildren"u8); writer.WriteEndObject(); } if (Patch.Contains("$.listOfDictionaries"u8)) @@ -331,22 +293,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); - bool hasPatch0 = Patch.Contains("$"u8, "listOfDictionaries"u8); #if NET8_0_OR_GREATER global::System.Span buffer = stackalloc byte[256]; #endif foreach (var item in ListOfDictionaries[i]) { - bool patchContains = false; - if (hasPatch0) - { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), buffer.Slice(0, bytesWritten)); #else - patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), Encoding.UTF8.GetBytes(item.Key)); #endif - } if (!patchContains) { writer.WritePropertyName(item.Key); @@ -354,10 +311,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } } - if (hasPatch0) - { - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]")); - } + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]")); writer.WriteEndObject(); } Patch.WriteTo(writer, "$.listOfDictionaries"u8); From 06c4f8c0b40c4411dfd45c569c5c11a529bdb80a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:27:29 +0000 Subject: [PATCH 05/30] fix(http-client-csharp): preserve dotted patch property names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 4 +- .../DynamicModelSerializationTests.cs | 32 +++++++++ ...ttedSerializedNameCollectionPatchGuards.cs | 71 +++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index b07e26c20c8..b0bcda4f714 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -114,7 +114,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var hasPatchDeclaration = Declare( "hasPatch", typeof(bool), - patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName.Split('.')[0])), + patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName)), out var hasPatch); var patchIsRemovedCondition = hasPatch.As().And(patchSnippet.IsRemoved( Utf8Snippets.GetBytes( @@ -616,7 +616,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) var hasPatchDeclaration = Declare( "hasPatch", typeof(bool), - _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName.Split('.')[0])), + _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName)), out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( BuildJsonPathForElement(serializedName, [indexVar]), diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index f301a4673b8..ddc0a4993c7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -433,6 +433,38 @@ public void PropagateModelListPropertyHelperMethods() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } + [Test] + public void DottedSerializedNameCollectionPatchGuards() + { + var inputModel = InputFactory.Model( + "dynamicModel", + isDynamicModel: true, + properties: + [ + InputFactory.Property( + "children", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + serializedName: "foo.bar") + ]); + + MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; + + Assert.IsNotNull(model); + var serialization = model!.SerializationProviders.Single(); + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serialization, + name => name is "JsonModelWriteCore" or "ActiveChildren")); + + Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); + } + [Test] public void PropagateModelDictionaryProperty() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs new file mode 100644 index 00000000000..08013d40622 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs @@ -0,0 +1,71 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Collections.Generic; +using System.Text; +using System.Text.Json; +using Sample.Models; + +namespace Sample +{ + public partial class DynamicModel + { + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format."); + } +#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + if (Patch.Contains("$.foo.bar"u8)) + { + if (!Patch.IsRemoved("$.foo.bar"u8)) + { + writer.WritePropertyName("foo.bar"u8); + Patch.WriteTo(writer, "$.foo.bar"u8); + } + } + else if (global::Sample.Optional.IsCollectionDefined(Children)) + { + writer.WritePropertyName("foo.bar"u8); + writer.WriteStartArray(); + bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); + for (int i = 0; (i < Children.Count); i++) + { + if (((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.foo.bar[{i}]"))) || ((Children[i] != null) && Children[i].Patch.IsRemoved("$"u8)))) + { + continue; + } + writer.WriteObjectValue(Children[i], options); + } + Patch.WriteTo(writer, "$.foo.bar"u8); + writer.WriteEndArray(); + } + + Patch.WriteTo(writer); +#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + } + +#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + private global::System.Collections.Generic.IEnumerable ActiveChildren() + { + if (!global::Sample.Optional.IsCollectionDefined(Children)) + { + yield break; + } + bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); + for (int i = 0; (i < Children.Count); i++) + { + if (((!hasPatch || !Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.foo.bar[{i}]"))) && ((Children[i] == null) || !Children[i].Patch.IsRemoved("$"u8)))) + { + yield return Children[i]; + } + } + } +#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + } +} From c267f7579a5faab32cbaa028aa7234546916afc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:40:20 +0000 Subject: [PATCH 06/30] fix(http-client-csharp): escape dotted patch paths Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 34 ++++++++++++------- ...ttedSerializedNameCollectionPatchGuards.cs | 12 +++---- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index b0bcda4f714..ce8e4b8b5e4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -30,9 +30,10 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( parentIndices ??= []; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); ValueExpression jsonPath = parentIndices.Count > 0 - ? Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, [.. parentIndices]).As()) - : LiteralU8($"$.{serializedName}"); + ? Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, [.. parentIndices]).As()) + : LiteralU8(jsonPathTemplate); var foreachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression keyValuePair); @@ -110,6 +111,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var indexDeclaration = Declare("i", out var indexVar); var allIndices = new List(parentIndices) { indexVar }; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); // The prefix overload includes indexed descendants, unlike an exact-path Contains check. var hasPatchDeclaration = Declare( "hasPatch", @@ -118,7 +120,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( out var hasPatch); var patchIsRemovedCondition = hasPatch.As().And(patchSnippet.IsRemoved( Utf8Snippets.GetBytes( - new FormattableStringExpression(jsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) + new FormattableStringExpression(csharpJsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) .As()))); // Handle model types with their own patch property @@ -159,7 +161,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var writeToPatchStatement = parentIndices.Count == 0 ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() - : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, parentIndices).As())).Terminate(); + : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, parentIndices).As())).Terminate(); return new[] { @@ -222,7 +224,7 @@ private IfElseStatement CreateConditionalPatchSerializationStatement( MethodBodyStatement writePropertySerializationStatement, MethodBodyStatement? elseStatementBody) { - string jsonPath = $"$.{serializedName}"; + string jsonPath = BuildJsonPathForElement(serializedName, []); var ifPatchIsNotRemoved = new IfStatement(Not(_jsonPatchProperty!.As().IsRemoved(LiteralU8(jsonPath)))) { _utf8JsonWriterSnippet.WritePropertyName(serializedName), @@ -619,7 +621,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName)), out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( - BuildJsonPathForElement(serializedName, [indexVar]), + BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpString: true), [indexVar]).As()); isActive = Not(hasPatch).Or(Not(_jsonPatchProperty!.As().IsRemoved(itemPath))).And(isActive); var forStatement = new ForStatement( @@ -670,15 +672,10 @@ private List GetQualifyingDynamicListProperties() #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - private static string BuildJsonPathForElement(string propertySerializedName, List indices) + private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpString = false) { var count = indices.Count; - if (count == 0) - { - return $"$.{propertySerializedName}"; - } - - var result = $"$.{propertySerializedName}"; + var result = BuildJsonPathForProperty(propertySerializedName, escapeForCSharpString); for (int i = 0; i < count; i++) { result += indices[i] is MemberExpression @@ -689,6 +686,17 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis return result; } + private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpString) + { + var jsonPath = propertySerializedName.Contains('.') + ? $"$[\"{propertySerializedName}\"]" + : $"$.{propertySerializedName}"; + + return escapeForCSharpString + ? jsonPath.Replace("\"", "\\\"") + : jsonPath; + } + private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs index 08013d40622..228d9253527 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs @@ -21,12 +21,12 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format."); } #pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - if (Patch.Contains("$.foo.bar"u8)) + if (Patch.Contains("$[\"foo.bar\"]"u8)) { - if (!Patch.IsRemoved("$.foo.bar"u8)) + if (!Patch.IsRemoved("$[\"foo.bar\"]"u8)) { writer.WritePropertyName("foo.bar"u8); - Patch.WriteTo(writer, "$.foo.bar"u8); + Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); } } else if (global::Sample.Optional.IsCollectionDefined(Children)) @@ -36,13 +36,13 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); for (int i = 0; (i < Children.Count); i++) { - if (((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.foo.bar[{i}]"))) || ((Children[i] != null) && Children[i].Patch.IsRemoved("$"u8)))) + if (((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$[\"foo.bar\"][{i}]"))) || ((Children[i] != null) && Children[i].Patch.IsRemoved("$"u8)))) { continue; } writer.WriteObjectValue(Children[i], options); } - Patch.WriteTo(writer, "$.foo.bar"u8); + Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); writer.WriteEndArray(); } @@ -60,7 +60,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); for (int i = 0; (i < Children.Count); i++) { - if (((!hasPatch || !Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.foo.bar[{i}]"))) && ((Children[i] == null) || !Children[i].Patch.IsRemoved("$"u8)))) + if (((!hasPatch || !Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$[\"foo.bar\"][{i}]"))) && ((Children[i] == null) || !Children[i].Patch.IsRemoved("$"u8)))) { yield return Children[i]; } From 92f3e8166af41295185f2c9608908d1f2b385249 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:42:11 +0000 Subject: [PATCH 07/30] fix(http-client-csharp): escape bracketed patch path segments Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index ce8e4b8b5e4..e1c29a141f3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -30,7 +30,7 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( parentIndices ??= []; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpInterpolatedString: true); ValueExpression jsonPath = parentIndices.Count > 0 ? Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, [.. parentIndices]).As()) : LiteralU8(jsonPathTemplate); @@ -111,7 +111,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var indexDeclaration = Declare("i", out var indexVar); var allIndices = new List(parentIndices) { indexVar }; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpInterpolatedString: true); // The prefix overload includes indexed descendants, unlike an exact-path Contains check. var hasPatchDeclaration = Declare( "hasPatch", @@ -621,7 +621,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName)), out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( - BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpString: true), + BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpInterpolatedString: true), [indexVar]).As()); isActive = Not(hasPatch).Or(Not(_jsonPatchProperty!.As().IsRemoved(itemPath))).And(isActive); var forStatement = new ForStatement( @@ -672,10 +672,10 @@ private List GetQualifyingDynamicListProperties() #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpString = false) + private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpInterpolatedString = false) { var count = indices.Count; - var result = BuildJsonPathForProperty(propertySerializedName, escapeForCSharpString); + var result = BuildJsonPathForProperty(propertySerializedName, escapeForCSharpInterpolatedString); for (int i = 0; i < count; i++) { result += indices[i] is MemberExpression @@ -686,17 +686,33 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis return result; } - private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpString) + private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpInterpolatedString) { - var jsonPath = propertySerializedName.Contains('.') - ? $"$[\"{propertySerializedName}\"]" + var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) + ? $"$[\"{EscapeJsonPathQuotedStringContent(propertySerializedName)}\"]" : $"$.{propertySerializedName}"; - return escapeForCSharpString - ? jsonPath.Replace("\"", "\\\"") + // FormattableStringExpression writes raw interpolated string text, unlike LiteralU8 which escapes string contents. + return escapeForCSharpInterpolatedString + ? EscapeCSharpStringContent(jsonPath) : jsonPath; } + private static bool RequiresJsonPathBracketNotation(string propertySerializedName) + { + return propertySerializedName.Any(c => c is '.' or '[' or ']' or '"' or '\'' or '\\' || char.IsWhiteSpace(c)); + } + + private static string EscapeJsonPathQuotedStringContent(string value) + { + return value.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + + private static string EscapeCSharpStringContent(string value) + { + return value.Replace("\\", "\\\\").Replace("\"", "\\\""); + } + private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, From a4f240b5c328adcd089b2fe01db4c72f93515bc8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:43:39 +0000 Subject: [PATCH 08/30] refactor(http-client-csharp): share patch path escaping helper Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index e1c29a141f3..45ac20ce226 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -689,12 +689,12 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpInterpolatedString) { var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) - ? $"$[\"{EscapeJsonPathQuotedStringContent(propertySerializedName)}\"]" + ? $"$[\"{EscapeBackslashAndDoubleQuote(propertySerializedName)}\"]" : $"$.{propertySerializedName}"; // FormattableStringExpression writes raw interpolated string text, unlike LiteralU8 which escapes string contents. return escapeForCSharpInterpolatedString - ? EscapeCSharpStringContent(jsonPath) + ? EscapeBackslashAndDoubleQuote(jsonPath) : jsonPath; } @@ -703,12 +703,7 @@ private static bool RequiresJsonPathBracketNotation(string propertySerializedNam return propertySerializedName.Any(c => c is '.' or '[' or ']' or '"' or '\'' or '\\' || char.IsWhiteSpace(c)); } - private static string EscapeJsonPathQuotedStringContent(string value) - { - return value.Replace("\\", "\\\\").Replace("\"", "\\\""); - } - - private static string EscapeCSharpStringContent(string value) + private static string EscapeBackslashAndDoubleQuote(string value) { return value.Replace("\\", "\\\\").Replace("\"", "\\\""); } From a13b3310a4ec3cf796a41c186ad1af8d88b6f3aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:45:52 +0000 Subject: [PATCH 09/30] test(http-client-csharp): cover escaped bracket patch paths Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../DynamicModelSerializationTests.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index ddc0a4993c7..472b9718bb0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -465,6 +465,40 @@ public void DottedSerializedNameCollectionPatchGuards() Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); } + [Test] + public void EscapedSerializedNameCollectionPatchGuards() + { + var inputModel = InputFactory.Model( + "dynamicModel", + isDynamicModel: true, + properties: + [ + InputFactory.Property( + "children", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + serializedName: "foo bar[\"\\baz") + ]); + + MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; + + Assert.IsNotNull(model); + var serialization = model!.SerializationProviders.Single(); + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serialization, + name => name is "JsonModelWriteCore" or "ActiveChildren")); + var content = writer.Write().Content; + + StringAssert.Contains("""Patch.Contains("$[\"foo bar[\\\"\\\\baz\"]"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"foo bar[\\\"\\\\baz\"][{i}]")""", content); + } + [Test] public void PropagateModelDictionaryProperty() { From 0aff3b23c1a6dea7b1a5e3d420cb33b006966fb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:47:34 +0000 Subject: [PATCH 10/30] fix(http-client-csharp): clarify patch path segment escaping Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 31 ++++++++++++++++--- .../DynamicModelSerializationTests.cs | 6 ++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 45ac20ce226..cd3270ce96a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -112,7 +112,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var allIndices = new List(parentIndices) { indexVar }; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpInterpolatedString: true); - // The prefix overload includes indexed descendants, unlike an exact-path Contains check. + // The prefix overload accepts a raw property segment and includes indexed descendants, unlike an exact-path Contains check. var hasPatchDeclaration = Declare( "hasPatch", typeof(bool), @@ -615,6 +615,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) isActive = item.Equal(Null).Or(isActive); } var serializedName = GetJsonSerializedName(property.WireInfo!); + // The prefix overload accepts a raw property segment and includes indexed descendants, unlike an exact-path Contains check. var hasPatchDeclaration = Declare( "hasPatch", typeof(bool), @@ -689,18 +690,40 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpInterpolatedString) { var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) - ? $"$[\"{EscapeBackslashAndDoubleQuote(propertySerializedName)}\"]" + ? $"$[\"{EscapeJsonPathSegment(propertySerializedName)}\"]" : $"$.{propertySerializedName}"; // FormattableStringExpression writes raw interpolated string text, unlike LiteralU8 which escapes string contents. return escapeForCSharpInterpolatedString - ? EscapeBackslashAndDoubleQuote(jsonPath) + ? EscapeForCSharpString(jsonPath) : jsonPath; } private static bool RequiresJsonPathBracketNotation(string propertySerializedName) { - return propertySerializedName.Any(c => c is '.' or '[' or ']' or '"' or '\'' or '\\' || char.IsWhiteSpace(c)); + return propertySerializedName.Length == 0 || + !IsJsonPathIdentifierStart(propertySerializedName[0]) || + propertySerializedName.Skip(1).Any(c => !IsJsonPathIdentifierPart(c)); + } + + private static bool IsJsonPathIdentifierStart(char c) + { + return c is '_' || char.IsLetter(c); + } + + private static bool IsJsonPathIdentifierPart(char c) + { + return IsJsonPathIdentifierStart(c) || char.IsDigit(c); + } + + private static string EscapeJsonPathSegment(string value) + { + return EscapeBackslashAndDoubleQuote(value); + } + + private static string EscapeForCSharpString(string value) + { + return EscapeBackslashAndDoubleQuote(value); } private static string EscapeBackslashAndDoubleQuote(string value) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index 472b9718bb0..832b980bd53 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -482,7 +482,7 @@ public void EscapedSerializedNameCollectionPatchGuards() [ InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) ])), - serializedName: "foo bar[\"\\baz") + serializedName: "1 foo-[\"\\baz") ]); MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); @@ -495,8 +495,8 @@ public void EscapedSerializedNameCollectionPatchGuards() name => name is "JsonModelWriteCore" or "ActiveChildren")); var content = writer.Write().Content; - StringAssert.Contains("""Patch.Contains("$[\"foo bar[\\\"\\\\baz\"]"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"foo bar[\\\"\\\\baz\"][{i}]")""", content); + StringAssert.Contains("""Patch.Contains("$[\"1 foo-[\\\"\\\\baz\"]"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo-[\\\"\\\\baz\"][{i}]")""", content); } [Test] From 5a5313e1329d0667ea8f6ab854428d96f5592435 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:49:00 +0000 Subject: [PATCH 11/30] refactor(http-client-csharp): simplify patch path escaping Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index cd3270ce96a..70c6c7f9649 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -673,6 +673,9 @@ private List GetQualifyingDynamicListProperties() #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + /// + /// Builds a JSONPath. Set when the path is written into a template. + /// private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpInterpolatedString = false) { var count = indices.Count; @@ -690,12 +693,12 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpInterpolatedString) { var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) - ? $"$[\"{EscapeJsonPathSegment(propertySerializedName)}\"]" + ? $"$[\"{EscapeBackslashAndDoubleQuote(propertySerializedName)}\"]" : $"$.{propertySerializedName}"; // FormattableStringExpression writes raw interpolated string text, unlike LiteralU8 which escapes string contents. return escapeForCSharpInterpolatedString - ? EscapeForCSharpString(jsonPath) + ? EscapeBackslashAndDoubleQuote(jsonPath) : jsonPath; } @@ -703,7 +706,19 @@ private static bool RequiresJsonPathBracketNotation(string propertySerializedNam { return propertySerializedName.Length == 0 || !IsJsonPathIdentifierStart(propertySerializedName[0]) || - propertySerializedName.Skip(1).Any(c => !IsJsonPathIdentifierPart(c)); + HasNonJsonPathIdentifierPart(propertySerializedName); + } + + private static bool HasNonJsonPathIdentifierPart(string propertySerializedName) + { + for (int i = 1; i < propertySerializedName.Length; i++) + { + if (!IsJsonPathIdentifierPart(propertySerializedName[i])) + { + return true; + } + } + return false; } private static bool IsJsonPathIdentifierStart(char c) @@ -716,16 +731,6 @@ private static bool IsJsonPathIdentifierPart(char c) return IsJsonPathIdentifierStart(c) || char.IsDigit(c); } - private static string EscapeJsonPathSegment(string value) - { - return EscapeBackslashAndDoubleQuote(value); - } - - private static string EscapeForCSharpString(string value) - { - return EscapeBackslashAndDoubleQuote(value); - } - private static string EscapeBackslashAndDoubleQuote(string value) { return value.Replace("\\", "\\\\").Replace("\"", "\\\""); From f4c39eff4addf4c4dd8c062c6231a3e817598f82 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:50:47 +0000 Subject: [PATCH 12/30] test(http-client-csharp): cover simple patch path identifiers Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 13 +++++++++---- .../DynamicModelSerializationTests.cs | 14 +++++++++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 70c6c7f9649..119ce5c2862 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -5,6 +5,7 @@ using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Text.Json; using Microsoft.TypeSpec.Generator.ClientModel.Snippets; using Microsoft.TypeSpec.Generator.Expressions; @@ -676,18 +677,22 @@ private List GetQualifyingDynamicListProperties() /// /// Builds a JSONPath. Set when the path is written into a template. /// + /// The JSON property name to use as the root path segment. + /// Collection indices to append to the property path. + /// Whether to escape the result for raw insertion into a C# interpolated string literal. + /// A JSONPath using dot notation for simple identifiers and bracket notation for property names that require escaping. private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpInterpolatedString = false) { var count = indices.Count; - var result = BuildJsonPathForProperty(propertySerializedName, escapeForCSharpInterpolatedString); + var result = new StringBuilder(BuildJsonPathForProperty(propertySerializedName, escapeForCSharpInterpolatedString)); for (int i = 0; i < count; i++) { - result += indices[i] is MemberExpression + result.Append(indices[i] is MemberExpression ? $"[\\\"{{{i}}}\\\"]" - : $"[{{{i}}}]"; + : $"[{{{i}}}]"); } - return result; + return result.ToString(); } private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpInterpolatedString) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index 832b980bd53..6bf94a8a7ec 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -482,7 +482,17 @@ public void EscapedSerializedNameCollectionPatchGuards() [ InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) ])), - serializedName: "1 foo-[\"\\baz") + serializedName: "1 foo-[\"\\baz"), + InputFactory.Property( + "siblings", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + serializedName: "plainName") ]); MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); @@ -497,6 +507,8 @@ public void EscapedSerializedNameCollectionPatchGuards() StringAssert.Contains("""Patch.Contains("$[\"1 foo-[\\\"\\\\baz\"]"u8)""", content); StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo-[\\\"\\\\baz\"][{i}]")""", content); + StringAssert.Contains("""Patch.Contains("$.plainName"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$.plainName[{i}]")""", content); } [Test] From 0dfa3869d0586bfb2f9a93abb49f61dad2adae62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:53:00 +0000 Subject: [PATCH 13/30] fix(http-client-csharp): escape braces in patch path templates Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Providers/MrwSerializationTypeDefinition.Dynamic.cs | 7 ++++++- .../DynamicModelSerializationTests.cs | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 119ce5c2862..b54caf09aa3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -703,7 +703,7 @@ private static string BuildJsonPathForProperty(string propertySerializedName, bo // FormattableStringExpression writes raw interpolated string text, unlike LiteralU8 which escapes string contents. return escapeForCSharpInterpolatedString - ? EscapeBackslashAndDoubleQuote(jsonPath) + ? EscapeForCSharpInterpolatedString(jsonPath) : jsonPath; } @@ -741,6 +741,11 @@ private static string EscapeBackslashAndDoubleQuote(string value) return value.Replace("\\", "\\\\").Replace("\"", "\\\""); } + private static string EscapeForCSharpInterpolatedString(string value) + { + return EscapeBackslashAndDoubleQuote(value).Replace("{", "{{{{").Replace("}", "}}}}"); + } + private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index 6bf94a8a7ec..715a727f57d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -482,7 +482,7 @@ public void EscapedSerializedNameCollectionPatchGuards() [ InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) ])), - serializedName: "1 foo-[\"\\baz"), + serializedName: "1 foo{bar}-[\"\\baz"), InputFactory.Property( "siblings", InputFactory.Array(InputFactory.Model( @@ -505,8 +505,8 @@ public void EscapedSerializedNameCollectionPatchGuards() name => name is "JsonModelWriteCore" or "ActiveChildren")); var content = writer.Write().Content; - StringAssert.Contains("""Patch.Contains("$[\"1 foo-[\\\"\\\\baz\"]"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo-[\\\"\\\\baz\"][{i}]")""", content); + StringAssert.Contains("""Patch.Contains("$[\"1 foo{bar}-[\\\"\\\\baz\"]"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo{{bar}}-[\\\"\\\\baz\"][{i}]")""", content); StringAssert.Contains("""Patch.Contains("$.plainName"u8)""", content); StringAssert.Contains("""Encoding.UTF8.GetBytes($"$.plainName[{i}]")""", content); } From e3db7710e43624566edc69576e547b6fc0e34e14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:54:24 +0000 Subject: [PATCH 14/30] revert(http-client-csharp): remove patch path escaping follow-up Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 77 ++++--------------- .../DynamicModelSerializationTests.cs | 46 ----------- 2 files changed, 14 insertions(+), 109 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index b54caf09aa3..ce8e4b8b5e4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -5,7 +5,6 @@ using System.ClientModel.Primitives; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Text.Json; using Microsoft.TypeSpec.Generator.ClientModel.Snippets; using Microsoft.TypeSpec.Generator.Expressions; @@ -31,7 +30,7 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( parentIndices ??= []; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpInterpolatedString: true); + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); ValueExpression jsonPath = parentIndices.Count > 0 ? Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, [.. parentIndices]).As()) : LiteralU8(jsonPathTemplate); @@ -112,8 +111,8 @@ private MethodBodyStatement CreateListSerializationWithPatch( var indexDeclaration = Declare("i", out var indexVar); var allIndices = new List(parentIndices) { indexVar }; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpInterpolatedString: true); - // The prefix overload accepts a raw property segment and includes indexed descendants, unlike an exact-path Contains check. + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); + // The prefix overload includes indexed descendants, unlike an exact-path Contains check. var hasPatchDeclaration = Declare( "hasPatch", typeof(bool), @@ -616,14 +615,13 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) isActive = item.Equal(Null).Or(isActive); } var serializedName = GetJsonSerializedName(property.WireInfo!); - // The prefix overload accepts a raw property segment and includes indexed descendants, unlike an exact-path Contains check. var hasPatchDeclaration = Declare( "hasPatch", typeof(bool), _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName)), out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( - BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpInterpolatedString: true), + BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpString: true), [indexVar]).As()); isActive = Not(hasPatch).Or(Not(_jsonPatchProperty!.As().IsRemoved(itemPath))).And(isActive); var forStatement = new ForStatement( @@ -674,78 +672,31 @@ private List GetQualifyingDynamicListProperties() #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - /// - /// Builds a JSONPath. Set when the path is written into a template. - /// - /// The JSON property name to use as the root path segment. - /// Collection indices to append to the property path. - /// Whether to escape the result for raw insertion into a C# interpolated string literal. - /// A JSONPath using dot notation for simple identifiers and bracket notation for property names that require escaping. - private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpInterpolatedString = false) + private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpString = false) { var count = indices.Count; - var result = new StringBuilder(BuildJsonPathForProperty(propertySerializedName, escapeForCSharpInterpolatedString)); + var result = BuildJsonPathForProperty(propertySerializedName, escapeForCSharpString); for (int i = 0; i < count; i++) { - result.Append(indices[i] is MemberExpression + result += indices[i] is MemberExpression ? $"[\\\"{{{i}}}\\\"]" - : $"[{{{i}}}]"); + : $"[{{{i}}}]"; } - return result.ToString(); + return result; } - private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpInterpolatedString) + private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpString) { - var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) - ? $"$[\"{EscapeBackslashAndDoubleQuote(propertySerializedName)}\"]" + var jsonPath = propertySerializedName.Contains('.') + ? $"$[\"{propertySerializedName}\"]" : $"$.{propertySerializedName}"; - // FormattableStringExpression writes raw interpolated string text, unlike LiteralU8 which escapes string contents. - return escapeForCSharpInterpolatedString - ? EscapeForCSharpInterpolatedString(jsonPath) + return escapeForCSharpString + ? jsonPath.Replace("\"", "\\\"") : jsonPath; } - private static bool RequiresJsonPathBracketNotation(string propertySerializedName) - { - return propertySerializedName.Length == 0 || - !IsJsonPathIdentifierStart(propertySerializedName[0]) || - HasNonJsonPathIdentifierPart(propertySerializedName); - } - - private static bool HasNonJsonPathIdentifierPart(string propertySerializedName) - { - for (int i = 1; i < propertySerializedName.Length; i++) - { - if (!IsJsonPathIdentifierPart(propertySerializedName[i])) - { - return true; - } - } - return false; - } - - private static bool IsJsonPathIdentifierStart(char c) - { - return c is '_' || char.IsLetter(c); - } - - private static bool IsJsonPathIdentifierPart(char c) - { - return IsJsonPathIdentifierStart(c) || char.IsDigit(c); - } - - private static string EscapeBackslashAndDoubleQuote(string value) - { - return value.Replace("\\", "\\\\").Replace("\"", "\\\""); - } - - private static string EscapeForCSharpInterpolatedString(string value) - { - return EscapeBackslashAndDoubleQuote(value).Replace("{", "{{{{").Replace("}", "}}}}"); - } - private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index 715a727f57d..ddc0a4993c7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -465,52 +465,6 @@ public void DottedSerializedNameCollectionPatchGuards() Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); } - [Test] - public void EscapedSerializedNameCollectionPatchGuards() - { - var inputModel = InputFactory.Model( - "dynamicModel", - isDynamicModel: true, - properties: - [ - InputFactory.Property( - "children", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - serializedName: "1 foo{bar}-[\"\\baz"), - InputFactory.Property( - "siblings", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - serializedName: "plainName") - ]); - - MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); - var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; - - Assert.IsNotNull(model); - var serialization = model!.SerializationProviders.Single(); - var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serialization, - name => name is "JsonModelWriteCore" or "ActiveChildren")); - var content = writer.Write().Content; - - StringAssert.Contains("""Patch.Contains("$[\"1 foo{bar}-[\\\"\\\\baz\"]"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo{{bar}}-[\\\"\\\\baz\"][{i}]")""", content); - StringAssert.Contains("""Patch.Contains("$.plainName"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$.plainName[{i}]")""", content); - } - [Test] public void PropagateModelDictionaryProperty() { From 54bf7fc30a71833043fbf32484be43508cc87c4c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 11 Sep 2026 23:12:08 +0000 Subject: [PATCH 15/30] fix(http-client-csharp): reuse nested collection patch guard Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 60 +++++++++++++------ .../WriteNestedArrayDictionaryProperties.cs | 6 +- .../WriteNestedArrayDynamicModelProperties.cs | 6 +- .../WriteNestedArrayPrimitiveProperties.cs | 6 +- .../Models/DynamicModel.Serialization.cs | 3 +- .../NullableDynamicModel.Serialization.cs | 3 +- 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index ce8e4b8b5e4..3236116d11b 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -25,7 +25,8 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( SerializationFormat serializationFormat, ScopedApi patchSnippet, string serializedName, - List? parentIndices = null) + List? parentIndices = null, + ValueExpression? parentHasPatch = null) { parentIndices ??= []; @@ -74,7 +75,8 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( patchSnippet, serializationFormat, serializedName, - childIndices) + childIndices, + parentHasPatch) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( @@ -105,7 +107,8 @@ private MethodBodyStatement CreateListSerializationWithPatch( ScopedApi patchSnippet, SerializationFormat serializationFormat, string serializedName, - List? parentIndices = null) + List? parentIndices = null, + ValueExpression? parentHasPatch = null) { parentIndices ??= []; var indexDeclaration = Declare("i", out var indexVar); @@ -113,11 +116,23 @@ private MethodBodyStatement CreateListSerializationWithPatch( var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); // The prefix overload includes indexed descendants, unlike an exact-path Contains check. - var hasPatchDeclaration = Declare( - "hasPatch", - typeof(bool), - patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName)), - out var hasPatch); + // Nested collections under the same serialized property can reuse the parent guard. + MethodBodyStatement? hasPatchDeclaration = null; + ValueExpression hasPatch; + if (parentHasPatch == null) + { + hasPatchDeclaration = Declare( + "hasPatch", + typeof(bool), + patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName)), + out var localHasPatch); + hasPatch = localHasPatch; + } + else + { + hasPatch = parentHasPatch; + } + var patchIsRemovedCondition = hasPatch.As().And(patchSnippet.IsRemoved( Utf8Snippets.GetBytes( new FormattableStringExpression(csharpJsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) @@ -155,7 +170,8 @@ private MethodBodyStatement CreateListSerializationWithPatch( patchSnippet, serializationFormat, serializedName, - allIndices) + allIndices, + hasPatch) } }; @@ -163,14 +179,19 @@ private MethodBodyStatement CreateListSerializationWithPatch( ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, parentIndices).As())).Terminate(); - return new[] + var listStatements = new List { - _utf8JsonWriterSnippet.WriteStartArray(), - hasPatchDeclaration, - forStatement, - writeToPatchStatement, - _utf8JsonWriterSnippet.WriteEndArray() + _utf8JsonWriterSnippet.WriteStartArray() }; + if (hasPatchDeclaration != null) + { + listStatements.Add(hasPatchDeclaration); + } + + listStatements.Add(forStatement); + listStatements.Add(writeToPatchStatement); + listStatements.Add(_utf8JsonWriterSnippet.WriteEndArray()); + return listStatements.ToArray(); } private MethodBodyStatement CreateElementSerializationWithPatch( @@ -179,7 +200,8 @@ private MethodBodyStatement CreateElementSerializationWithPatch( ScopedApi patchSnippet, SerializationFormat serializationFormat, string serializedName, - List currentIndices) + List currentIndices, + ValueExpression? parentHasPatch = null) { var nestedSerialization = elementType switch { @@ -190,13 +212,15 @@ private MethodBodyStatement CreateElementSerializationWithPatch( patchSnippet, serializationFormat, serializedName, - currentIndices), + currentIndices, + parentHasPatch), { IsDictionary: true } => CreateDictionarySerializationWithPatch( new DictionaryExpression(elementType, element), serializationFormat, patchSnippet, serializedName, - currentIndices), + currentIndices, + parentHasPatch), _ => null }; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index f3c36c61481..b0968810078 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -62,10 +62,9 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if ((hasPatch0 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) { continue; } @@ -75,10 +74,9 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); - bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if ((hasPatch1 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs index 5b20e01d13d..c6b2daa157d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs @@ -62,10 +62,9 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if ((hasPatch0 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) { continue; } @@ -75,10 +74,9 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); - bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if (((hasPatch1 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"))) || ((PropertyWithNestedArray[i][i0][i1] != null) && PropertyWithNestedArray[i][i0][i1].Patch.IsRemoved("$"u8)))) + if (((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"))) || ((PropertyWithNestedArray[i][i0][i1] != null) && PropertyWithNestedArray[i][i0][i1].Patch.IsRemoved("$"u8)))) { continue; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs index bbbb0586dfa..f90c561ba88 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs @@ -62,10 +62,9 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if ((hasPatch0 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")))) { continue; } @@ -75,10 +74,9 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartArray(); - bool hasPatch1 = Patch.Contains("$"u8, "propertyWithNestedArray"u8); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if ((hasPatch1 && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")))) + if ((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")))) { continue; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs index 27e072713e6..dc05b4efbd0 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs @@ -306,10 +306,9 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "listOfListFoo"u8); for (int i0 = 0; i0 < ListOfListFoo[i].Count; i0++) { - if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}][{i0}]")) || ListOfListFoo[i][i0] != null && ListOfListFoo[i][i0].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}][{i0}]")) || ListOfListFoo[i][i0] != null && ListOfListFoo[i][i0].Patch.IsRemoved("$"u8)) { continue; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs index 43814cb472f..480eb525701 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs @@ -162,10 +162,9 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch0 = Patch.Contains("$"u8, "nestedChildren"u8); for (int i0 = 0; i0 < NestedChildren[i].Count; i0++) { - if (hasPatch0 && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}][{i0}]")) || NestedChildren[i][i0] != null && NestedChildren[i][i0].Patch.IsRemoved("$"u8)) + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}][{i0}]")) || NestedChildren[i][i0] != null && NestedChildren[i][i0].Patch.IsRemoved("$"u8)) { continue; } From 8ccb6acf4b742cc352dcf1e1c8a23eb12837bdc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:20:41 +0000 Subject: [PATCH 16/30] fix(http-client-csharp): guard nested dictionary patch paths Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 38 ++++++++++-- .../MrwSerializationTypeDefinition.cs | 2 +- .../DynamicModelSerializationTests.cs | 26 +++++++++ ...ottedSerializedNameDictionaryPatchGuard.cs | 58 +++++++++++++++++++ .../WriteNestedArrayDictionaryProperties.cs | 44 ++++++++++---- .../WriteNestedArrayDynamicModelProperties.cs | 10 +++- .../WriteNestedArrayPrimitiveProperties.cs | 10 +++- 7 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 3236116d11b..adfcd4afaf6 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -89,13 +89,39 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( foreachStatement.Add(innerIfElseProcessorStatement); foreachStatement.Add(ifPatchDoesNotContainStatement); - return new[] + var patchedStatements = new MethodBodyStatement[] { - _utf8JsonWriterSnippet.WriteStartObject(), new IfElsePreprocessorStatement("NET8_0_OR_GREATER", bufferDeclaration), foreachStatement, MethodBodyStatement.EmptyLine, - patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate(), + patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate() + }; + + MethodBodyStatement dictionarySerialization; + if (parentHasPatch == null) + { + dictionarySerialization = patchedStatements; + } + else + { + var unpatchedForeachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); + unpatchedForeachStatement.Add(_utf8JsonWriterSnippet.WritePropertyName(unpatchedKeyValuePair.Key)); + unpatchedForeachStatement.Add(CreateElementSerializationWithPatch( + unpatchedKeyValuePair.Value, + unpatchedKeyValuePair.ValueType, + patchSnippet, + serializationFormat, + serializedName, + parentIndices, + False)); + + dictionarySerialization = new IfElseStatement(parentHasPatch.As(), patchedStatements, unpatchedForeachStatement); + } + + return new[] + { + _utf8JsonWriterSnippet.WriteStartObject(), + dictionarySerialization, _utf8JsonWriterSnippet.WriteEndObject(), }; } @@ -175,9 +201,13 @@ private MethodBodyStatement CreateListSerializationWithPatch( } }; - var writeToPatchStatement = parentIndices.Count == 0 + MethodBodyStatement writeToPatchStatement = parentIndices.Count == 0 ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, parentIndices).As())).Terminate(); + if (parentHasPatch != null) + { + writeToPatchStatement = new IfStatement(parentHasPatch.As()) { writeToPatchStatement }; + } var listStatements = new List { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index a21f31ffba6..2161daf27ec 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -2001,7 +2001,7 @@ private MethodBodyStatement WrapInIsDefined( { #pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. ScopedApi? patchCheck = _jsonPatchProperty != null - ? Not(_jsonPatchProperty.As().Contains(LiteralU8($"$.{jsonSerializedName}"))) + ? Not(_jsonPatchProperty.As().Contains(LiteralU8(BuildJsonPathForProperty(jsonSerializedName, escapeForCSharpString: false)))) : null; #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index ddc0a4993c7..fd8de96e57a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -465,6 +465,32 @@ public void DottedSerializedNameCollectionPatchGuards() Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); } + [Test] + public void DottedSerializedNameDictionaryPatchGuard() + { + var inputModel = InputFactory.Model( + "dynamicModel", + isDynamicModel: true, + properties: + [ + InputFactory.Property( + "metadata", + InputFactory.Dictionary(InputPrimitiveType.String), + serializedName: "foo.bar") + ]); + + MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; + + Assert.IsNotNull(model); + var serialization = model!.SerializationProviders.Single(); + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serialization, + name => name is "JsonModelWriteCore")); + + Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); + } + [Test] public void PropagateModelDictionaryProperty() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs new file mode 100644 index 00000000000..57cfb2020b3 --- /dev/null +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs @@ -0,0 +1,58 @@ +// + +#nullable disable + +using System; +using System.ClientModel.Primitives; +using System.Text; +using System.Text.Json; +using Sample.Models; + +namespace Sample +{ + public partial class DynamicModel + { + protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) + { + string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; + if ((format != "J")) + { + throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format."); + } +#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + if ((global::Sample.Optional.IsCollectionDefined(Metadata) && !Patch.Contains("$[\"foo.bar\"]"u8))) + { + writer.WritePropertyName("foo.bar"u8); + writer.WriteStartObject(); +#if NET8_0_OR_GREATER + global::System.Span buffer = stackalloc byte[256]; +#endif + foreach (var item in Metadata) + { +#if NET8_0_OR_GREATER + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$[\"foo.bar\"]"u8, buffer.Slice(0, bytesWritten)); +#else + bool patchContains = Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); +#endif + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + } + + Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); + writer.WriteEndObject(); + } + + Patch.WriteTo(writer); +#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. + } + } +} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index b0968810078..d0044bbe1e2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -86,18 +86,36 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in PropertyWithNestedArray[i][i0][i1]) - { + foreach (var item in PropertyWithNestedArray[i][i0][i1]) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")); + } + else + { + foreach (var item in PropertyWithNestedArray[i][i0][i1]) { writer.WritePropertyName(item.Key); if ((item.Value == null)) @@ -108,14 +126,18 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStringValue(item.Value); } } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}][{i1}]")); writer.WriteEndObject(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + if (hasPatch) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + } writer.WriteEndArray(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + if (hasPatch) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs index c6b2daa157d..3c0976f2e80 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDynamicModelProperties.cs @@ -82,10 +82,16 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } writer.WriteObjectValue(PropertyWithNestedArray[i][i0][i1], options); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + if (hasPatch) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + } writer.WriteEndArray(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + if (hasPatch) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs index f90c561ba88..964bfffd55f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayPrimitiveProperties.cs @@ -87,10 +87,16 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } writer.WriteStringValue(PropertyWithNestedArray[i][i0][i1]); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + if (hasPatch) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}][{i0}]")); + } writer.WriteEndArray(); } - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + if (hasPatch) + { + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedArray[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.propertyWithNestedArray"u8); From 121ecea09783615516802bec5ddfab0290655082 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:22:34 +0000 Subject: [PATCH 17/30] refactor(http-client-csharp): share dictionary item serialization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index adfcd4afaf6..d18f5e50f2f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -61,22 +61,30 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( ReadOnlySpanSnippets.Slice(bufferVar, Int(0), bytesWrittenVar))), out var patchContainsNet8Var); - List childIndices = keyValuePair.ValueType.IsCollection - ? [.. parentIndices, keyValuePair.Key] - : parentIndices; + MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression item, ValueExpression? itemParentHasPatch) + { + List itemChildIndices = item.ValueType.IsCollection + ? [.. parentIndices, item.Key] + : parentIndices; + + return new MethodBodyStatement[] + { + _utf8JsonWriterSnippet.WritePropertyName(item.Key), + CreateElementSerializationWithPatch( + item.Value, + item.ValueType, + patchSnippet, + serializationFormat, + serializedName, + itemChildIndices, + itemParentHasPatch) + }; + } // Process key-value pair if patch doesn't contain it var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) { - _utf8JsonWriterSnippet.WritePropertyName(keyValuePair.Key), - CreateElementSerializationWithPatch( - keyValuePair.Value, - keyValuePair.ValueType, - patchSnippet, - serializationFormat, - serializedName, - childIndices, - parentHasPatch) + CreateDictionaryItemSerialization(keyValuePair, parentHasPatch) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( @@ -105,15 +113,7 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( else { var unpatchedForeachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); - unpatchedForeachStatement.Add(_utf8JsonWriterSnippet.WritePropertyName(unpatchedKeyValuePair.Key)); - unpatchedForeachStatement.Add(CreateElementSerializationWithPatch( - unpatchedKeyValuePair.Value, - unpatchedKeyValuePair.ValueType, - patchSnippet, - serializationFormat, - serializedName, - parentIndices, - False)); + unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, False)); dictionarySerialization = new IfElseStatement(parentHasPatch.As(), patchedStatements, unpatchedForeachStatement); } From a1b932d1cdb9c3a1f8f7e6ce963447ad37e52b47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:24:23 +0000 Subject: [PATCH 18/30] fix(http-client-csharp): avoid unpatched item name reuse Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Providers/MrwSerializationTypeDefinition.Dynamic.cs | 3 ++- .../WriteNestedArrayDictionaryProperties.cs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index d18f5e50f2f..4fc00f4941f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -112,7 +112,8 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite } else { - var unpatchedForeachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); + var unpatchedForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); + // This branch only runs when the inherited patch guard is false, so nested serializers can skip path-dependent patch work. unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, False)); dictionarySerialization = new IfElseStatement(parentHasPatch.As(), patchedStatements, unpatchedForeachStatement); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index d0044bbe1e2..6f2df515ebf 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -115,15 +115,15 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var item in PropertyWithNestedArray[i][i0][i1]) + foreach (var unpatchedItem in PropertyWithNestedArray[i][i0][i1]) { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(item.Value); + writer.WriteStringValue(unpatchedItem.Value); } } writer.WriteEndObject(); From 412735570d0a59812a8ed10430d1db8f3e50ff70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:46:42 +0000 Subject: [PATCH 19/30] fix(http-client-csharp): guard root dictionary patch paths Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 46 +-- ...ottedSerializedNameDictionaryPatchGuard.cs | 41 ++- .../WriteDictionaryProperties.cs | 116 +++++--- .../WriteNestedDictDynamicModelProperties.cs | 244 +++++++++++++-- .../WriteNestedDictPrimitiveProperties.cs | 281 +++++++++++++++--- 5 files changed, 597 insertions(+), 131 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 4fc00f4941f..ca7b7e11741 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -32,6 +32,22 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); + MethodBodyStatement? hasPatchDeclaration = null; + ValueExpression hasPatch; + if (parentHasPatch == null) + { + hasPatchDeclaration = Declare( + "hasPatch", + typeof(bool), + patchSnippet.Contains(LiteralU8("$"), LiteralU8(serializedName)), + out var localHasPatch); + hasPatch = localHasPatch; + } + else + { + hasPatch = parentHasPatch; + } + ValueExpression jsonPath = parentIndices.Count > 0 ? Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, [.. parentIndices]).As()) : LiteralU8(jsonPathTemplate); @@ -84,7 +100,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite // Process key-value pair if patch doesn't contain it var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) { - CreateDictionaryItemSerialization(keyValuePair, parentHasPatch) + CreateDictionaryItemSerialization(keyValuePair, hasPatch) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( @@ -105,26 +121,22 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate() }; - MethodBodyStatement dictionarySerialization; - if (parentHasPatch == null) - { - dictionarySerialization = patchedStatements; - } - else - { - var unpatchedForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); - // This branch only runs when the inherited patch guard is false, so nested serializers can skip path-dependent patch work. - unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, False)); + var unpatchedForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); + // This branch only runs when the collection has no relevant patch, so serializers can skip path-dependent patch work. + unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, False)); - dictionarySerialization = new IfElseStatement(parentHasPatch.As(), patchedStatements, unpatchedForeachStatement); - } - - return new[] + var dictionaryStatements = new List { _utf8JsonWriterSnippet.WriteStartObject(), - dictionarySerialization, - _utf8JsonWriterSnippet.WriteEndObject(), }; + if (hasPatchDeclaration != null) + { + dictionaryStatements.Add(hasPatchDeclaration); + } + + dictionaryStatements.Add(new IfElseStatement(hasPatch.As(), patchedStatements, unpatchedForeachStatement)); + dictionaryStatements.Add(_utf8JsonWriterSnippet.WriteEndObject()); + return dictionaryStatements.ToArray(); } private MethodBodyStatement CreateListSerializationWithPatch( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs index 57cfb2020b3..aedcdf9ed6f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs @@ -24,30 +24,47 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("foo.bar"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in Metadata) - { + foreach (var item in Metadata) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$[\"foo.bar\"]"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$[\"foo.bar\"]"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + } + + Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); + } + else + { + foreach (var unpatchedItem in Metadata) { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(item.Value); + writer.WriteStringValue(unpatchedItem.Value); } } - - Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs index 3e786fd1bea..16855fda334 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs @@ -41,85 +41,131 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("cats"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "cats"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in Cats) - { + foreach (var item in Cats) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.cats"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.cats"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.cats"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); + } + } + + Patch.WriteTo(writer, "$.cats"u8); + } + else + { + foreach (var unpatchedItem in Cats) { - writer.WritePropertyName(item.Key); - writer.WriteObjectValue(item.Value, options); + writer.WritePropertyName(unpatchedItem.Key); + writer.WriteObjectValue(unpatchedItem.Value, options); } } - - Patch.WriteTo(writer, "$.cats"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.names"u8)) { writer.WritePropertyName("names"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "names"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in Names) - { + foreach (var item in Names) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.names"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.names"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.names"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + } + + Patch.WriteTo(writer, "$.names"u8); + } + else + { + foreach (var unpatchedItem in Names) { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(item.Value); + writer.WriteStringValue(unpatchedItem.Value); } } - - Patch.WriteTo(writer, "$.names"u8); writer.WriteEndObject(); } if ((global::Sample.Optional.IsCollectionDefined(OptionalNames) && !Patch.Contains("$.optionalNames"u8))) { writer.WritePropertyName("optionalNames"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "optionalNames"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in OptionalNames) - { + foreach (var item in OptionalNames) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNames"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNames"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.optionalNames"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + } + + Patch.WriteTo(writer, "$.optionalNames"u8); + } + else + { + foreach (var unpatchedItem in OptionalNames) { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(item.Value); + writer.WriteStringValue(unpatchedItem.Value); } } - - Patch.WriteTo(writer, "$.optionalNames"u8); writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index e2997e91593..169738266b3 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -41,75 +41,253 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in PropertyWithNestedDictionary) - { + foreach (var item in PropertyWithNestedDictionary) + { +#if NET8_0_OR_GREATER + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); +#else + bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); +#endif + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (hasPatch) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in item.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); +#else + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); +#endif + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (hasPatch) + { +#if NET8_0_OR_GREATER + global::System.Span buffer1 = stackalloc byte[256]; +#endif + foreach (var item1 in item0.Value) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); + bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); #else - bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); #endif - if (!patchContains) + if (!patchContains1) + { + writer.WritePropertyName(item1.Key); + writer.WriteObjectValue(item1.Value, options); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); + } + else + { + foreach (var unpatchedItem in item0.Value) + { + writer.WritePropertyName(unpatchedItem.Key); + writer.WriteObjectValue(unpatchedItem.Value, options); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); + } + else + { + foreach (var unpatchedItem in item.Value) + { + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (false) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in unpatchedItem.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), buffer0.Slice(0, bytesWritten0)); +#else + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); +#endif + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]")); + } + else + { + foreach (var unpatchedItem0 in unpatchedItem.Value) + { + writer.WritePropertyName(unpatchedItem0.Key); + writer.WriteObjectValue(unpatchedItem0.Value, options); + } + } + writer.WriteEndObject(); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); + } + else + { + foreach (var unpatchedItem in PropertyWithNestedDictionary) { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); + if (false) + { #if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item0 in item.Value) - { + foreach (var item in unpatchedItem.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), buffer.Slice(0, bytesWritten)); +#else + bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); +#endif + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (false) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in item.Value) + { #if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); #endif - if (!patchContains0) + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]")); + } + else + { + foreach (var unpatchedItem0 in item.Value) + { + writer.WritePropertyName(unpatchedItem0.Key); + writer.WriteObjectValue(unpatchedItem0.Value, options); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]")); + } + else + { + foreach (var unpatchedItem0 in unpatchedItem.Value) { - writer.WritePropertyName(item0.Key); - if ((item0.Value == null)) + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); + if (false) + { #if NET8_0_OR_GREATER - global::System.Span buffer1 = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item1 in item0.Value) - { + foreach (var item in unpatchedItem0.Value) + { #if NET8_0_OR_GREATER - int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); - bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); + bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains1) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]")); + } + else + { + foreach (var unpatchedItem1 in unpatchedItem0.Value) { - writer.WritePropertyName(item1.Key); - writer.WriteObjectValue(item1.Value, options); + writer.WritePropertyName(unpatchedItem1.Key); + writer.WriteObjectValue(unpatchedItem1.Value, options); } } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); writer.WriteEndObject(); } } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); writer.WriteEndObject(); } } - - Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index 0dc4862a482..09557723068 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -41,80 +41,293 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite { writer.WritePropertyName("propertyWithNestedDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "propertyWithNestedDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in PropertyWithNestedDictionary) - { + foreach (var item in PropertyWithNestedDictionary) + { +#if NET8_0_OR_GREATER + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); +#else + bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); +#endif + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (hasPatch) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in item.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); +#else + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); +#endif + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (hasPatch) + { +#if NET8_0_OR_GREATER + global::System.Span buffer1 = stackalloc byte[256]; +#endif + foreach (var item1 in item0.Value) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.propertyWithNestedDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); + bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); #else - bool patchContains = Patch.Contains("$.propertyWithNestedDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); + bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); #endif - if (!patchContains) + if (!patchContains1) + { + writer.WritePropertyName(item1.Key); + if ((item1.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item1.Value); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); + } + else + { + foreach (var unpatchedItem in item0.Value) + { + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(unpatchedItem.Value); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); + } + else + { + foreach (var unpatchedItem in item.Value) + { + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (false) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in unpatchedItem.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), buffer0.Slice(0, bytesWritten0)); +#else + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); +#endif + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item0.Value); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]")); + } + else + { + foreach (var unpatchedItem0 in unpatchedItem.Value) + { + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(unpatchedItem0.Value); + } + } + writer.WriteEndObject(); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); + } + else + { + foreach (var unpatchedItem in PropertyWithNestedDictionary) { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) + writer.WritePropertyName(unpatchedItem.Key); + if ((unpatchedItem.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); + if (false) + { #if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item0 in item.Value) - { + foreach (var item in unpatchedItem.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), buffer.Slice(0, bytesWritten)); +#else + bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); +#endif + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (false) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in item.Value) + { #if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); + bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); #endif - if (!patchContains0) + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item0.Value); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]")); + } + else + { + foreach (var unpatchedItem0 in item.Value) + { + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(unpatchedItem0.Value); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]")); + } + else + { + foreach (var unpatchedItem0 in unpatchedItem.Value) { - writer.WritePropertyName(item0.Key); - if ((item0.Value == null)) + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); + if (false) + { #if NET8_0_OR_GREATER - global::System.Span buffer1 = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item1 in item0.Value) - { + foreach (var item in unpatchedItem0.Value) + { #if NET8_0_OR_GREATER - int bytesWritten1 = global::System.Text.Encoding.UTF8.GetBytes(item1.Key.AsSpan(), buffer1); - bool patchContains1 = (bytesWritten1 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), buffer1.Slice(0, bytesWritten1)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains1 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item1.Key)); + bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains1) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if ((item.Value == null)) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStringValue(item.Value); + } + } + + Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]")); + } + else + { + foreach (var unpatchedItem1 in unpatchedItem0.Value) { - writer.WritePropertyName(item1.Key); - if ((item1.Value == null)) + writer.WritePropertyName(unpatchedItem1.Key); + if ((unpatchedItem1.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(item1.Value); + writer.WriteStringValue(unpatchedItem1.Value); } } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{item0.Key}\"]")); writer.WriteEndObject(); } } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"]")); writer.WriteEndObject(); } } - - Patch.WriteTo(writer, "$.propertyWithNestedDictionary"u8); writer.WriteEndObject(); } From af2b65e2e14834d5c99678d11849109825d06330 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 15:59:32 +0000 Subject: [PATCH 20/30] fix(http-client-csharp): preserve special patch path names Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 27 +++++++++-- .../DynamicModelSerializationTests.cs | 46 +++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index ca7b7e11741..f6200c765a4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -217,9 +217,9 @@ private MethodBodyStatement CreateListSerializationWithPatch( MethodBodyStatement writeToPatchStatement = parentIndices.Count == 0 ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, parentIndices).As())).Terminate(); - if (parentHasPatch != null) + if (parentIndices.Count > 0) { - writeToPatchStatement = new IfStatement(parentHasPatch.As()) { writeToPatchStatement }; + writeToPatchStatement = new IfStatement(hasPatch.As()) { writeToPatchStatement }; } var listStatements = new List @@ -755,15 +755,32 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpString) { - var jsonPath = propertySerializedName.Contains('.') - ? $"$[\"{propertySerializedName}\"]" + var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) + ? BuildJsonPathQuotedProperty(propertySerializedName) : $"$.{propertySerializedName}"; return escapeForCSharpString - ? jsonPath.Replace("\"", "\\\"") + ? EscapeForCSharpInterpolatedString(jsonPath) : jsonPath; } + private static string BuildJsonPathQuotedProperty(string propertySerializedName) + { + char quote = propertySerializedName.Contains('\"') ? '\'' : '\"'; + return $"$[{quote}{propertySerializedName}{quote}]"; + } + + private static bool RequiresJsonPathBracketNotation(string propertySerializedName) + { + return propertySerializedName.IndexOfAny(['.', '[', ']', '"', '\'', '\\']) >= 0 || + propertySerializedName.Any(char.IsWhiteSpace); + } + + private static string EscapeForCSharpInterpolatedString(string value) + { + return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("{", "{{{{").Replace("}", "}}}}"); + } + private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index fd8de96e57a..1be6d66546d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -465,6 +465,52 @@ public void DottedSerializedNameCollectionPatchGuards() Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); } + [Test] + public void EscapedSerializedNameCollectionPatchGuards() + { + var inputModel = InputFactory.Model( + "dynamicModel", + isDynamicModel: true, + properties: + [ + InputFactory.Property( + "children", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + serializedName: "1 foo{bar}-[\"\\baz"), + InputFactory.Property( + "siblings", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + serializedName: "plainName") + ]); + + MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; + + Assert.IsNotNull(model); + var serialization = model!.SerializationProviders.Single(); + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serialization, + name => name is "JsonModelWriteCore" or "ActiveChildren")); + var content = writer.Write().Content; + + StringAssert.Contains("""Patch.Contains("$['1 foo{bar}-[\"\\baz']"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$['1 foo{{bar}}-[\"\\baz'][{i}]")""", content); + StringAssert.Contains("""Patch.Contains("$.plainName"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$.plainName[{i}]")""", content); + } + [Test] public void DottedSerializedNameDictionaryPatchGuard() { From 2660a004be944aaef3f42fbfb5c7d207e8b6d066 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:23:03 +0000 Subject: [PATCH 21/30] fix(http-client-csharp): pick collision-safe JSON path quote delimiter Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 7 ++- .../DynamicModelSerializationTests.cs | 52 ++++++++++++++++++- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index f6200c765a4..f588463dec7 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -766,7 +766,12 @@ private static string BuildJsonPathForProperty(string propertySerializedName, bo private static string BuildJsonPathQuotedProperty(string propertySerializedName) { - char quote = propertySerializedName.Contains('\"') ? '\'' : '\"'; + // The JsonPath reader has no escape syntax: a quoted segment ends at the first occurrence of the + // chosen delimiter immediately followed by ']'. Pick whichever delimiter doesn't form that sequence + // in the property name so a name containing both quote characters (e.g. `a"b'c`) is not truncated. + bool doubleQuoteCollides = propertySerializedName.Contains("\"]", StringComparison.Ordinal); + bool singleQuoteCollides = propertySerializedName.Contains("']", StringComparison.Ordinal); + char quote = doubleQuoteCollides && !singleQuoteCollides ? '\'' : '\"'; return $"$[{quote}{propertySerializedName}{quote}]"; } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index 1be6d66546d..9e98a2b2156 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -505,12 +505,60 @@ public void EscapedSerializedNameCollectionPatchGuards() name => name is "JsonModelWriteCore" or "ActiveChildren")); var content = writer.Write().Content; - StringAssert.Contains("""Patch.Contains("$['1 foo{bar}-[\"\\baz']"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$['1 foo{{bar}}-[\"\\baz'][{i}]")""", content); + StringAssert.Contains("""Patch.Contains("$[\"1 foo{bar}-[\"\\baz\"]"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo{{bar}}-[\"\\baz\"][{i}]")""", content); StringAssert.Contains("""Patch.Contains("$.plainName"u8)""", content); StringAssert.Contains("""Encoding.UTF8.GetBytes($"$.plainName[{i}]")""", content); } + [Test] + public void BothQuoteCharactersSerializedNameCollectionPatchGuards() + { + var inputModel = InputFactory.Model( + "dynamicModel", + isDynamicModel: true, + properties: + [ + InputFactory.Property( + "children", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + serializedName: "a\"b'c"), + InputFactory.Property( + "siblings", + InputFactory.Array(InputFactory.Model( + "anotherDynamic", + isDynamicModel: true, + properties: + [ + InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) + ])), + // A single-quote delimiter would terminate the JsonPath segment early here because the + // name contains "']", so the double-quote delimiter must be chosen instead. + serializedName: "a\"b']c") + ]); + + MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); + var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; + + Assert.IsNotNull(model); + var serialization = model!.SerializationProviders.Single(); + var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( + serialization, + name => name is "JsonModelWriteCore" or "ActiveChildren")); + var content = writer.Write().Content; + + StringAssert.Contains("""Patch.Contains("$[\"a\"b'c\"]"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"a\"b'c\"][{i}]")""", content); + StringAssert.Contains("""Patch.Contains("$[\"a\"b']c\"]"u8)""", content); + StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"a\"b']c\"][{i}]")""", content); + } + [Test] public void DottedSerializedNameDictionaryPatchGuard() { From ee1effaabf7a37516c81a94a0864647cbc377507 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:33:54 +0000 Subject: [PATCH 22/30] fix(http-client-csharp): eliminate dead if(false) patch branches in nested collections Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 144 ++++++++++----- .../WriteNestedDictDynamicModelProperties.cs | 137 ++------------- .../WriteNestedDictPrimitiveProperties.cs | 165 ++---------------- 3 files changed, 132 insertions(+), 314 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index f588463dec7..1c1d4be9285 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -26,10 +26,47 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( ScopedApi patchSnippet, string serializedName, List? parentIndices = null, - ValueExpression? parentHasPatch = null) + ValueExpression? parentHasPatch = null, + bool suppressPatchLogic = false) { parentIndices ??= []; + MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression item, ValueExpression? itemParentHasPatch, bool itemSuppressPatchLogic) + { + List itemChildIndices = item.ValueType.IsCollection + ? [.. parentIndices, item.Key] + : parentIndices; + + return new MethodBodyStatement[] + { + _utf8JsonWriterSnippet.WritePropertyName(item.Key), + CreateElementSerializationWithPatch( + item.Value, + item.ValueType, + patchSnippet, + serializationFormat, + serializedName, + itemChildIndices, + itemParentHasPatch, + itemSuppressPatchLogic) + }; + } + + // The collection is known to have no relevant patch at or below this level, so skip building + // any path-dependent patch checks entirely instead of materializing a dead patched branch. + if (suppressPatchLogic) + { + var noPatchForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression noPatchKeyValuePair); + noPatchForeachStatement.Add(CreateDictionaryItemSerialization(noPatchKeyValuePair, null, itemSuppressPatchLogic: true)); + + return new MethodBodyStatement[] + { + _utf8JsonWriterSnippet.WriteStartObject(), + noPatchForeachStatement, + _utf8JsonWriterSnippet.WriteEndObject() + }; + } + var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); MethodBodyStatement? hasPatchDeclaration = null; @@ -77,30 +114,10 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( ReadOnlySpanSnippets.Slice(bufferVar, Int(0), bytesWrittenVar))), out var patchContainsNet8Var); - MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression item, ValueExpression? itemParentHasPatch) - { - List itemChildIndices = item.ValueType.IsCollection - ? [.. parentIndices, item.Key] - : parentIndices; - - return new MethodBodyStatement[] - { - _utf8JsonWriterSnippet.WritePropertyName(item.Key), - CreateElementSerializationWithPatch( - item.Value, - item.ValueType, - patchSnippet, - serializationFormat, - serializedName, - itemChildIndices, - itemParentHasPatch) - }; - } - // Process key-value pair if patch doesn't contain it var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) { - CreateDictionaryItemSerialization(keyValuePair, hasPatch) + CreateDictionaryItemSerialization(keyValuePair, hasPatch, itemSuppressPatchLogic: false) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( @@ -123,7 +140,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite var unpatchedForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); // This branch only runs when the collection has no relevant patch, so serializers can skip path-dependent patch work. - unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, False)); + unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, null, itemSuppressPatchLogic: true)); var dictionaryStatements = new List { @@ -147,11 +164,64 @@ private MethodBodyStatement CreateListSerializationWithPatch( SerializationFormat serializationFormat, string serializedName, List? parentIndices = null, - ValueExpression? parentHasPatch = null) + ValueExpression? parentHasPatch = null, + bool suppressPatchLogic = false) { parentIndices ??= []; var indexDeclaration = Declare("i", out var indexVar); var allIndices = new List(parentIndices) { indexVar }; + + // Handle model types with their own patch property. This is independent of whether this + // collection has a relevant patch, so it applies in both the patched and no-patch paths below. + ScopedApi? childIsRemovedCondition = null; + if (ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(type, out var provider) && + provider is ScmModelProvider scmModelProvider && scmModelProvider.JsonPatchProperty != null) + { + var childIsRemoved = new IndexerExpression(collection, indexVar) + .Property(scmModelProvider.JsonPatchProperty.Name) + .As() + .IsRemoved(LiteralU8("$")); + if (!type.IsValueType) + { + childIsRemoved = new IndexerExpression(collection, indexVar).NotEqual(Null).And(childIsRemoved); + } + childIsRemovedCondition = childIsRemoved; + } + + string lengthProperty = isReadOnlySpan || type.IsArray + ? "Length" + : "Count"; + + // The collection is known to have no relevant patch at or below this level, so skip building any + // path-dependent patch checks entirely instead of materializing a dead patched branch. + if (suppressPatchLogic) + { + var noPatchForStatement = new ForStatement( + indexDeclaration.Assign(Literal(0)), + indexVar.LessThan(collection.Property(lengthProperty)), + indexVar.Increment()); + if (childIsRemovedCondition != null) + { + noPatchForStatement.Add(new IfStatement(childIsRemovedCondition) { Continue }); + } + noPatchForStatement.Add(CreateElementSerializationWithPatch( + new IndexerExpression(collection, indexVar), + type, + patchSnippet, + serializationFormat, + serializedName, + allIndices, + parentHasPatch, + suppressPatchLogic: true)); + + return new MethodBodyStatement[] + { + _utf8JsonWriterSnippet.WriteStartArray(), + noPatchForStatement, + _utf8JsonWriterSnippet.WriteEndArray() + }; + } + var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); // The prefix overload includes indexed descendants, unlike an exact-path Contains check. @@ -177,24 +247,11 @@ private MethodBodyStatement CreateListSerializationWithPatch( new FormattableStringExpression(csharpJsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) .As()))); - // Handle model types with their own patch property - if (ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(type, out var provider) && - provider is ScmModelProvider scmModelProvider && scmModelProvider.JsonPatchProperty != null) + if (childIsRemovedCondition != null) { - var childIsRemoved = new IndexerExpression(collection, indexVar) - .Property(scmModelProvider.JsonPatchProperty.Name) - .As() - .IsRemoved(LiteralU8("$")); - if (!type.IsValueType) - { - childIsRemoved = new IndexerExpression(collection, indexVar).NotEqual(Null).And(childIsRemoved); - } - patchIsRemovedCondition = patchIsRemovedCondition.Or(childIsRemoved); + patchIsRemovedCondition = patchIsRemovedCondition.Or(childIsRemovedCondition); } - string lengthProperty = isReadOnlySpan || type.IsArray - ? "Length" - : "Count"; var forStatement = new ForStatement( indexDeclaration.Assign(Literal(0)), indexVar.LessThan(collection.Property(lengthProperty)), @@ -244,7 +301,8 @@ private MethodBodyStatement CreateElementSerializationWithPatch( SerializationFormat serializationFormat, string serializedName, List currentIndices, - ValueExpression? parentHasPatch = null) + ValueExpression? parentHasPatch = null, + bool suppressPatchLogic = false) { var nestedSerialization = elementType switch { @@ -256,14 +314,16 @@ private MethodBodyStatement CreateElementSerializationWithPatch( serializationFormat, serializedName, currentIndices, - parentHasPatch), + parentHasPatch, + suppressPatchLogic), { IsDictionary: true } => CreateDictionarySerializationWithPatch( new DictionaryExpression(elementType, element), serializationFormat, patchSnippet, serializedName, currentIndices, - parentHasPatch), + parentHasPatch, + suppressPatchLogic), _ => null }; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index 169738266b3..0b6e4df78f0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -133,35 +133,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (false) + foreach (var unpatchedItem0 in unpatchedItem.Value) { -#if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; -#endif - foreach (var item0 in unpatchedItem.Value) - { -#if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), buffer0.Slice(0, bytesWritten0)); -#else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); -#endif - if (!patchContains0) - { - writer.WritePropertyName(item0.Key); - writer.WriteObjectValue(item0.Value, options); - } - } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]")); - } - else - { - foreach (var unpatchedItem0 in unpatchedItem.Value) - { - writer.WritePropertyName(unpatchedItem0.Key); - writer.WriteObjectValue(unpatchedItem0.Value, options); - } + writer.WritePropertyName(unpatchedItem0.Key); + writer.WriteObjectValue(unpatchedItem0.Value, options); } writer.WriteEndObject(); } @@ -183,107 +158,21 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (false) + foreach (var unpatchedItem0 in unpatchedItem.Value) { -#if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; -#endif - foreach (var item in unpatchedItem.Value) + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) { -#if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), buffer.Slice(0, bytesWritten)); -#else - bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); -#endif - if (!patchContains) - { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStartObject(); - if (false) - { -#if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; -#endif - foreach (var item0 in item.Value) - { -#if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); -#else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); -#endif - if (!patchContains0) - { - writer.WritePropertyName(item0.Key); - writer.WriteObjectValue(item0.Value, options); - } - } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]")); - } - else - { - foreach (var unpatchedItem0 in item.Value) - { - writer.WritePropertyName(unpatchedItem0.Key); - writer.WriteObjectValue(unpatchedItem0.Value, options); - } - } - writer.WriteEndObject(); - } + writer.WriteNullValue(); + continue; } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]")); - } - else - { - foreach (var unpatchedItem0 in unpatchedItem.Value) + writer.WriteStartObject(); + foreach (var unpatchedItem1 in unpatchedItem0.Value) { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStartObject(); - if (false) - { -#if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; -#endif - foreach (var item in unpatchedItem0.Value) - { -#if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), buffer.Slice(0, bytesWritten)); -#else - bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); -#endif - if (!patchContains) - { - writer.WritePropertyName(item.Key); - writer.WriteObjectValue(item.Value, options); - } - } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]")); - } - else - { - foreach (var unpatchedItem1 in unpatchedItem0.Value) - { - writer.WritePropertyName(unpatchedItem1.Key); - writer.WriteObjectValue(unpatchedItem1.Value, options); - } - } - writer.WriteEndObject(); + writer.WritePropertyName(unpatchedItem1.Key); + writer.WriteObjectValue(unpatchedItem1.Value, options); } + writer.WriteEndObject(); } writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index 09557723068..f5b4c88a227 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -143,45 +143,15 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (false) + foreach (var unpatchedItem0 in unpatchedItem.Value) { -#if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; -#endif - foreach (var item0 in unpatchedItem.Value) + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) { -#if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), buffer0.Slice(0, bytesWritten0)); -#else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); -#endif - if (!patchContains0) - { - writer.WritePropertyName(item0.Key); - if ((item0.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(item0.Value); - } - } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{item.Key}\"][\"{unpatchedItem.Key}\"]")); - } - else - { - foreach (var unpatchedItem0 in unpatchedItem.Value) - { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(unpatchedItem0.Value); + writer.WriteNullValue(); + continue; } + writer.WriteStringValue(unpatchedItem0.Value); } writer.WriteEndObject(); } @@ -203,127 +173,26 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (false) + foreach (var unpatchedItem0 in unpatchedItem.Value) { -#if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; -#endif - foreach (var item in unpatchedItem.Value) + writer.WritePropertyName(unpatchedItem0.Key); + if ((unpatchedItem0.Value == null)) { -#if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), buffer.Slice(0, bytesWritten)); -#else - bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); -#endif - if (!patchContains) - { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStartObject(); - if (false) - { -#if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; -#endif - foreach (var item0 in item.Value) - { -#if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); -#else - bool patchContains0 = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)); -#endif - if (!patchContains0) - { - writer.WritePropertyName(item0.Key); - if ((item0.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(item0.Value); - } - } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{item.Key}\"]")); - } - else - { - foreach (var unpatchedItem0 in item.Value) - { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(unpatchedItem0.Value); - } - } - writer.WriteEndObject(); - } + writer.WriteNullValue(); + continue; } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"]")); - } - else - { - foreach (var unpatchedItem0 in unpatchedItem.Value) + writer.WriteStartObject(); + foreach (var unpatchedItem1 in unpatchedItem0.Value) { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) + writer.WritePropertyName(unpatchedItem1.Key); + if ((unpatchedItem1.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStartObject(); - if (false) - { -#if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; -#endif - foreach (var item in unpatchedItem0.Value) - { -#if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), buffer.Slice(0, bytesWritten)); -#else - bool patchContains = Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)); -#endif - if (!patchContains) - { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(item.Value); - } - } - - Patch.WriteTo(writer, global::System.Text.Encoding.UTF8.GetBytes($"$.propertyWithNestedDictionary[\"{unpatchedItem.Key}\"][\"{unpatchedItem0.Key}\"]")); - } - else - { - foreach (var unpatchedItem1 in unpatchedItem0.Value) - { - writer.WritePropertyName(unpatchedItem1.Key); - if ((unpatchedItem1.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(unpatchedItem1.Value); - } - } - writer.WriteEndObject(); + writer.WriteStringValue(unpatchedItem1.Value); } + writer.WriteEndObject(); } writer.WriteEndObject(); } From 4f16ce3619ad39ec9e66afe158f833b4a599e994 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:14:09 +0000 Subject: [PATCH 23/30] refactor(http-client-csharp): remove standalone C# interpolation escaping helper Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Providers/MrwSerializationTypeDefinition.Dynamic.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 1c1d4be9285..9b08060d39f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -819,8 +819,10 @@ private static string BuildJsonPathForProperty(string propertySerializedName, bo ? BuildJsonPathQuotedProperty(propertySerializedName) : $"$.{propertySerializedName}"; + // FormattableStringExpression writes this text raw into a C# interpolated string, so the literal + // portion must be escaped for both C# string syntax and interpolation-hole syntax. return escapeForCSharpString - ? EscapeForCSharpInterpolatedString(jsonPath) + ? jsonPath.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("{", "{{{{").Replace("}", "}}}}") : jsonPath; } @@ -841,11 +843,6 @@ private static bool RequiresJsonPathBracketNotation(string propertySerializedNam propertySerializedName.Any(char.IsWhiteSpace); } - private static string EscapeForCSharpInterpolatedString(string value) - { - return value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("{", "{{{{").Replace("}", "}}}}"); - } - private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, From 7d7bcafee3d5df381ad322cb3b5d18d0e5b6ab9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:37:51 +0000 Subject: [PATCH 24/30] refactor(http-client-csharp): remove special patch path escaping Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 25 +---- .../DynamicModelSerializationTests.cs | 94 ------------------- 2 files changed, 3 insertions(+), 116 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 9b08060d39f..c0a499b5851 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -815,34 +815,15 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpString) { - var jsonPath = RequiresJsonPathBracketNotation(propertySerializedName) - ? BuildJsonPathQuotedProperty(propertySerializedName) + var jsonPath = propertySerializedName.Contains('.') + ? $"$[\"{propertySerializedName}\"]" : $"$.{propertySerializedName}"; - // FormattableStringExpression writes this text raw into a C# interpolated string, so the literal - // portion must be escaped for both C# string syntax and interpolation-hole syntax. return escapeForCSharpString - ? jsonPath.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("{", "{{{{").Replace("}", "}}}}") + ? jsonPath.Replace("\"", "\\\"") : jsonPath; } - private static string BuildJsonPathQuotedProperty(string propertySerializedName) - { - // The JsonPath reader has no escape syntax: a quoted segment ends at the first occurrence of the - // chosen delimiter immediately followed by ']'. Pick whichever delimiter doesn't form that sequence - // in the property name so a name containing both quote characters (e.g. `a"b'c`) is not truncated. - bool doubleQuoteCollides = propertySerializedName.Contains("\"]", StringComparison.Ordinal); - bool singleQuoteCollides = propertySerializedName.Contains("']", StringComparison.Ordinal); - char quote = doubleQuoteCollides && !singleQuoteCollides ? '\'' : '\"'; - return $"$[{quote}{propertySerializedName}{quote}]"; - } - - private static bool RequiresJsonPathBracketNotation(string propertySerializedName) - { - return propertySerializedName.IndexOfAny(['.', '[', ']', '"', '\'', '\\']) >= 0 || - propertySerializedName.Any(char.IsWhiteSpace); - } - private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index 9e98a2b2156..fd8de96e57a 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -465,100 +465,6 @@ public void DottedSerializedNameCollectionPatchGuards() Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); } - [Test] - public void EscapedSerializedNameCollectionPatchGuards() - { - var inputModel = InputFactory.Model( - "dynamicModel", - isDynamicModel: true, - properties: - [ - InputFactory.Property( - "children", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - serializedName: "1 foo{bar}-[\"\\baz"), - InputFactory.Property( - "siblings", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - serializedName: "plainName") - ]); - - MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); - var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; - - Assert.IsNotNull(model); - var serialization = model!.SerializationProviders.Single(); - var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serialization, - name => name is "JsonModelWriteCore" or "ActiveChildren")); - var content = writer.Write().Content; - - StringAssert.Contains("""Patch.Contains("$[\"1 foo{bar}-[\"\\baz\"]"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"1 foo{{bar}}-[\"\\baz\"][{i}]")""", content); - StringAssert.Contains("""Patch.Contains("$.plainName"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$.plainName[{i}]")""", content); - } - - [Test] - public void BothQuoteCharactersSerializedNameCollectionPatchGuards() - { - var inputModel = InputFactory.Model( - "dynamicModel", - isDynamicModel: true, - properties: - [ - InputFactory.Property( - "children", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - serializedName: "a\"b'c"), - InputFactory.Property( - "siblings", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - // A single-quote delimiter would terminate the JsonPath segment early here because the - // name contains "']", so the double-quote delimiter must be chosen instead. - serializedName: "a\"b']c") - ]); - - MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); - var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; - - Assert.IsNotNull(model); - var serialization = model!.SerializationProviders.Single(); - var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serialization, - name => name is "JsonModelWriteCore" or "ActiveChildren")); - var content = writer.Write().Content; - - StringAssert.Contains("""Patch.Contains("$[\"a\"b'c\"]"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"a\"b'c\"][{i}]")""", content); - StringAssert.Contains("""Patch.Contains("$[\"a\"b']c\"]"u8)""", content); - StringAssert.Contains("""Encoding.UTF8.GetBytes($"$[\"a\"b']c\"][{i}]")""", content); - } - [Test] public void DottedSerializedNameDictionaryPatchGuard() { From 5b6c5a63974228b56e7972e23d8221a8b8408b2f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 20:43:53 +0000 Subject: [PATCH 25/30] test(http-client-csharp): cover nested collection patch serialization Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.cs | 2 +- .../Sample_TypeSpec/DynamicModelTests.cs | 60 +++++++++++++++++++ ...ottedSerializedNameDictionaryPatchGuard.cs | 2 +- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index ba886189ede..5a19c692e73 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -2001,7 +2001,7 @@ private MethodBodyStatement WrapInIsDefined( { #pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. ScopedApi? patchCheck = _jsonPatchProperty != null - ? Not(_jsonPatchProperty.As().Contains(LiteralU8(BuildJsonPathForProperty(jsonSerializedName, escapeForCSharpString: false)))) + ? Not(_jsonPatchProperty.As().Contains(LiteralU8($"$.{jsonSerializedName}"))) : null; #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs index ee75c25c910..0e0decc1737 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs @@ -403,6 +403,66 @@ public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(bool unr Assert.That(document.RootElement.GetProperty("children").GetArrayLength(), Is.EqualTo(256)); } + [TestCase("nestedChildren")] + [TestCase("nestedChildDictionary")] + [TestCase("dictionaryChildren")] + [TestCase("listOfDictionaries")] + public void JsonModelWrite_UnpatchedNestedCollectionSerializes(string propertyName) + { + const int Count = 256; + var model = new NullableDynamicModel(); + switch (propertyName) + { + case "nestedChildren": + model.NestedChildren = Enumerable.Range(0, Count) + .Select(_ => (IList)[new AnotherDynamicModel("value")]) + .ToList(); + break; + case "nestedChildDictionary": + model.NestedChildDictionary = Enumerable.Range(0, Count) + .ToDictionary( + index => index.ToString(), + _ => (IDictionary)new Dictionary + { + ["value"] = new AnotherDynamicModel("value") + }); + break; + case "dictionaryChildren": + model.DictionaryChildren = Enumerable.Range(0, Count) + .ToDictionary( + index => index.ToString(), + _ => (IList)[new AnotherDynamicModel("value")]); + break; + case "listOfDictionaries": + model.ListOfDictionaries = Enumerable.Range(0, Count) + .Select(_ => (IDictionary)new Dictionary + { + ["value"] = new AnotherDynamicModel("value") + }) + .ToList(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(propertyName), propertyName, null); + } + + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer); + var jsonModel = (IJsonModel)model; + jsonModel.Write(writer, ModelReaderWriterOptions.Json); + writer.Flush(); + buffer.Clear(); + writer.Reset(buffer); + + jsonModel.Write(writer, ModelReaderWriterOptions.Json); + writer.Flush(); + + using var document = JsonDocument.Parse(buffer.WrittenMemory); + var collection = document.RootElement.GetProperty(propertyName); + Assert.That( + collection.ValueKind == JsonValueKind.Array ? collection.GetArrayLength() : collection.EnumerateObject().Count(), + Is.EqualTo(Count)); + } + [TestCase(false)] [TestCase(true)] public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(bool unrelatedPatch) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs index aedcdf9ed6f..23dd95bd85d 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs @@ -20,7 +20,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format."); } #pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - if ((global::Sample.Optional.IsCollectionDefined(Metadata) && !Patch.Contains("$[\"foo.bar\"]"u8))) + if ((global::Sample.Optional.IsCollectionDefined(Metadata) && !Patch.Contains("$.foo.bar"u8))) { writer.WritePropertyName("foo.bar"u8); writer.WriteStartObject(); From a4844ac8dde1785aa649118b499e8387619be2c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 21:05:18 +0000 Subject: [PATCH 26/30] test(http-client-csharp): cover patched nested collections Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 2 +- .../Sample_TypeSpec/DynamicModelTests.cs | 87 +++++++++++++++++++ .../WriteNestedDictDynamicModelProperties.cs | 4 +- .../WriteNestedDictPrimitiveProperties.cs | 4 +- 4 files changed, 92 insertions(+), 5 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index c0a499b5851..2142c796ad4 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -117,7 +117,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite // Process key-value pair if patch doesn't contain it var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) { - CreateDictionaryItemSerialization(keyValuePair, hasPatch, itemSuppressPatchLogic: false) + CreateDictionaryItemSerialization(keyValuePair, patchContainsNet8Var, itemSuppressPatchLogic: false) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs index 0e0decc1737..99df7b74a00 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs @@ -463,6 +463,93 @@ public void JsonModelWrite_UnpatchedNestedCollectionSerializes(string propertyNa Is.EqualTo(Count)); } + [TestCase("nestedChildren")] + [TestCase("nestedChildDictionary")] + [TestCase("dictionaryChildren")] + [TestCase("listOfDictionaries")] + public void JsonModelWrite_PatchedNestedCollectionSerializesParentPatch(string propertyName) + { + var model = new NullableDynamicModel(); + var patchPath = propertyName switch + { + "nestedChildren" => SetNestedChildren(model), + "nestedChildDictionary" => SetNestedChildDictionary(model), + "dictionaryChildren" => SetDictionaryChildren(model), + "listOfDictionaries" => SetListOfDictionaries(model), + _ => throw new ArgumentOutOfRangeException(nameof(propertyName), propertyName, null) + }; + +#pragma warning disable SCME0001 + model.Patch.Set(Encoding.UTF8.GetBytes(patchPath), "patched"); +#pragma warning restore SCME0001 + + var data = ModelReaderWriter.Write(model, ModelReaderWriterOptions.Json, SampleTypeSpecContext.Default); + using var document = JsonDocument.Parse(data); + JsonElement patchedElement; + switch (propertyName) + { + case "nestedChildren": + var nestedChildren = document.RootElement.GetProperty("nestedChildren")[0]; + patchedElement = nestedChildren[0]; + break; + case "nestedChildDictionary": + var nestedChildDictionary = document.RootElement.GetProperty("nestedChildDictionary").GetProperty("outer"); + patchedElement = nestedChildDictionary.GetProperty("patched"); + break; + case "dictionaryChildren": + var dictionaryChildren = document.RootElement.GetProperty("dictionaryChildren").GetProperty("outer"); + patchedElement = dictionaryChildren[0]; + break; + case "listOfDictionaries": + var listOfDictionaries = document.RootElement.GetProperty("listOfDictionaries")[0]; + patchedElement = listOfDictionaries.GetProperty("patched"); + break; + default: + throw new ArgumentOutOfRangeException(nameof(propertyName), propertyName, null); + } + + Assert.That(patchedElement.GetProperty("extra").GetString(), Is.EqualTo("patched")); + + static string SetNestedChildren(NullableDynamicModel model) + { + model.NestedChildren = [[null!]]; + return "$.nestedChildren[0][0].extra"; + } + + static string SetNestedChildDictionary(NullableDynamicModel model) + { + model.NestedChildDictionary = new Dictionary> + { + ["outer"] = new Dictionary + { + ["patched"] = null! + } + }; + return "$.nestedChildDictionary.outer.patched.extra"; + } + + static string SetDictionaryChildren(NullableDynamicModel model) + { + model.DictionaryChildren = new Dictionary> + { + ["outer"] = [null!] + }; + return "$.dictionaryChildren.outer[0].extra"; + } + + static string SetListOfDictionaries(NullableDynamicModel model) + { + model.ListOfDictionaries = + [ + new Dictionary + { + ["patched"] = null! + } + ]; + return "$.listOfDictionaries[0].patched.extra"; + } + } + [TestCase(false)] [TestCase(true)] public void JsonPatchRemove_ChildRootWithUnpatchedParentCollection(bool unrelatedPatch) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index 0b6e4df78f0..e66848ced6c 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -64,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (hasPatch) + if (patchContains) { #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; @@ -86,7 +86,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (hasPatch) + if (patchContains0) { #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index f5b4c88a227..d227ea732f0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -64,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (hasPatch) + if (patchContains) { #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; @@ -86,7 +86,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (hasPatch) + if (patchContains0) { #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; From e046d65b677025fb41a9f1902ea1db56024ae887 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 16 Sep 2026 21:23:43 +0000 Subject: [PATCH 27/30] revert(http-client-csharp): remove out-of-scope JSON path escaping and fix nested patch guard Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 27 ++----- .../DynamicModelSerializationTests.cs | 58 -------------- ...ttedSerializedNameCollectionPatchGuards.cs | 71 ------------------ ...ottedSerializedNameDictionaryPatchGuard.cs | 75 ------------------- .../WriteNestedDictDynamicModelProperties.cs | 4 +- .../WriteNestedDictPrimitiveProperties.cs | 4 +- 6 files changed, 11 insertions(+), 228 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs delete mode 100644 packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 2142c796ad4..2804e7b9d72 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -68,7 +68,6 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite } var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); MethodBodyStatement? hasPatchDeclaration = null; ValueExpression hasPatch; if (parentHasPatch == null) @@ -86,7 +85,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite } ValueExpression jsonPath = parentIndices.Count > 0 - ? Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, [.. parentIndices]).As()) + ? Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, [.. parentIndices]).As()) : LiteralU8(jsonPathTemplate); var foreachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression keyValuePair); @@ -117,7 +116,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite // Process key-value pair if patch doesn't contain it var ifPatchDoesNotContainStatement = new IfStatement(Not(patchContainsNet8Var)) { - CreateDictionaryItemSerialization(keyValuePair, patchContainsNet8Var, itemSuppressPatchLogic: false) + CreateDictionaryItemSerialization(keyValuePair, hasPatch, itemSuppressPatchLogic: false) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( @@ -223,7 +222,6 @@ private MethodBodyStatement CreateListSerializationWithPatch( } var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); // The prefix overload includes indexed descendants, unlike an exact-path Contains check. // Nested collections under the same serialized property can reuse the parent guard. MethodBodyStatement? hasPatchDeclaration = null; @@ -244,7 +242,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( var patchIsRemovedCondition = hasPatch.As().And(patchSnippet.IsRemoved( Utf8Snippets.GetBytes( - new FormattableStringExpression(csharpJsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) + new FormattableStringExpression(jsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) .As()))); if (childIsRemovedCondition != null) @@ -273,7 +271,7 @@ private MethodBodyStatement CreateListSerializationWithPatch( MethodBodyStatement writeToPatchStatement = parentIndices.Count == 0 ? patchSnippet.WriteTo(_utf8JsonWriterSnippet, LiteralU8(jsonPathTemplate)).Terminate() - : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(csharpJsonPathTemplate, parentIndices).As())).Terminate(); + : patchSnippet.WriteTo(_utf8JsonWriterSnippet, Utf8Snippets.GetBytes(new FormattableStringExpression(jsonPathTemplate, parentIndices).As())).Terminate(); if (parentIndices.Count > 0) { writeToPatchStatement = new IfStatement(hasPatch.As()) { writeToPatchStatement }; @@ -748,7 +746,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) _jsonPatchProperty!.As().Contains(LiteralU8("$"), LiteralU8(serializedName)), out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( - BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpString: true), + BuildJsonPathForElement(serializedName, [indexVar]), [indexVar]).As()); isActive = Not(hasPatch).Or(Not(_jsonPatchProperty!.As().IsRemoved(itemPath))).And(isActive); var forStatement = new ForStatement( @@ -799,10 +797,10 @@ private List GetQualifyingDynamicListProperties() #pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - private static string BuildJsonPathForElement(string propertySerializedName, List indices, bool escapeForCSharpString = false) + private static string BuildJsonPathForElement(string propertySerializedName, List indices) { var count = indices.Count; - var result = BuildJsonPathForProperty(propertySerializedName, escapeForCSharpString); + var result = $"$.{propertySerializedName}"; for (int i = 0; i < count; i++) { result += indices[i] is MemberExpression @@ -813,17 +811,6 @@ private static string BuildJsonPathForElement(string propertySerializedName, Lis return result; } - private static string BuildJsonPathForProperty(string propertySerializedName, bool escapeForCSharpString) - { - var jsonPath = propertySerializedName.Contains('.') - ? $"$[\"{propertySerializedName}\"]" - : $"$.{propertySerializedName}"; - - return escapeForCSharpString - ? jsonPath.Replace("\"", "\\\"") - : jsonPath; - } - private static ValueExpression GetDeserializationMethodInvocationForType( ModelProvider model, ScopedApi element, diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs index fd8de96e57a..f301a4673b8 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/DynamicModelSerializationTests.cs @@ -433,64 +433,6 @@ public void PropagateModelListPropertyHelperMethods() Assert.AreEqual(Helpers.GetExpectedFromFile(), file.Content); } - [Test] - public void DottedSerializedNameCollectionPatchGuards() - { - var inputModel = InputFactory.Model( - "dynamicModel", - isDynamicModel: true, - properties: - [ - InputFactory.Property( - "children", - InputFactory.Array(InputFactory.Model( - "anotherDynamic", - isDynamicModel: true, - properties: - [ - InputFactory.Property("value", InputPrimitiveType.String, isRequired: true) - ])), - serializedName: "foo.bar") - ]); - - MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); - var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; - - Assert.IsNotNull(model); - var serialization = model!.SerializationProviders.Single(); - var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serialization, - name => name is "JsonModelWriteCore" or "ActiveChildren")); - - Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); - } - - [Test] - public void DottedSerializedNameDictionaryPatchGuard() - { - var inputModel = InputFactory.Model( - "dynamicModel", - isDynamicModel: true, - properties: - [ - InputFactory.Property( - "metadata", - InputFactory.Dictionary(InputPrimitiveType.String), - serializedName: "foo.bar") - ]); - - MockHelpers.LoadMockGenerator(inputModels: () => [inputModel]); - var model = ScmCodeModelGenerator.Instance.TypeFactory.CreateModel(inputModel) as ClientModel.Providers.ScmModelProvider; - - Assert.IsNotNull(model); - var serialization = model!.SerializationProviders.Single(); - var writer = new TypeProviderWriter(new FilteredMethodsTypeProvider( - serialization, - name => name is "JsonModelWriteCore")); - - Assert.AreEqual(Helpers.GetExpectedFromFile(), writer.Write().Content); - } - [Test] public void PropagateModelDictionaryProperty() { diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs deleted file mode 100644 index 228d9253527..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameCollectionPatchGuards.cs +++ /dev/null @@ -1,71 +0,0 @@ -// - -#nullable disable - -using System; -using System.ClientModel.Primitives; -using System.Collections.Generic; -using System.Text; -using System.Text.Json; -using Sample.Models; - -namespace Sample -{ - public partial class DynamicModel - { - protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) - { - string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if ((format != "J")) - { - throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format."); - } -#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - if (Patch.Contains("$[\"foo.bar\"]"u8)) - { - if (!Patch.IsRemoved("$[\"foo.bar\"]"u8)) - { - writer.WritePropertyName("foo.bar"u8); - Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); - } - } - else if (global::Sample.Optional.IsCollectionDefined(Children)) - { - writer.WritePropertyName("foo.bar"u8); - writer.WriteStartArray(); - bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); - for (int i = 0; (i < Children.Count); i++) - { - if (((hasPatch && Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$[\"foo.bar\"][{i}]"))) || ((Children[i] != null) && Children[i].Patch.IsRemoved("$"u8)))) - { - continue; - } - writer.WriteObjectValue(Children[i], options); - } - Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); - writer.WriteEndArray(); - } - - Patch.WriteTo(writer); -#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - } - -#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - private global::System.Collections.Generic.IEnumerable ActiveChildren() - { - if (!global::Sample.Optional.IsCollectionDefined(Children)) - { - yield break; - } - bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); - for (int i = 0; (i < Children.Count); i++) - { - if (((!hasPatch || !Patch.IsRemoved(global::System.Text.Encoding.UTF8.GetBytes($"$[\"foo.bar\"][{i}]"))) && ((Children[i] == null) || !Children[i].Patch.IsRemoved("$"u8)))) - { - yield return Children[i]; - } - } - } -#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - } -} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs deleted file mode 100644 index 23dd95bd85d..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/DottedSerializedNameDictionaryPatchGuard.cs +++ /dev/null @@ -1,75 +0,0 @@ -// - -#nullable disable - -using System; -using System.ClientModel.Primitives; -using System.Text; -using System.Text.Json; -using Sample.Models; - -namespace Sample -{ - public partial class DynamicModel - { - protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) - { - string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel)this).GetFormatFromOptions(options) : options.Format; - if ((format != "J")) - { - throw new global::System.FormatException($"The model {nameof(global::Sample.Models.DynamicModel)} does not support writing '{format}' format."); - } -#pragma warning disable SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - if ((global::Sample.Optional.IsCollectionDefined(Metadata) && !Patch.Contains("$.foo.bar"u8))) - { - writer.WritePropertyName("foo.bar"u8); - writer.WriteStartObject(); - bool hasPatch = Patch.Contains("$"u8, "foo.bar"u8); - if (hasPatch) - { -#if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; -#endif - foreach (var item in Metadata) - { -#if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$[\"foo.bar\"]"u8, buffer.Slice(0, bytesWritten)); -#else - bool patchContains = Patch.Contains("$[\"foo.bar\"]"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)); -#endif - if (!patchContains) - { - writer.WritePropertyName(item.Key); - if ((item.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(item.Value); - } - } - - Patch.WriteTo(writer, "$[\"foo.bar\"]"u8); - } - else - { - foreach (var unpatchedItem in Metadata) - { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) - { - writer.WriteNullValue(); - continue; - } - writer.WriteStringValue(unpatchedItem.Value); - } - } - writer.WriteEndObject(); - } - - Patch.WriteTo(writer); -#pragma warning restore SCME0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. - } - } -} diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index e66848ced6c..0b6e4df78f0 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -64,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (patchContains) + if (hasPatch) { #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; @@ -86,7 +86,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (patchContains0) + if (hasPatch) { #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index d227ea732f0..f5b4c88a227 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -64,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (patchContains) + if (hasPatch) { #if NET8_0_OR_GREATER global::System.Span buffer0 = stackalloc byte[256]; @@ -86,7 +86,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite continue; } writer.WriteStartObject(); - if (patchContains0) + if (hasPatch) { #if NET8_0_OR_GREATER global::System.Span buffer1 = stackalloc byte[256]; From 7fe4b32667619d220eaa4c4d09783ee96ade8ffe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:16:53 +0000 Subject: [PATCH 28/30] rename unpatchedItem loop variable back to item per review feedback Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../MrwSerializationTypeDefinition.Dynamic.cs | 4 +- .../WriteDictionaryProperties.cs | 22 +++++----- .../WriteNestedArrayDictionaryProperties.cs | 8 ++-- .../WriteNestedDictDynamicModelProperties.cs | 36 ++++++++-------- .../WriteNestedDictPrimitiveProperties.cs | 42 +++++++++---------- 5 files changed, 56 insertions(+), 56 deletions(-) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs index 2804e7b9d72..dda66667d85 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/src/Providers/MrwSerializationTypeDefinition.Dynamic.cs @@ -56,7 +56,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite // any path-dependent patch checks entirely instead of materializing a dead patched branch. if (suppressPatchLogic) { - var noPatchForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression noPatchKeyValuePair); + var noPatchForeachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression noPatchKeyValuePair); noPatchForeachStatement.Add(CreateDictionaryItemSerialization(noPatchKeyValuePair, null, itemSuppressPatchLogic: true)); return new MethodBodyStatement[] @@ -137,7 +137,7 @@ MethodBodyStatement CreateDictionaryItemSerialization(KeyValuePairExpression ite patchSnippet.WriteTo(_utf8JsonWriterSnippet, jsonPath).Terminate() }; - var unpatchedForeachStatement = new ForEachStatement("unpatchedItem", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); + var unpatchedForeachStatement = new ForEachStatement("item", dictionary, out KeyValuePairExpression unpatchedKeyValuePair); // This branch only runs when the collection has no relevant patch, so serializers can skip path-dependent patch work. unpatchedForeachStatement.Add(CreateDictionaryItemSerialization(unpatchedKeyValuePair, null, itemSuppressPatchLogic: true)); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs index 16855fda334..1d31320c51f 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteDictionaryProperties.cs @@ -66,10 +66,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in Cats) + foreach (var item in Cats) { - writer.WritePropertyName(unpatchedItem.Key); - writer.WriteObjectValue(unpatchedItem.Value, options); + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); } } writer.WriteEndObject(); @@ -108,15 +108,15 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in Names) + foreach (var item in Names) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item.Key); + if ((item.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(unpatchedItem.Value); + writer.WriteStringValue(item.Value); } } writer.WriteEndObject(); @@ -155,15 +155,15 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in OptionalNames) + foreach (var item in OptionalNames) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item.Key); + if ((item.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(unpatchedItem.Value); + writer.WriteStringValue(item.Value); } } writer.WriteEndObject(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs index 6f2df515ebf..d0044bbe1e2 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedArrayDictionaryProperties.cs @@ -115,15 +115,15 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in PropertyWithNestedArray[i][i0][i1]) + foreach (var item in PropertyWithNestedArray[i][i0][i1]) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item.Key); + if ((item.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(unpatchedItem.Value); + writer.WriteStringValue(item.Value); } } writer.WriteEndObject(); diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs index 0b6e4df78f0..9e7dfdafb1e 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictDynamicModelProperties.cs @@ -110,10 +110,10 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in item0.Value) + foreach (var item1 in item0.Value) { - writer.WritePropertyName(unpatchedItem.Key); - writer.WriteObjectValue(unpatchedItem.Value, options); + writer.WritePropertyName(item1.Key); + writer.WriteObjectValue(item1.Value, options); } } writer.WriteEndObject(); @@ -124,19 +124,19 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in item.Value) + foreach (var item0 in item.Value) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); - foreach (var unpatchedItem0 in unpatchedItem.Value) + foreach (var item1 in item0.Value) { - writer.WritePropertyName(unpatchedItem0.Key); - writer.WriteObjectValue(unpatchedItem0.Value, options); + writer.WritePropertyName(item1.Key); + writer.WriteObjectValue(item1.Value, options); } writer.WriteEndObject(); } @@ -149,28 +149,28 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in PropertyWithNestedDictionary) + foreach (var item in PropertyWithNestedDictionary) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item.Key); + if ((item.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); - foreach (var unpatchedItem0 in unpatchedItem.Value) + foreach (var item0 in item.Value) { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); - foreach (var unpatchedItem1 in unpatchedItem0.Value) + foreach (var item1 in item0.Value) { - writer.WritePropertyName(unpatchedItem1.Key); - writer.WriteObjectValue(unpatchedItem1.Value, options); + writer.WritePropertyName(item1.Key); + writer.WriteObjectValue(item1.Value, options); } writer.WriteEndObject(); } diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs index f5b4c88a227..0b79e811907 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/Providers/MrwSerializationTypeDefinitions/TestData/DynamicModelSerializationTests/WriteNestedDictPrimitiveProperties.cs @@ -115,15 +115,15 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in item0.Value) + foreach (var item1 in item0.Value) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item1.Key); + if ((item1.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(unpatchedItem.Value); + writer.WriteStringValue(item1.Value); } } writer.WriteEndObject(); @@ -134,24 +134,24 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in item.Value) + foreach (var item0 in item.Value) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); - foreach (var unpatchedItem0 in unpatchedItem.Value) + foreach (var item1 in item0.Value) { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) + writer.WritePropertyName(item1.Key); + if ((item1.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(unpatchedItem0.Value); + writer.WriteStringValue(item1.Value); } writer.WriteEndObject(); } @@ -164,33 +164,33 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite } else { - foreach (var unpatchedItem in PropertyWithNestedDictionary) + foreach (var item in PropertyWithNestedDictionary) { - writer.WritePropertyName(unpatchedItem.Key); - if ((unpatchedItem.Value == null)) + writer.WritePropertyName(item.Key); + if ((item.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); - foreach (var unpatchedItem0 in unpatchedItem.Value) + foreach (var item0 in item.Value) { - writer.WritePropertyName(unpatchedItem0.Key); - if ((unpatchedItem0.Value == null)) + writer.WritePropertyName(item0.Key); + if ((item0.Value == null)) { writer.WriteNullValue(); continue; } writer.WriteStartObject(); - foreach (var unpatchedItem1 in unpatchedItem0.Value) + foreach (var item1 in item0.Value) { - writer.WritePropertyName(unpatchedItem1.Key); - if ((unpatchedItem1.Value == null)) + writer.WritePropertyName(item1.Key); + if ((item1.Value == null)) { writer.WriteNullValue(); continue; } - writer.WriteStringValue(unpatchedItem1.Value); + writer.WriteStringValue(item1.Value); } writer.WriteEndObject(); } From 856a745eeab057216c5f2a754c6cfa97ed12b974 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 17 Sep 2026 15:35:00 +0000 Subject: [PATCH 29/30] regen Sample-TypeSpec generated files to fix CI regen-check failure Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Models/DynamicModel.Serialization.cs | 290 +++++++++++++----- .../NullableDynamicModel.Serialization.cs | 200 ++++++++---- 2 files changed, 351 insertions(+), 139 deletions(-) diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs index 2f49b19db2a..7fac448642e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/DynamicModel.Serialization.cs @@ -177,50 +177,74 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("optionalNullableDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "optionalNullableDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in OptionalNullableDictionary) - { + foreach (var item in OptionalNullableDictionary) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNullableDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.optionalNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.optionalNullableDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.optionalNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.optionalNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteNumberValue(item.Value); + } + } + + Patch.WriteTo(writer, "$.optionalNullableDictionary"u8); + } + else + { + foreach (var item in OptionalNullableDictionary) { writer.WritePropertyName(item.Key); writer.WriteNumberValue(item.Value); } } - - Patch.WriteTo(writer, "$.optionalNullableDictionary"u8); writer.WriteEndObject(); } if (Optional.IsCollectionDefined(RequiredNullableDictionary) && !Patch.Contains("$.requiredNullableDictionary"u8)) { writer.WritePropertyName("requiredNullableDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "requiredNullableDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in RequiredNullableDictionary) - { + foreach (var item in RequiredNullableDictionary) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.requiredNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.requiredNullableDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.requiredNullableDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.requiredNullableDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.requiredNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.requiredNullableDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteNumberValue(item.Value); + } + } + + Patch.WriteTo(writer, "$.requiredNullableDictionary"u8); + } + else + { + foreach (var item in RequiredNullableDictionary) { writer.WritePropertyName(item.Key); writer.WriteNumberValue(item.Value); } } - - Patch.WriteTo(writer, "$.requiredNullableDictionary"u8); writer.WriteEndObject(); } else if (!Patch.Contains("$.requiredNullableDictionary"u8)) @@ -231,25 +255,37 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("primitiveDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "primitiveDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in PrimitiveDictionary) - { + foreach (var item in PrimitiveDictionary) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.primitiveDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.primitiveDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.primitiveDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.primitiveDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.primitiveDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.primitiveDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteNumberValue(item.Value); + } + } + + Patch.WriteTo(writer, "$.primitiveDictionary"u8); + } + else + { + foreach (var item in PrimitiveDictionary) { writer.WritePropertyName(item.Key); writer.WriteNumberValue(item.Value); } } - - Patch.WriteTo(writer, "$.primitiveDictionary"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.foo"u8)) @@ -314,7 +350,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } writer.WriteObjectValue(ListOfListFoo[i][i0], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]")); + if (hasPatch) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.listOfListFoo"u8); @@ -324,43 +363,105 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("dictionaryFoo"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryFoo"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in DictionaryFoo) - { + foreach (var item in DictionaryFoo) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryFoo"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); + } + } + + Patch.WriteTo(writer, "$.dictionaryFoo"u8); + } + else + { + foreach (var item in DictionaryFoo) { writer.WritePropertyName(item.Key); writer.WriteObjectValue(item.Value, options); } } - - Patch.WriteTo(writer, "$.dictionaryFoo"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.dictionaryOfDictionaryFoo"u8)) { writer.WritePropertyName("dictionaryOfDictionaryFoo"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryOfDictionaryFoo"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in DictionaryOfDictionaryFoo) - { + foreach (var item in DictionaryOfDictionaryFoo) + { +#if NET8_0_OR_GREATER + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryOfDictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryOfDictionaryFoo"u8, buffer.Slice(0, bytesWritten)); +#else + bool patchContains = Patch.Contains("$.dictionaryOfDictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); +#endif + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (hasPatch) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in item.Value) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryOfDictionaryFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryOfDictionaryFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); #else - bool patchContains = Patch.Contains("$.dictionaryOfDictionaryFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); #endif - if (!patchContains) + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); + } + } + + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]")); + } + else + { + foreach (var item0 in item.Value) + { + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, "$.dictionaryOfDictionaryFoo"u8); + } + else + { + foreach (var item in DictionaryOfDictionaryFoo) { writer.WritePropertyName(item.Key); if (item.Value == null) @@ -369,48 +470,64 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); -#if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; -#endif foreach (var item0 in item.Value) { -#if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); -#else - bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); -#endif - if (!patchContains0) - { - writer.WritePropertyName(item0.Key); - writer.WriteObjectValue(item0.Value, options); - } + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); } - - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryOfDictionaryFoo[\"{item.Key}\"]")); writer.WriteEndObject(); } } - - Patch.WriteTo(writer, "$.dictionaryOfDictionaryFoo"u8); writer.WriteEndObject(); } if (!Patch.Contains("$.dictionaryListFoo"u8)) { writer.WritePropertyName("dictionaryListFoo"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in DictionaryListFoo) - { + foreach (var item in DictionaryListFoo) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryListFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryListFoo"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryListFoo"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryListFoo"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryListFoo"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryListFoo"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartArray(); + for (int i = 0; i < item.Value.Count; i++) + { + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + { + continue; + } + writer.WriteObjectValue(item.Value[i], options); + } + if (hasPatch) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"]")); + } + writer.WriteEndArray(); + } + } + + Patch.WriteTo(writer, "$.dictionaryListFoo"u8); + } + else + { + foreach (var item in DictionaryListFoo) { writer.WritePropertyName(item.Key); if (item.Value == null) @@ -419,21 +536,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryListFoo"u8); for (int i = 0; i < item.Value.Count; i++) { - if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } writer.WriteObjectValue(item.Value[i], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryListFoo[\"{item.Key}\"]")); writer.WriteEndArray(); } } - - Patch.WriteTo(writer, "$.dictionaryListFoo"u8); writer.WriteEndObject(); } if (Patch.Contains("$.listOfDictionaryFoo"u8)) @@ -461,25 +574,36 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in ListOfDictionaryFoo[i]) - { + foreach (var item in ListOfDictionaryFoo[i]) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]"), Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); + } + } + + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]")); + } + else + { + foreach (var item in ListOfDictionaryFoo[i]) { writer.WritePropertyName(item.Key); writer.WriteObjectValue(item.Value, options); } } - - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaryFoo[{i}]")); writer.WriteEndObject(); } Patch.WriteTo(writer, "$.listOfDictionaryFoo"u8); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs index 480eb525701..60ec553e5b7 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Sample-TypeSpec/src/Generated/Models/NullableDynamicModel.Serialization.cs @@ -116,25 +116,37 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("childDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "childDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in ChildDictionary) - { + foreach (var item in ChildDictionary) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.childDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.childDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.childDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.childDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.childDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.childDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); + } + } + + Patch.WriteTo(writer, "$.childDictionary"u8); + } + else + { + foreach (var item in ChildDictionary) { writer.WritePropertyName(item.Key); writer.WriteObjectValue(item.Value, options); } } - - Patch.WriteTo(writer, "$.childDictionary"u8); writer.WriteEndObject(); } if (Patch.Contains("$.nestedChildren"u8)) @@ -170,7 +182,10 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit } writer.WriteObjectValue(NestedChildren[i][i0], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]")); + if (hasPatch) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildren[{i}]")); + } writer.WriteEndArray(); } Patch.WriteTo(writer, "$.nestedChildren"u8); @@ -180,18 +195,68 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit { writer.WritePropertyName("nestedChildDictionary"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "nestedChildDictionary"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in NestedChildDictionary) - { + foreach (var item in NestedChildDictionary) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.nestedChildDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.nestedChildDictionary"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.nestedChildDictionary"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.nestedChildDictionary"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.nestedChildDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.nestedChildDictionary"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartObject(); + if (hasPatch) + { +#if NET8_0_OR_GREATER + global::System.Span buffer0 = stackalloc byte[256]; +#endif + foreach (var item0 in item.Value) + { +#if NET8_0_OR_GREATER + int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); + bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); +#else + bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); +#endif + if (!patchContains0) + { + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); + } + } + + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]")); + } + else + { + foreach (var item0 in item.Value) + { + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); + } + } + writer.WriteEndObject(); + } + } + + Patch.WriteTo(writer, "$.nestedChildDictionary"u8); + } + else + { + foreach (var item in NestedChildDictionary) { writer.WritePropertyName(item.Key); if (item.Value == null) @@ -200,48 +265,64 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); -#if NET8_0_OR_GREATER - global::System.Span buffer0 = stackalloc byte[256]; -#endif foreach (var item0 in item.Value) { -#if NET8_0_OR_GREATER - int bytesWritten0 = global::System.Text.Encoding.UTF8.GetBytes(item0.Key.AsSpan(), buffer0); - bool patchContains0 = (bytesWritten0 == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), global::System.Text.Encoding.UTF8.GetBytes(item0.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), buffer0.Slice(0, bytesWritten0)); -#else - bool patchContains0 = Patch.Contains(Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]"), Encoding.UTF8.GetBytes(item0.Key)); -#endif - if (!patchContains0) - { - writer.WritePropertyName(item0.Key); - writer.WriteObjectValue(item0.Value, options); - } + writer.WritePropertyName(item0.Key); + writer.WriteObjectValue(item0.Value, options); } - - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.nestedChildDictionary[\"{item.Key}\"]")); writer.WriteEndObject(); } } - - Patch.WriteTo(writer, "$.nestedChildDictionary"u8); writer.WriteEndObject(); } if (Optional.IsCollectionDefined(DictionaryChildren) && !Patch.Contains("$.dictionaryChildren"u8)) { writer.WritePropertyName("dictionaryChildren"u8); writer.WriteStartObject(); + bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in DictionaryChildren) - { + foreach (var item in DictionaryChildren) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryChildren"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryChildren"u8, buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains("$.dictionaryChildren"u8, global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains("$.dictionaryChildren"u8, buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains("$.dictionaryChildren"u8, Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains("$.dictionaryChildren"u8, Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + if (item.Value == null) + { + writer.WriteNullValue(); + continue; + } + writer.WriteStartArray(); + for (int i = 0; i < item.Value.Count; i++) + { + if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + { + continue; + } + writer.WriteObjectValue(item.Value[i], options); + } + if (hasPatch) + { + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"]")); + } + writer.WriteEndArray(); + } + } + + Patch.WriteTo(writer, "$.dictionaryChildren"u8); + } + else + { + foreach (var item in DictionaryChildren) { writer.WritePropertyName(item.Key); if (item.Value == null) @@ -250,21 +331,17 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartArray(); - bool hasPatch = Patch.Contains("$"u8, "dictionaryChildren"u8); for (int i = 0; i < item.Value.Count; i++) { - if (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"][{i}]")) || item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) + if (item.Value[i] != null && item.Value[i].Patch.IsRemoved("$"u8)) { continue; } writer.WriteObjectValue(item.Value[i], options); } - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.dictionaryChildren[\"{item.Key}\"]")); writer.WriteEndArray(); } } - - Patch.WriteTo(writer, "$.dictionaryChildren"u8); writer.WriteEndObject(); } if (Patch.Contains("$.listOfDictionaries"u8)) @@ -292,25 +369,36 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit continue; } writer.WriteStartObject(); + if (hasPatch) + { #if NET8_0_OR_GREATER - global::System.Span buffer = stackalloc byte[256]; + global::System.Span buffer = stackalloc byte[256]; #endif - foreach (var item in ListOfDictionaries[i]) - { + foreach (var item in ListOfDictionaries[i]) + { #if NET8_0_OR_GREATER - int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); - bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), buffer.Slice(0, bytesWritten)); + int bytesWritten = global::System.Text.Encoding.UTF8.GetBytes(item.Key.AsSpan(), buffer); + bool patchContains = (bytesWritten == 256) ? Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), global::System.Text.Encoding.UTF8.GetBytes(item.Key)) : Patch.Contains(global::System.Text.Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), buffer.Slice(0, bytesWritten)); #else - bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), Encoding.UTF8.GetBytes(item.Key)); + bool patchContains = Patch.Contains(Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]"), Encoding.UTF8.GetBytes(item.Key)); #endif - if (!patchContains) + if (!patchContains) + { + writer.WritePropertyName(item.Key); + writer.WriteObjectValue(item.Value, options); + } + } + + Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]")); + } + else + { + foreach (var item in ListOfDictionaries[i]) { writer.WritePropertyName(item.Key); writer.WriteObjectValue(item.Value, options); } } - - Patch.WriteTo(writer, Encoding.UTF8.GetBytes($"$.listOfDictionaries[{i}]")); writer.WriteEndObject(); } Patch.WriteTo(writer, "$.listOfDictionaries"u8); From 152be08d8013031e754b804ec651238905e08db8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 17 Sep 2026 16:04:48 +0000 Subject: [PATCH 30/30] test(http-client-csharp): add allocation coverage for unpatched nested collections Co-authored-by: jorgerangel-msft <102122018+jorgerangel-msft@users.noreply.github.com> --- .../Sample_TypeSpec/DynamicModelTests.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs index 99df7b74a00..457a065bb83 100644 --- a/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.TypeSpec.Generator.ClientModel/test/ModelReaderWriterValidation/TestProjects/Sample_TypeSpec/DynamicModelTests.cs @@ -403,6 +403,86 @@ public void JsonModelWrite_UnpatchedCollectionDoesNotAllocatePerElement(bool unr Assert.That(document.RootElement.GetProperty("children").GetArrayLength(), Is.EqualTo(256)); } + [TestCase("nestedChildren", false)] + [TestCase("nestedChildren", true)] + [TestCase("nestedChildDictionary", false)] + [TestCase("nestedChildDictionary", true)] + [TestCase("dictionaryChildren", false)] + [TestCase("dictionaryChildren", true)] + [TestCase("listOfDictionaries", false)] + [TestCase("listOfDictionaries", true)] + public void JsonModelWrite_UnpatchedNestedCollectionDoesNotAllocatePerElement(string propertyName, bool unrelatedPatch) + { + const int Count = 256; + var model = new NullableDynamicModel(); + switch (propertyName) + { + case "nestedChildren": + model.NestedChildren = Enumerable.Range(0, Count) + .Select(_ => (IList)[new AnotherDynamicModel("value")]) + .ToList(); + break; + case "nestedChildDictionary": + model.NestedChildDictionary = Enumerable.Range(0, Count) + .ToDictionary( + index => index.ToString(), + _ => (IDictionary)new Dictionary + { + ["value"] = new AnotherDynamicModel("value") + }); + break; + case "dictionaryChildren": + model.DictionaryChildren = Enumerable.Range(0, Count) + .ToDictionary( + index => index.ToString(), + _ => (IList)[new AnotherDynamicModel("value")]); + break; + case "listOfDictionaries": + model.ListOfDictionaries = Enumerable.Range(0, Count) + .Select(_ => (IDictionary)new Dictionary + { + ["value"] = new AnotherDynamicModel("value") + }) + .ToList(); + break; + default: + throw new ArgumentOutOfRangeException(nameof(propertyName), propertyName, null); + } + +#pragma warning disable SCME0001 + if (unrelatedPatch) + { + model.Patch.Set("$.unrelated"u8, 1); + } +#pragma warning restore SCME0001 + + var buffer = new ArrayBufferWriter(); + using var writer = new Utf8JsonWriter(buffer); + var jsonModel = (IJsonModel)model; + jsonModel.Write(writer, ModelReaderWriterOptions.Json); + writer.Flush(); + buffer.Clear(); + writer.Reset(buffer); + + long before = GC.GetAllocatedBytesForCurrentThread(); + jsonModel.Write(writer, ModelReaderWriterOptions.Json); + writer.Flush(); + long allocated = GC.GetAllocatedBytesForCurrentThread() - before; + + // Shapes whose innermost collection element is an IDictionary<,> (nestedChildDictionary, + // listOfDictionaries) enumerate that dictionary via its interface once per outer element, + // which boxes a struct enumerator regardless of any patch guard. That is unrelated overhead + // this PR does not address, so those shapes use a higher bound; all shapes must stay far + // below what unconditional per-element interpolated-path formatting would cost (tens of KB). + long maxAllocated = propertyName is "nestedChildDictionary" or "listOfDictionaries" ? 20 * 1024 : 4096; + Assert.That(allocated, Is.LessThan(maxAllocated), "Indexed patch paths must not allocate for each unpatched nested element."); + using var document = JsonDocument.Parse(buffer.WrittenMemory); + var collection = document.RootElement.GetProperty(propertyName); + Assert.That( + collection.ValueKind == JsonValueKind.Array ? collection.GetArrayLength() : collection.EnumerateObject().Count(), + Is.EqualTo(Count)); + } + [TestCase("nestedChildren")] [TestCase("nestedChildDictionary")] [TestCase("dictionaryChildren")]