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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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/15] 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; }