From 255226e1c56164573cd58b8ac3f9b5ad79461ca2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 May 2026 08:57:42 +0000 Subject: [PATCH 1/2] Initial plan From fba69a4c0814664404498519823d2b503ac688d7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 30 May 2026 09:05:29 +0000 Subject: [PATCH 2/2] Implement PopulateObject extension method for JsonSerializer --- ...on_HasNoBreakingChanges_Async.verified.txt | 6 ++ .../IJsonSerializerExtensionsFacts.cs | 90 +++++++++++++++++++ .../Extensions/IJsonSerializerExtensions.cs | 29 ++++++ .../Services/Interfaces/IJsonSerializer.cs | 2 + .../Services/JsonSerializer.cs | 32 +++++++ 5 files changed, 159 insertions(+) diff --git a/src/Orc.Serialization.Json.Tests/PublicApiFacts.Orc_Serialization_Json_HasNoBreakingChanges_Async.verified.txt b/src/Orc.Serialization.Json.Tests/PublicApiFacts.Orc_Serialization_Json_HasNoBreakingChanges_Async.verified.txt index 7a96173..ac478fa 100644 --- a/src/Orc.Serialization.Json.Tests/PublicApiFacts.Orc_Serialization_Json_HasNoBreakingChanges_Async.verified.txt +++ b/src/Orc.Serialization.Json.Tests/PublicApiFacts.Orc_Serialization_Json_HasNoBreakingChanges_Async.verified.txt @@ -23,6 +23,7 @@ namespace Orc.Serialization.Json { object? Deserialize(System.IO.Stream stream, System.Type targetType); T? Deserialize(System.IO.Stream stream); + void PopulateObject(System.IO.Stream stream, object target); void Serialize(System.IO.Stream stream, object obj); void Serialize(System.IO.Stream stream, T obj); } @@ -30,6 +31,10 @@ namespace Orc.Serialization.Json { public static T? Deserialize(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, System.IO.Stream stream) { } public static T? DeserializeFromString(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, string value) { } + public static void PopulateObject(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, System.IO.Stream stream, T target) + where T : class { } + public static void PopulateObjectFromString(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, string value, T target) + where T : class { } public static string SerializeToString(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, T instance) { } } public interface IJsonSerializerFactory @@ -46,6 +51,7 @@ namespace Orc.Serialization.Json public JsonSerializer(Orc.Serialization.Json.JsonSerializerSettings settings) { } public object? Deserialize(System.IO.Stream stream, System.Type targetType) { } public T? Deserialize(System.IO.Stream stream) { } + public void PopulateObject(System.IO.Stream stream, object target) { } public void Serialize(System.IO.Stream stream, object obj) { } public void Serialize(System.IO.Stream stream, T obj) { } } diff --git a/src/Orc.Serialization.Json.Tests/Services/IJsonSerializerExtensionsFacts.cs b/src/Orc.Serialization.Json.Tests/Services/IJsonSerializerExtensionsFacts.cs index e0d9980..984a48e 100644 --- a/src/Orc.Serialization.Json.Tests/Services/IJsonSerializerExtensionsFacts.cs +++ b/src/Orc.Serialization.Json.Tests/Services/IJsonSerializerExtensionsFacts.cs @@ -126,4 +126,94 @@ public void Deserializes_Complex_Nested_Object_From_Json_String() Assert.That(secondItem.Tags![0].Priority, Is.EqualTo(4)); } } + + [TestFixture] + public class The_PopulateObjectFromString_Method + { + [Test] + public void Updates_Only_Properties_Present_In_Json() + { + var serializer = CreateSerializer(); + var model = new SampleModel { Name = "John", Value = 10, Status = Status.Active }; + var json = "{\"Name\":\"Jane\"}"; + + serializer.PopulateObjectFromString(json, model); + + Assert.That(model.Name, Is.EqualTo("Jane")); + Assert.That(model.Value, Is.EqualTo(10)); + Assert.That(model.Status, Is.EqualTo(Status.Active)); + } + + [Test] + public void Updates_Multiple_Properties_From_Json() + { + var serializer = CreateSerializer(); + var model = new SampleModel { Name = "John", Value = 10, Status = Status.Active }; + var json = "{\"Name\":\"Jane\",\"Value\":99}"; + + serializer.PopulateObjectFromString(json, model); + + Assert.That(model.Name, Is.EqualTo("Jane")); + Assert.That(model.Value, Is.EqualTo(99)); + Assert.That(model.Status, Is.EqualTo(Status.Active)); + } + + [Test] + public void Leaves_All_Properties_Intact_When_Json_Is_Empty_Object() + { + var serializer = CreateSerializer(); + var model = new SampleModel { Name = "John", Value = 42, Status = Status.Pending }; + var json = "{}"; + + serializer.PopulateObjectFromString(json, model); + + Assert.That(model.Name, Is.EqualTo("John")); + Assert.That(model.Value, Is.EqualTo(42)); + Assert.That(model.Status, Is.EqualTo(Status.Pending)); + } + + [Test] + public void Is_Case_Insensitive_For_Property_Names() + { + var serializer = CreateSerializer(); + var model = new SampleModel { Name = "John", Value = 5, Status = Status.Active }; + var json = "{\"name\":\"Doe\"}"; + + serializer.PopulateObjectFromString(json, model); + + Assert.That(model.Name, Is.EqualTo("Doe")); + Assert.That(model.Value, Is.EqualTo(5)); + } + + [Test] + public void Matches_Issue_Example_Scenario() + { + var serializer = CreateSerializer(); + var model = new SampleModel { Name = "John", Value = 1 }; + var json = "{\"Value\":2}"; + + serializer.PopulateObjectFromString(json, model); + + Assert.That(model.Name, Is.EqualTo("John")); + Assert.That(model.Value, Is.EqualTo(2)); + } + } + + [TestFixture] + public class The_PopulateObject_Method + { + [Test] + public void Updates_Only_Properties_Present_In_Stream() + { + var serializer = CreateSerializer(); + var model = new SampleModel { Name = "John", Value = 10, Status = Status.Active }; + + using var stream = ToStream("{\"Value\":55}"); + serializer.PopulateObject(stream, model); + + Assert.That(model.Name, Is.EqualTo("John")); + Assert.That(model.Value, Is.EqualTo(55)); + Assert.That(model.Status, Is.EqualTo(Status.Active)); + } + } } diff --git a/src/Orc.Serialization.Json/Services/Extensions/IJsonSerializerExtensions.cs b/src/Orc.Serialization.Json/Services/Extensions/IJsonSerializerExtensions.cs index 60f1f21..a2defcc 100644 --- a/src/Orc.Serialization.Json/Services/Extensions/IJsonSerializerExtensions.cs +++ b/src/Orc.Serialization.Json/Services/Extensions/IJsonSerializerExtensions.cs @@ -43,4 +43,33 @@ public static string SerializeToString(this IJsonSerializer jsonSerializer, T jsonSerializer.Serialize(stream, instance!); return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length); } + + /// + /// Populates the properties of an existing object from the specified stream containing JSON data. + /// Only properties present in the JSON are updated; all other properties remain unchanged. + /// + /// The type of the object to populate. + /// The JSON serializer. + /// The stream containing the JSON data. + /// The existing object whose properties will be updated. + public static void PopulateObject(this IJsonSerializer jsonSerializer, Stream stream, T target) + where T : class + { + jsonSerializer.PopulateObject(stream, (object)target); + } + + /// + /// Populates the properties of an existing object from a JSON string. + /// Only properties present in the JSON are updated; all other properties remain unchanged. + /// + /// The type of the object to populate. + /// The JSON serializer. + /// The JSON string containing the properties to update. + /// The existing object whose properties will be updated. + public static void PopulateObjectFromString(this IJsonSerializer jsonSerializer, string value, T target) + where T : class + { + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(value)); + jsonSerializer.PopulateObject(stream, (object)target); + } } diff --git a/src/Orc.Serialization.Json/Services/Interfaces/IJsonSerializer.cs b/src/Orc.Serialization.Json/Services/Interfaces/IJsonSerializer.cs index cca514e..7f6d486 100644 --- a/src/Orc.Serialization.Json/Services/Interfaces/IJsonSerializer.cs +++ b/src/Orc.Serialization.Json/Services/Interfaces/IJsonSerializer.cs @@ -12,4 +12,6 @@ public interface IJsonSerializer void Serialize(Stream stream, object obj); void Serialize(Stream stream, T obj); + + void PopulateObject(Stream stream, object target); } diff --git a/src/Orc.Serialization.Json/Services/JsonSerializer.cs b/src/Orc.Serialization.Json/Services/JsonSerializer.cs index d74e98a..c59f68c 100644 --- a/src/Orc.Serialization.Json/Services/JsonSerializer.cs +++ b/src/Orc.Serialization.Json/Services/JsonSerializer.cs @@ -2,6 +2,7 @@ using System; using System.IO; +using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; @@ -62,4 +63,35 @@ public void Serialize(Stream stream, T obj) { System.Text.Json.JsonSerializer.Serialize(stream, obj, _options); } + + public void PopulateObject(Stream stream, object target) + { + ArgumentNullException.ThrowIfNull(stream); + ArgumentNullException.ThrowIfNull(target); + + using var document = JsonDocument.Parse(stream); + var root = document.RootElement; + + if (root.ValueKind != JsonValueKind.Object) + { + return; + } + + var type = target.GetType(); + using var enumerator = root.EnumerateObject(); + while (enumerator.MoveNext()) + { + var jsonProperty = enumerator.Current; + var property = type.GetProperty(jsonProperty.Name, + BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + + if (property is null || !property.CanWrite) + { + continue; + } + + var value = jsonProperty.Value.Deserialize(property.PropertyType, _options); + property.SetValue(target, value); + } + } }