Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
d24d871
fix: Add logging for serialization in ArrayReflectionConverter and en…
IvanMurzak Jan 5, 2026
990f442
fix: Refactor JSON serialization to improve logging and handle custom…
IvanMurzak Jan 5, 2026
18538a9
Merge branch 'main' into update/serialization-deserialization
IvanMurzak Jan 5, 2026
a9c029b
Merge branch 'main' into update/serialization-deserialization
IvanMurzak Jan 5, 2026
f34c9fe
refactor: Enhance serialization logic by removing unused custom JsonC…
IvanMurzak Jan 5, 2026
0658c77
refactor: Update serialization and deserialization methods for improv…
IvanMurzak Jan 5, 2026
e09682f
Merge branch 'main' into update/serialization-deserialization
IvanMurzak Jan 5, 2026
4d7cf34
Merge branch 'main' into update/serialization-deserialization
IvanMurzak Jan 5, 2026
ab6d471
refactor: Rename parameter 'type' to 'fallbackType' in Serialize meth…
IvanMurzak Jan 5, 2026
c1cb3eb
feat: Enhance JSON schema support in BigIntegerJsonConverter and Vers…
IvanMurzak Jan 5, 2026
9a0ade6
fix: Add null-forgiving operator to JSON parsing in schema tests
IvanMurzak Jan 5, 2026
6c64137
Merge branch 'main' into update/serialization-deserialization
IvanMurzak Jan 5, 2026
cead477
fix: Simplify null checks in ArrayReflectionConverter and SerializedM…
IvanMurzak Jan 5, 2026
b899216
refactor: Replace GetConverters method with GetJsonConverter for impr…
IvanMurzak Jan 5, 2026
efaa6fd
refactor: Update serialization methods to improve field and property …
IvanMurzak Jan 5, 2026
4e936e7
Apply suggestions from code review
IvanMurzak Jan 5, 2026
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 @@ -30,13 +30,15 @@ void ActAssertPopulate(object? original, Type? fallbackType = null)
var targetObject = reflector.CreateInstance(type);

var populateLogger = new StringBuilderLogger();
var populateOutput = reflector.TryPopulate(
var success = reflector.TryPopulate(
obj: ref targetObject,
data: serialized,
fallbackObjType: type,
logs: new Logs(),
logger: populateLogger);

Assert.True(success);

_output.WriteLine($"Population:\n{populateLogger}");

// Assert
Expand All @@ -53,6 +55,8 @@ void ActAssertPopulate(object? original, Type? fallbackType = null)
_output.WriteLine($"Serialized JSON:\n{serialized.ToJson(reflector)}\n");

Assert.Equal(originalJson, populatedJson);

_output.WriteLine($"============================\n");
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ private void ValidateBasicSchemaConformance(JsonNode schema, object serialized,

// Convert serialized object to JSON and extract the "value" field
var json = serialized.ToJson(reflector);
var jsonNode = JsonNode.Parse(json);
var jsonNode = JsonNode.Parse(json!);

// SerializedMember has structure: { "name": "...", "typeName": "...", "value": ... }
// We need to extract just the "value" field to compare against the schema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void SerializedMember_Should_Serialize_PrimitiveTypes_Correctly(object va
var json = serialized.ToJson(reflector);

// Parse and validate JSON structure
var jsonNode = JsonNode.Parse(json);
var jsonNode = JsonNode.Parse(json!);
Assert.NotNull(jsonNode);

var jsonObj = jsonNode!.AsObject();
Expand All @@ -85,7 +85,7 @@ public void SerializedMember_Should_Serialize_Null_Correctly()
var json = serialized.ToJson(reflector);

// Parse and validate JSON structure
var jsonNode = JsonNode.Parse(json);
var jsonNode = JsonNode.Parse(json!);
Assert.NotNull(jsonNode);

var jsonObj = jsonNode!.AsObject();
Expand All @@ -111,7 +111,7 @@ public void SerializedMember_Should_Serialize_Array_Correctly()
var json = serialized.ToJson(reflector);

// Parse and validate JSON structure
var jsonNode = JsonNode.Parse(json);
var jsonNode = JsonNode.Parse(json!);
Assert.NotNull(jsonNode);

var jsonObj = jsonNode!.AsObject();
Expand All @@ -136,7 +136,7 @@ public void SerializedMember_Should_Serialize_Object_Correctly()
var json = serialized.ToJson(reflector);

// Parse and validate JSON structure
var jsonNode = JsonNode.Parse(json);
var jsonNode = JsonNode.Parse(json!);
Assert.NotNull(jsonNode);

var jsonObj = jsonNode!.AsObject();
Expand Down
2 changes: 1 addition & 1 deletion ReflectorNet/src/Converter/IReflectionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ bool TryPopulate(
Reflector reflector,
ref object? obj,
SerializedMember data,
Type? fallbackType = null,
Type type,
int depth = 0,
Logs? logs = null,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Expand Down
2 changes: 1 addition & 1 deletion ReflectorNet/src/Converter/Json/BigIntegerJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class BigIntegerJsonConverter : JsonSchemaConverter<BigInteger>, IJsonSch
public static JsonNode Schema => new JsonObject
{
[JsonSchema.Type] = JsonSchema.String,
["description"] = "A large integer represented as a string"
[JsonSchema.Description] = "A large integer represented as a string"
};

public static JsonNode SchemaRef => new JsonObject
Expand Down
16 changes: 14 additions & 2 deletions ReflectorNet/src/Converter/Json/VersionJsonConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,28 @@

using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Nodes;
using com.IvanMurzak.ReflectorNet.Utils;

namespace com.IvanMurzak.ReflectorNet.Json
{
/// <summary>
/// JsonConverter that handles conversion between JSON strings and System.Version.
/// </summary>
public class VersionJsonConverter : JsonConverter<Version>
public class VersionJsonConverter : JsonSchemaConverter<Version>, IJsonSchemaConverter
{
public static JsonNode Schema => new JsonObject
{
[JsonSchema.Type] = JsonSchema.String,
};
public static JsonNode SchemaRef => new JsonObject
{
[JsonSchema.Ref] = JsonSchema.RefValue + StaticId
};

public override JsonNode GetSchema() => Schema;
public override JsonNode GetSchemaRef() => SchemaRef;

public override Version? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.Null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ protected override SerializedMember InternalSerialize(

if (logger?.IsEnabled(LogLevel.Trace) == true)
logger.LogTrace("{padding} Serializing item '{index}' of type '{type}' in '{objType}'.\nPath: {path}",
StringUtils.GetPadding(depth), index, currentType?.GetTypeId(), obj.GetType().GetTypeId(), context?.GetPath(obj));
StringUtils.GetPadding(depth), index, currentType?.GetTypeId().ValueOrNull(), obj.GetType().GetTypeId().ValueOrNull(), context?.GetPath(obj));

if (thisElementType != null && reflector.Converters.IsTypeBlacklisted(thisElementType))
{
Expand Down Expand Up @@ -119,7 +119,7 @@ protected override SerializedMember InternalSerialize(
// Handle non-recursive serialization
return SerializedMember.FromJson(
type: type,
json: obj.ToJson(reflector),
json: obj.ToJson(reflector, depth: depth, logger: logger),
name: name);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,57 +22,45 @@ public virtual bool TryPopulate(
Reflector reflector,
ref object? obj,
SerializedMember data,
Type? fallbackType = null,
Type type,
int depth = 0,
Logs? logs = null,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
ILogger? logger = null)
{
var padding = StringUtils.GetPadding(depth);

var objType = TypeUtils.GetTypeWithNamePriority(data, fallbackType, out var typeError) ?? obj?.GetType();
if (objType == null)
{
if (logger?.IsEnabled(LogLevel.Error) == true)
logger.LogError($"{padding}Failed to determine type for object '{data.name.ValueOrNull()}'. {typeError}");

if (logs != null)
logs.Error($"Failed to determine type for object '{data.name.ValueOrNull()}'. {typeError}", depth);

return false;
}

if (obj == null)
{
// obj = CreateInstance(reflector, objType);
obj = reflector.Deserialize(
data: data,
fallbackType: objType,
fallbackType: type,
depth: depth,
logs: logs,
logger: logger);

if (obj == null)
{
if (logger?.IsEnabled(LogLevel.Error) == true)
logger.LogError($"{padding}Object '{data.name.ValueOrNull()}' population failed: Object is null. Instance creation failed for type '{objType.GetTypeId()}'.");
logger.LogError($"{padding}Object '{data.name.ValueOrNull()}' population failed: Object is null. Instance creation failed for type '{type.GetTypeId()}'.");

if (logs != null)
logs.Error($"Object '{data.name.ValueOrNull()}' population failed: Object is null. Instance creation failed for type '{objType.GetTypeId()}'.", depth);
logs.Error($"Object '{data.name.ValueOrNull()}' population failed: Object is null. Instance creation failed for type '{type.GetTypeId()}'.", depth);

return false;
}

if (logger?.IsEnabled(LogLevel.Trace) == true)
logger.LogTrace($"{padding}Object '{data.name.ValueOrNull()}' populated with type '{objType.GetTypeId()}'.");
logger.LogTrace($"{padding}Object '{data.name.ValueOrNull()}' populated with type '{type.GetTypeId()}'.");

if (logs != null)
logs.Success($"Object '{data.name.ValueOrNull()}' populated with type '{objType.GetTypeId()}'.", depth);
logs.Success($"Object '{data.name.ValueOrNull()}' populated with type '{type.GetTypeId()}'.", depth);

return true;
}

if (!TypeUtils.IsCastable(obj.GetType(), objType))
if (!TypeUtils.IsCastable(obj.GetType(), type))
{
if (logger?.IsEnabled(LogLevel.Error) == true)
logger.LogError($"{padding}Type mismatch: '{data.typeName}' vs '{obj.GetType().GetTypeId().ValueOrNull()}'.");
Expand All @@ -91,7 +79,7 @@ public virtual bool TryPopulate(
var success = SetValue(
reflector: reflector,
obj: ref obj,
type: objType,
type: type,
value: data.valueJsonElement,
depth: depth,
logs: logs,
Expand Down Expand Up @@ -136,7 +124,7 @@ public virtual bool TryPopulate(
var success = TryPopulateField(
reflector,
obj: ref obj,
objType: objType,
objType: type,
fieldValue: field,
depth: nextDepth,
logs: logs,
Expand Down Expand Up @@ -180,7 +168,7 @@ public virtual bool TryPopulate(
var success = TryPopulateProperty(
reflector,
obj: ref obj,
objType: objType,
objType: type,
propertyValue: property,
depth: nextDepth,
logs: logs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public abstract partial class BaseReflectionConverter<T> : IReflectionConverter
public virtual SerializedMember Serialize(
Reflector reflector,
object? obj,
Type? type = null,
Type? fallbackType = null,
string? name = null,
bool recursive = true,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Expand All @@ -32,10 +32,12 @@ public virtual SerializedMember Serialize(
ILogger? logger = null,
SerializationContext? context = null)
{
var actualType = fallbackType ?? obj?.GetType() ?? typeof(T);

return InternalSerialize(
reflector: reflector,
obj: obj,
type: type ?? obj?.GetType() ?? typeof(T),
type: actualType,
name: name,
recursive: recursive,
flags: flags,
Expand Down Expand Up @@ -63,13 +65,6 @@ public virtual SerializedMember Serialize(

foreach (var field in fields)
{
if (GetIgnoredFields().Contains(field.Name))
{
if (logger?.IsEnabled(LogLevel.Trace) == true)
logger.LogTrace("{padding}Skipping serialization of field '{fieldName}' in '{objType}' because it is ignored.\nPath: {path}",
StringUtils.GetPadding(depth + 1), field.Name, objType.GetTypeId(), context?.GetPath(obj));
continue;
}
if (reflector.Converters.IsTypeBlacklisted(field.FieldType))
{
if (logger?.IsEnabled(LogLevel.Trace) == true)
Expand Down Expand Up @@ -128,24 +123,17 @@ public virtual SerializedMember Serialize(

foreach (var prop in properties)
{
if (GetIgnoredProperties().Contains(prop.Name))
{
if (logger?.IsEnabled(LogLevel.Trace) == true)
logger.LogTrace("{padding} Skipping serialization of property '{propertyName}' in '{objType}' because it is ignored.\nPath: {path}",
StringUtils.GetPadding(depth + 1), prop.Name, objType.GetTypeId(), context?.GetPath(obj));
continue;
}
if (reflector.Converters.IsTypeBlacklisted(prop.PropertyType))
{
if (logger?.IsEnabled(LogLevel.Trace) == true)
logger.LogTrace("{padding} Skipping serialization of property '{propertyName}' of type '{type}' in '{objType}' because its type is blacklisted.\nPath: {path}",
logger.LogTrace("{padding}Skipping serialization of property '{propertyName}' of type '{type}' in '{objType}' because its type is blacklisted.\nPath: {path}",
StringUtils.GetPadding(depth + 1), prop.Name, prop.PropertyType.GetTypeId(), objType.GetTypeId(), context?.GetPath(obj));
continue;
}
try
{
if (logger?.IsEnabled(LogLevel.Trace) == true)
logger.LogTrace("{padding} Serializing property '{propertyName}' of type '{type}' in '{objType}'.\nPath: {path}",
logger.LogTrace("{padding}Serializing property '{propertyName}' of type '{type}' in '{objType}'.\nPath: {path}",
StringUtils.GetPadding(depth + 1), prop.Name, prop.PropertyType.GetTypeId(), objType.GetTypeId(), context?.GetPath(obj));

var value = prop.GetValue(obj);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,22 @@ public virtual int SerializationPriority(Type type, ILogger? logger = null)
/// Default implementation returns public fields that are not marked with [Obsolete] or [NonSerialized].
/// Derived classes can override to customize field selection.
/// </summary>
public virtual IEnumerable<FieldInfo>? GetSerializableFields(
public IEnumerable<FieldInfo>? GetSerializableFields(
Reflector reflector,
Type objType,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
ILogger? logger = null)
{
return GetSerializableFieldsInternal(reflector, objType, flags, logger)
?.Where(field => !GetIgnoredFields().Contains(field.Name));
}

/// <summary>
/// Gets the serializable fields for the specified type.
/// Default implementation returns public fields that are not marked with [Obsolete] or [NonSerialized].
/// Derived classes can override to customize field selection.
/// </summary>
protected virtual IEnumerable<FieldInfo>? GetSerializableFieldsInternal(
Reflector reflector,
Type objType,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Expand All @@ -120,7 +135,22 @@ public virtual int SerializationPriority(Type type, ILogger? logger = null)
/// Default implementation returns readable properties that are not marked with [Obsolete].
/// Derived classes can override to customize property selection.
/// </summary>
public virtual IEnumerable<PropertyInfo>? GetSerializableProperties(
public IEnumerable<PropertyInfo>? GetSerializableProperties(
Reflector reflector,
Type objType,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
ILogger? logger = null)
{
return GetSerializablePropertiesInternal(reflector, objType, flags, logger)
?.Where(prop => !GetIgnoredProperties().Contains(prop.Name));
}

/// <summary>
/// Gets the serializable properties for the specified type.
/// Default implementation returns readable properties that are not marked with [Obsolete].
/// Derived classes can override to customize property selection.
/// </summary>
protected virtual IEnumerable<PropertyInfo>? GetSerializablePropertiesInternal(
Reflector reflector,
Type objType,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ protected override SerializedMember InternalSerialize(
valueJsonElement = new JsonObject().ToJsonElement()
};
}
return SerializedMember.FromJson(type, obj.ToJson(reflector, logger: logger), name: name);
return SerializedMember.FromJson(type, obj.ToJson(reflector, depth: depth, logger: logger), name: name);
}
throw new ArgumentException($"Unsupported type: '{type.GetTypeId()}' for converter '{GetType().GetTypeShortName()}'.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,26 @@ public IgnoreFieldsAndPropertiesReflectionConverter(bool ignoreFields, bool igno
/// </summary>
public override bool AllowCascadeSerialization => false;

public override IEnumerable<FieldInfo>? GetSerializableFields(
protected override IEnumerable<FieldInfo>? GetSerializableFieldsInternal(
Reflector reflector,
Type objType,
BindingFlags flags,
ILogger? logger = null)
{
return _ignoreFields
? null
: base.GetSerializableFields(reflector, objType, flags, logger);
: base.GetSerializableFieldsInternal(reflector, objType, flags, logger);
}

public override IEnumerable<PropertyInfo>? GetSerializableProperties(
protected override IEnumerable<PropertyInfo>? GetSerializablePropertiesInternal(
Reflector reflector,
Type objType,
BindingFlags flags,
ILogger? logger = null)
{
return _ignoreProperties
? null
: base.GetSerializableProperties(reflector, objType, flags, logger);
: base.GetSerializablePropertiesInternal(reflector, objType, flags, logger);
}
}
}
Loading