From a060e19efd92fe92f9966d62e2d9d6d0a7677118 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Fri, 3 Oct 2025 18:28:13 +0530 Subject: [PATCH 1/8] mark from as nullable --- .../JsonPatch.SystemTextJson/src/Operations/OperationBase.cs | 4 +++- .../JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs index adfa72767aa0..54145253e1c4 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs @@ -44,7 +44,9 @@ public string op } [JsonPropertyName(nameof(from))] - public string from { get; set; } + #nullable enable + public string? from { get; set; } + #nullable restore public OperationBase() { diff --git a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt index e2421fc8af3c..7679b041b315 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt +++ b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt @@ -97,8 +97,8 @@ Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType.Test = 5 ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Apply(TModel objectToApplyTo, Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters.IObjectAdapter adapter) -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Operation(string op, string path, string from) -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Operation(string op, string path, string from, object value) -> void -~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.get -> string -~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.set -> void +Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.get -> string? +Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.set -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.op.get -> string ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.op.set -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.OperationBase(string op, string path, string from) -> void From 1216ce54b271263bd1793f7aa7f0e4bdf335b231 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Fri, 3 Oct 2025 23:12:54 +0530 Subject: [PATCH 2/8] Add ignore null --- .../src/Operations/OperationBase.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs index 54145253e1c4..622d94a63e16 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs @@ -43,10 +43,10 @@ public string op } } - [JsonPropertyName(nameof(from))] - #nullable enable +#nullable enable + [JsonPropertyName(nameof(from)), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? from { get; set; } - #nullable restore +#nullable restore public OperationBase() { @@ -61,10 +61,4 @@ public OperationBase(string op, string path, string from) this.path = path; this.from = from; } - - public bool ShouldSerializeFrom() - { - return (OperationType == OperationType.Move - || OperationType == OperationType.Copy); - } } From 5b2cba7c3fcbf169fdfdec4d6777866287ac2d4d Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Sat, 4 Oct 2025 00:40:54 +0530 Subject: [PATCH 3/8] Add test case --- .../src/PublicAPI.Unshipped.txt | 1 - .../test/JsonPatchDocumentTest.cs | 67 +++++++++---------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt index 7679b041b315..73f3c1904e71 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt +++ b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt @@ -14,7 +14,6 @@ Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Opera Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.OperationBase() -> void Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.OperationType.get -> Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType -Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.ShouldSerializeFrom() -> bool Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType.Add = 0 -> Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType.Copy = 4 -> Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType diff --git a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs index 9e7c6073e036..230b8aa56c1a 100644 --- a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs +++ b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Converters; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations; using Xunit; +using Xunit.Abstractions; namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson; @@ -20,14 +22,11 @@ public void InvalidPathAtBeginningShouldThrowException() var patchDocument = new JsonPatchDocument(); // Act - var exception = Assert.Throws(() => - { - patchDocument.Add("//NewInt", 1); - }); + var exception = Assert.Throws(() => { patchDocument.Add("//NewInt", 1); }); // Assert Assert.Equal( - "The provided string '//NewInt' is an invalid path.", + "The provided string '//NewInt' is an invalid path.", exception.Message); } @@ -38,14 +37,11 @@ public void InvalidPathAtEndShouldThrowException() var patchDocument = new JsonPatchDocument(); // Act - var exception = Assert.Throws(() => - { - patchDocument.Add("NewInt//", 1); - }); + var exception = Assert.Throws(() => { patchDocument.Add("NewInt//", 1); }); // Assert Assert.Equal( - "The provided string 'NewInt//' is an invalid path.", + "The provided string 'NewInt//' is an invalid path.", exception.Message); } @@ -53,11 +49,7 @@ public void InvalidPathAtEndShouldThrowException() public void NonGenericPatchDocToGenericMustSerialize() { // Arrange - var targetObject = new SimpleObject() - { - StringProperty = "A", - AnotherStringProperty = "B" - }; + var targetObject = new SimpleObject() { StringProperty = "A", AnotherStringProperty = "B" }; var patchDocument = new JsonPatchDocument(); patchDocument.Copy("StringProperty", "AnotherStringProperty"); @@ -111,11 +103,7 @@ public void ListWithGenericTypeWorkForSpecificChildren() public void GenericPatchDocToNonGenericMustSerialize() { // Arrange - var targetObject = new SimpleObject() - { - StringProperty = "A", - AnotherStringProperty = "B" - }; + var targetObject = new SimpleObject() { StringProperty = "A", AnotherStringProperty = "B" }; var patchDocTyped = new JsonPatchDocument(); patchDocTyped.Copy(o => o.StringProperty, o => o.AnotherStringProperty); @@ -200,15 +188,32 @@ var deserialized Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message); } + [Fact] + public void Serialization_ShouldExcludeFrom_WhenNullAndNotMoveOrCopy() + { + // Arrange + JsonPatchDocument patchDocument = new(); + patchDocument.Add("/a/b/c", "foo"); + patchDocument.Remove("/x/y/z"); + patchDocument.Replace("/d/e", "bar"); + patchDocument.Test("/f/e", "t1"); + + var json = JsonSerializer.Serialize(patchDocument); + Console.WriteLine(json); + + // Assert + var expectedJson = """ + [{"value":"foo","path":"/a/b/c","op":"add"},{"value":null,"path":"/x/y/z","op":"remove"}, + { "value":"bar","path":"/d/e","op":"replace"},{ "value":"t1","path":"/f/e","op":"test"}] + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expectedJson), JsonNode.Parse(json))); + } + [Fact] public void Deserialization_RespectsNamingPolicy() { // Arrange - var childToAdd = new SimpleObject - { - GuidValue = Guid.NewGuid(), - StringProperty = "some test data" - }; + var childToAdd = new SimpleObject { GuidValue = Guid.NewGuid(), StringProperty = "some test data" }; var options = new JsonSerializerOptions(JsonSerializerDefaults.Web); options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; @@ -228,10 +233,7 @@ public void Deserialization_RespectsNamingPolicy() docSuccess.ApplyTo(getTestObject()); // The following call should fail - Assert.Throws(() => - { - docFail.ApplyTo(getTestObject()); - }); + Assert.Throws(() => { docFail.ApplyTo(getTestObject()); }); } private static JsonPatchDocument DeserializePatchDocumentWithNamingPolicy(string json, JsonNamingPolicy policy) @@ -245,12 +247,7 @@ private static JsonPatchDocument DeserializePatchDocumentWithNamin private string GeneratePatchDocumentJson(SimpleObject toAdd, JsonSerializerOptions jsonSerializerOptions) { var document = new JsonPatchDocument(); - var operation = new Operation - { - op = "add", - path = "/simpleObjectList/-", - value = toAdd - }; + var operation = new Operation { op = "add", path = "/simpleObjectList/-", value = toAdd }; document.Operations.Add(operation); return JsonSerializer.Serialize>(document, jsonSerializerOptions); From dd1eebe017b2e2e385c2ced57078cc4631fcff50 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Fri, 3 Oct 2025 18:28:13 +0530 Subject: [PATCH 4/8] mark from as nullable --- .../JsonPatch.SystemTextJson/src/Operations/OperationBase.cs | 4 +++- .../JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs index adfa72767aa0..54145253e1c4 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs @@ -44,7 +44,9 @@ public string op } [JsonPropertyName(nameof(from))] - public string from { get; set; } + #nullable enable + public string? from { get; set; } + #nullable restore public OperationBase() { diff --git a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt index e2421fc8af3c..7679b041b315 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt +++ b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt @@ -97,8 +97,8 @@ Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType.Test = 5 ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Apply(TModel objectToApplyTo, Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters.IObjectAdapter adapter) -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Operation(string op, string path, string from) -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Operation(string op, string path, string from, object value) -> void -~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.get -> string -~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.set -> void +Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.get -> string? +Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.from.set -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.op.get -> string ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.op.set -> void ~Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.OperationBase(string op, string path, string from) -> void From fa9b64261037aecbb1511582ccd8800455322b16 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Fri, 3 Oct 2025 23:12:54 +0530 Subject: [PATCH 5/8] Add ignore null --- .../src/Operations/OperationBase.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs index 54145253e1c4..622d94a63e16 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/Operations/OperationBase.cs @@ -43,10 +43,10 @@ public string op } } - [JsonPropertyName(nameof(from))] - #nullable enable +#nullable enable + [JsonPropertyName(nameof(from)), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? from { get; set; } - #nullable restore +#nullable restore public OperationBase() { @@ -61,10 +61,4 @@ public OperationBase(string op, string path, string from) this.path = path; this.from = from; } - - public bool ShouldSerializeFrom() - { - return (OperationType == OperationType.Move - || OperationType == OperationType.Copy); - } } From b01959512379f25e5f8fabc1dbb797f82ca89027 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Sat, 4 Oct 2025 00:40:54 +0530 Subject: [PATCH 6/8] Add test case --- .../src/PublicAPI.Unshipped.txt | 1 - .../test/JsonPatchDocumentTest.cs | 67 +++++++++---------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt index 7679b041b315..73f3c1904e71 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt +++ b/src/Features/JsonPatch.SystemTextJson/src/PublicAPI.Unshipped.txt @@ -14,7 +14,6 @@ Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.Operation.Opera Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.OperationBase() -> void Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.OperationType.get -> Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType -Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationBase.ShouldSerializeFrom() -> bool Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType.Add = 0 -> Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType.Copy = 4 -> Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations.OperationType diff --git a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs index 9e7c6073e036..230b8aa56c1a 100644 --- a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs +++ b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Text.Json; +using System.Text.Json.Nodes; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Converters; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations; using Xunit; +using Xunit.Abstractions; namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson; @@ -20,14 +22,11 @@ public void InvalidPathAtBeginningShouldThrowException() var patchDocument = new JsonPatchDocument(); // Act - var exception = Assert.Throws(() => - { - patchDocument.Add("//NewInt", 1); - }); + var exception = Assert.Throws(() => { patchDocument.Add("//NewInt", 1); }); // Assert Assert.Equal( - "The provided string '//NewInt' is an invalid path.", + "The provided string '//NewInt' is an invalid path.", exception.Message); } @@ -38,14 +37,11 @@ public void InvalidPathAtEndShouldThrowException() var patchDocument = new JsonPatchDocument(); // Act - var exception = Assert.Throws(() => - { - patchDocument.Add("NewInt//", 1); - }); + var exception = Assert.Throws(() => { patchDocument.Add("NewInt//", 1); }); // Assert Assert.Equal( - "The provided string 'NewInt//' is an invalid path.", + "The provided string 'NewInt//' is an invalid path.", exception.Message); } @@ -53,11 +49,7 @@ public void InvalidPathAtEndShouldThrowException() public void NonGenericPatchDocToGenericMustSerialize() { // Arrange - var targetObject = new SimpleObject() - { - StringProperty = "A", - AnotherStringProperty = "B" - }; + var targetObject = new SimpleObject() { StringProperty = "A", AnotherStringProperty = "B" }; var patchDocument = new JsonPatchDocument(); patchDocument.Copy("StringProperty", "AnotherStringProperty"); @@ -111,11 +103,7 @@ public void ListWithGenericTypeWorkForSpecificChildren() public void GenericPatchDocToNonGenericMustSerialize() { // Arrange - var targetObject = new SimpleObject() - { - StringProperty = "A", - AnotherStringProperty = "B" - }; + var targetObject = new SimpleObject() { StringProperty = "A", AnotherStringProperty = "B" }; var patchDocTyped = new JsonPatchDocument(); patchDocTyped.Copy(o => o.StringProperty, o => o.AnotherStringProperty); @@ -200,15 +188,32 @@ var deserialized Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message); } + [Fact] + public void Serialization_ShouldExcludeFrom_WhenNullAndNotMoveOrCopy() + { + // Arrange + JsonPatchDocument patchDocument = new(); + patchDocument.Add("/a/b/c", "foo"); + patchDocument.Remove("/x/y/z"); + patchDocument.Replace("/d/e", "bar"); + patchDocument.Test("/f/e", "t1"); + + var json = JsonSerializer.Serialize(patchDocument); + Console.WriteLine(json); + + // Assert + var expectedJson = """ + [{"value":"foo","path":"/a/b/c","op":"add"},{"value":null,"path":"/x/y/z","op":"remove"}, + { "value":"bar","path":"/d/e","op":"replace"},{ "value":"t1","path":"/f/e","op":"test"}] + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expectedJson), JsonNode.Parse(json))); + } + [Fact] public void Deserialization_RespectsNamingPolicy() { // Arrange - var childToAdd = new SimpleObject - { - GuidValue = Guid.NewGuid(), - StringProperty = "some test data" - }; + var childToAdd = new SimpleObject { GuidValue = Guid.NewGuid(), StringProperty = "some test data" }; var options = new JsonSerializerOptions(JsonSerializerDefaults.Web); options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; @@ -228,10 +233,7 @@ public void Deserialization_RespectsNamingPolicy() docSuccess.ApplyTo(getTestObject()); // The following call should fail - Assert.Throws(() => - { - docFail.ApplyTo(getTestObject()); - }); + Assert.Throws(() => { docFail.ApplyTo(getTestObject()); }); } private static JsonPatchDocument DeserializePatchDocumentWithNamingPolicy(string json, JsonNamingPolicy policy) @@ -245,12 +247,7 @@ private static JsonPatchDocument DeserializePatchDocumentWithNamin private string GeneratePatchDocumentJson(SimpleObject toAdd, JsonSerializerOptions jsonSerializerOptions) { var document = new JsonPatchDocument(); - var operation = new Operation - { - op = "add", - path = "/simpleObjectList/-", - value = toAdd - }; + var operation = new Operation { op = "add", path = "/simpleObjectList/-", value = toAdd }; document.Operations.Add(operation); return JsonSerializer.Serialize>(document, jsonSerializerOptions); From a14fb43c23cf0a902d1e54b286c8c6441d15047d Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Tue, 14 Oct 2025 21:38:41 +0530 Subject: [PATCH 7/8] Code cleanup --- .../test/JsonPatchDocumentTest.cs | 88 ++++++++++++------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs index 230b8aa56c1a..05f8ad6ce0b8 100644 --- a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs +++ b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations; using Xunit; -using Xunit.Abstractions; namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson; @@ -22,11 +21,14 @@ public void InvalidPathAtBeginningShouldThrowException() var patchDocument = new JsonPatchDocument(); // Act - var exception = Assert.Throws(() => { patchDocument.Add("//NewInt", 1); }); + var exception = Assert.Throws(() => + { + patchDocument.Add("//NewInt", 1); + }); // Assert Assert.Equal( - "The provided string '//NewInt' is an invalid path.", + "The provided string '//NewInt' is an invalid path.", exception.Message); } @@ -37,11 +39,14 @@ public void InvalidPathAtEndShouldThrowException() var patchDocument = new JsonPatchDocument(); // Act - var exception = Assert.Throws(() => { patchDocument.Add("NewInt//", 1); }); + var exception = Assert.Throws(() => + { + patchDocument.Add("NewInt//", 1); + }); // Assert Assert.Equal( - "The provided string 'NewInt//' is an invalid path.", + "The provided string 'NewInt//' is an invalid path.", exception.Message); } @@ -49,7 +54,11 @@ public void InvalidPathAtEndShouldThrowException() public void NonGenericPatchDocToGenericMustSerialize() { // Arrange - var targetObject = new SimpleObject() { StringProperty = "A", AnotherStringProperty = "B" }; + var targetObject = new SimpleObject() + { + StringProperty = "A", + AnotherStringProperty = "B" + }; var patchDocument = new JsonPatchDocument(); patchDocument.Copy("StringProperty", "AnotherStringProperty"); @@ -103,7 +112,11 @@ public void ListWithGenericTypeWorkForSpecificChildren() public void GenericPatchDocToNonGenericMustSerialize() { // Arrange - var targetObject = new SimpleObject() { StringProperty = "A", AnotherStringProperty = "B" }; + var targetObject = new SimpleObject() + { + StringProperty = "A", + AnotherStringProperty = "B" + }; var patchDocTyped = new JsonPatchDocument(); patchDocTyped.Copy(o => o.StringProperty, o => o.AnotherStringProperty); @@ -188,32 +201,15 @@ var deserialized Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message); } - [Fact] - public void Serialization_ShouldExcludeFrom_WhenNullAndNotMoveOrCopy() - { - // Arrange - JsonPatchDocument patchDocument = new(); - patchDocument.Add("/a/b/c", "foo"); - patchDocument.Remove("/x/y/z"); - patchDocument.Replace("/d/e", "bar"); - patchDocument.Test("/f/e", "t1"); - - var json = JsonSerializer.Serialize(patchDocument); - Console.WriteLine(json); - - // Assert - var expectedJson = """ - [{"value":"foo","path":"/a/b/c","op":"add"},{"value":null,"path":"/x/y/z","op":"remove"}, - { "value":"bar","path":"/d/e","op":"replace"},{ "value":"t1","path":"/f/e","op":"test"}] - """; - Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expectedJson), JsonNode.Parse(json))); - } - [Fact] public void Deserialization_RespectsNamingPolicy() { // Arrange - var childToAdd = new SimpleObject { GuidValue = Guid.NewGuid(), StringProperty = "some test data" }; + var childToAdd = new SimpleObject + { + GuidValue = Guid.NewGuid(), + StringProperty = "some test data" + }; var options = new JsonSerializerOptions(JsonSerializerDefaults.Web); options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; @@ -233,7 +229,10 @@ public void Deserialization_RespectsNamingPolicy() docSuccess.ApplyTo(getTestObject()); // The following call should fail - Assert.Throws(() => { docFail.ApplyTo(getTestObject()); }); + Assert.Throws(() => + { + docFail.ApplyTo(getTestObject()); + }); } private static JsonPatchDocument DeserializePatchDocumentWithNamingPolicy(string json, JsonNamingPolicy policy) @@ -247,9 +246,34 @@ private static JsonPatchDocument DeserializePatchDocumentWithNamin private string GeneratePatchDocumentJson(SimpleObject toAdd, JsonSerializerOptions jsonSerializerOptions) { var document = new JsonPatchDocument(); - var operation = new Operation { op = "add", path = "/simpleObjectList/-", value = toAdd }; + var operation = new Operation + { + op = "add", + path = "/simpleObjectList/-", + value = toAdd + }; document.Operations.Add(operation); return JsonSerializer.Serialize>(document, jsonSerializerOptions); } -} + + [Fact] + public void Serialization_ShouldExcludeFrom_WhenNullAndNotMoveOrCopy() + { + // Arrange + JsonPatchDocument patchDocument = new(); + patchDocument.Add("/a/b/c", "foo"); + patchDocument.Remove("/x/y/z"); + patchDocument.Replace("/d/e", "bar"); + patchDocument.Test("/f/e", "t1"); + + var json = JsonSerializer.Serialize(patchDocument); + + // Assert + var expectedJson = """ + [{"value":"foo","path":"/a/b/c","op":"add"},{"value":null,"path":"/x/y/z","op":"remove"}, + { "value":"bar","path":"/d/e","op":"replace"},{ "value":"t1","path":"/f/e","op":"test"}] + """; + Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expectedJson), JsonNode.Parse(json))); + } +} \ No newline at end of file From 5af37c0e8d89ff1cf1223f5a5c2ad419f2d43900 Mon Sep 17 00:00:00 2001 From: Akshay Kumar Date: Tue, 14 Oct 2025 21:40:02 +0530 Subject: [PATCH 8/8] Code cleanup --- .../JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs index 05f8ad6ce0b8..2f91bed85eaf 100644 --- a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs +++ b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchDocumentTest.cs @@ -274,6 +274,8 @@ public void Serialization_ShouldExcludeFrom_WhenNullAndNotMoveOrCopy() [{"value":"foo","path":"/a/b/c","op":"add"},{"value":null,"path":"/x/y/z","op":"remove"}, { "value":"bar","path":"/d/e","op":"replace"},{ "value":"t1","path":"/f/e","op":"test"}] """; + + // Act Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expectedJson), JsonNode.Parse(json))); } } \ No newline at end of file