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..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,14 +25,16 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( SerializationFormat serializationFormat, ScopedApi patchSnippet, string serializedName, - List? parentIndices = null) + List? parentIndices = null, + ValueExpression? parentHasPatch = null) { 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); @@ -73,7 +75,8 @@ private MethodBodyStatement CreateDictionarySerializationWithPatch( patchSnippet, serializationFormat, serializedName, - childIndices) + childIndices, + parentHasPatch) }; var innerIfElseProcessorStatement = new IfElsePreprocessorStatement( @@ -104,16 +107,36 @@ 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); var allIndices = new List(parentIndices) { indexVar }; var jsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices); - var patchIsRemovedCondition = patchSnippet.IsRemoved( + var csharpJsonPathTemplate = BuildJsonPathForElement(serializedName, parentIndices, escapeForCSharpString: true); + // The prefix overload includes indexed descendants, unlike an exact-path Contains check. + // Nested collections under the same serialized property can reuse the parent guard. + MethodBodyStatement? hasPatchDeclaration = null; + 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(jsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) - .As())); + new FormattableStringExpression(csharpJsonPathTemplate + $"[{{{parentIndices.Count}}}]", allIndices) + .As()))); // Handle model types with their own patch property if (ScmCodeModelGenerator.Instance.TypeFactory.CSharpTypeMap.TryGetValue(type, out var provider) && @@ -147,21 +170,28 @@ private MethodBodyStatement CreateListSerializationWithPatch( patchSnippet, serializationFormat, serializedName, - allIndices) + allIndices, + hasPatch) } }; 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[] + var listStatements = new List { - _utf8JsonWriterSnippet.WriteStartArray(), - 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( @@ -170,7 +200,8 @@ private MethodBodyStatement CreateElementSerializationWithPatch( ScopedApi patchSnippet, SerializationFormat serializationFormat, string serializedName, - List currentIndices) + List currentIndices, + ValueExpression? parentHasPatch = null) { var nestedSerialization = elementType switch { @@ -181,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 }; @@ -215,7 +248,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), @@ -605,10 +638,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)), + out var hasPatch); var itemPath = Utf8Snippets.GetBytes(new FormattableStringExpression( - BuildJsonPathForElement(GetJsonSerializedName(property.WireInfo!), [indexVar]), + BuildJsonPathForElement(serializedName, [indexVar], escapeForCSharpString: true), [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 +665,7 @@ private MethodProvider BuildActiveItemsMethod(PropertyProvider property) { YieldBreak() }, + hasPatchDeclaration, forStatement }; @@ -656,15 +696,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 @@ -675,6 +710,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/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/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..228d9253527 --- /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. + } +} 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..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 @@ -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; } @@ -63,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStartArray(); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if (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,7 +76,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStartArray(); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if (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 58f44014717..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 @@ -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; } @@ -63,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStartArray(); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if (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,7 +76,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStartArray(); 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 (((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 0db787e69fd..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 @@ -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; } @@ -63,7 +64,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStartArray(); for (int i0 = 0; (i0 < PropertyWithNestedArray[i].Count); i0++) { - if (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,7 +76,7 @@ protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWrite writer.WriteStartArray(); for (int i1 = 0; (i1 < PropertyWithNestedArray[i][i0].Count); i1++) { - if (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/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..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 @@ -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; } @@ -304,7 +308,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit writer.WriteStartArray(); 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 (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.listOfListFoo[{i}][{i0}]")) || ListOfListFoo[i][i0] != null && ListOfListFoo[i][i0].Patch.IsRemoved("$"u8)) { continue; } @@ -415,9 +419,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 +448,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 +1136,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..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 @@ -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; } @@ -162,7 +164,7 @@ protected virtual void JsonModelWriteCore(Utf8JsonWriter writer, ModelReaderWrit writer.WriteStartArray(); 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 (hasPatch && Patch.IsRemoved(Encoding.UTF8.GetBytes($"$.nestedChildren[{i}][{i0}]")) || NestedChildren[i][i0] != null && NestedChildren[i][i0].Patch.IsRemoved("$"u8)) { continue; } @@ -248,9 +250,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 +279,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 +889,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]; }