Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,18 @@ namespace Orc.Serialization.Json
{
object? Deserialize(System.IO.Stream stream, System.Type targetType);
T? Deserialize<T>(System.IO.Stream stream);
void PopulateObject(System.IO.Stream stream, object target);
void Serialize(System.IO.Stream stream, object obj);
void Serialize<T>(System.IO.Stream stream, T obj);
}
public static class IJsonSerializerExtensions
{
public static T? Deserialize<T>(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, System.IO.Stream stream) { }
public static T? DeserializeFromString<T>(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, string value) { }
public static void PopulateObject<T>(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, System.IO.Stream stream, T target)
where T : class { }
public static void PopulateObjectFromString<T>(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, string value, T target)
where T : class { }
public static string SerializeToString<T>(this Orc.Serialization.Json.IJsonSerializer jsonSerializer, T instance) { }
}
public interface IJsonSerializerFactory
Expand All @@ -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<T>(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<T>(System.IO.Stream stream, T obj) { }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot object initializers should be separated on new lines for readability

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));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,33 @@ public static string SerializeToString<T>(this IJsonSerializer jsonSerializer, T
jsonSerializer.Serialize(stream, instance!);
return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length);
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="T">The type of the object to populate.</typeparam>
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="stream">The stream containing the JSON data.</param>
/// <param name="target">The existing object whose properties will be updated.</param>
public static void PopulateObject<T>(this IJsonSerializer jsonSerializer, Stream stream, T target)
where T : class
{
jsonSerializer.PopulateObject(stream, (object)target);
}

/// <summary>
/// Populates the properties of an existing object from a JSON string.
/// Only properties present in the JSON are updated; all other properties remain unchanged.
/// </summary>
/// <typeparam name="T">The type of the object to populate.</typeparam>
/// <param name="jsonSerializer">The JSON serializer.</param>
/// <param name="value">The JSON string containing the properties to update.</param>
/// <param name="target">The existing object whose properties will be updated.</param>
public static void PopulateObjectFromString<T>(this IJsonSerializer jsonSerializer, string value, T target)
where T : class
{
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(value));
jsonSerializer.PopulateObject(stream, (object)target);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface IJsonSerializer
void Serialize(Stream stream, object obj);

void Serialize<T>(Stream stream, T obj);

void PopulateObject(Stream stream, object target);
}
32 changes: 32 additions & 0 deletions src/Orc.Serialization.Json/Services/JsonSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -62,4 +63,35 @@ public void Serialize<T>(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);
}
}
}
Loading