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 @@ -166,28 +166,74 @@ public void Serialize_DelegatesToBackingConverter()
var serialized = reflector.Serialize(obj);

// Assert
Assert.True(mockConverter.WasCalled);
Assert.Equal("{}", serialized.valueJsonElement?.ToString());
Assert.True(mockConverter.MetadataWasAccessed, "Backing converter metadata should be accessed.");

// "Name" should be present as normal serialization happens using the metadata
Assert.NotNull(serialized.props);
Assert.Contains(serialized.props, p => p.name == "Name");
// "Value" should also be present since we are using TestTarget
Assert.Contains(serialized.props, p => p.name == "Value");
}

[Fact]
public void Serialize_DelegatesAndFilters()
{
// Arrange
var typeName = typeof(TestTarget).FullName!;
var mockConverter = new MockConverter(); // Serializes everything normally because it returns all properties

// Should ignore "Secret" even though delegated
var converter = new LazyReflectionConverter(
typeName,
backingConverter: mockConverter,
ignoredProperties: new[] { "Secret" });

var reflector = new Reflector();
reflector.Converters.Add(converter);

var obj = new TestTarget { Name = "Filtered", Secret = "ShouldBeGone" };

// Act
var serialized = reflector.Serialize(obj);

// Assert
Assert.True(mockConverter.MetadataWasAccessed, "Backing converter metadata should be accessed.");
Assert.NotNull(serialized.props);

// "Name" should remain
Assert.Contains(serialized.props, p => p.name == "Name");

// "Secret" should be removed by LazyReflectionConverter filtering logic
Assert.DoesNotContain(serialized.props, p => p.name == "Secret");
}

[Fact]
public void Constructor_BackingConverterWithIgnoredMembers_Succeeds()
{
// This verifies the fix: we no longer throw exception for this combination
var typeName = typeof(TestTarget).FullName!;
var mockConverter = new MockConverter();

var converter = new LazyReflectionConverter(
typeName,
ignoredProperties: new[] { "Test" },
backingConverter: mockConverter);

Assert.NotNull(converter);
}

class MockConverter : GenericReflectionConverter<TestTarget>
{
public bool WasCalled { get; private set; }
public bool MetadataWasAccessed { get; private set; }

protected override SerializedMember InternalSerialize(
protected override IEnumerable<System.Reflection.PropertyInfo>? GetSerializablePropertiesInternal(
Reflector reflector,
object? obj,
Type type,
string? name = null,
bool recursive = true,
Type objType,
System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic,
int depth = 0,
Logs? logs = null,
ILogger? logger = null,
SerializationContext? context = null)
ILogger? logger = null)
{
WasCalled = true;
return SerializedMember.FromJson(type, "{}", name);
MetadataWasAccessed = true;
return base.GetSerializablePropertiesInternal(reflector, objType, flags, logger);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion ReflectorNet/ReflectorNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<!-- NuGet Package Information -->
<PackageId>com.IvanMurzak.ReflectorNet</PackageId>
<Version>3.8.0</Version>
<Version>3.8.1</Version>
<Authors>Ivan Murzak</Authors>
<Copyright>Copyright © Ivan Murzak 2025</Copyright>
<Description>ReflectorNet is an advanced .NET reflection toolkit designed for AI-driven scenarios. Effortlessly search for C# methods using natural language queries, invoke any method by supplying arguments as JSON, and receive results as JSON. The library also provides a powerful API to inspect, modify, and manage in-memory object instances dynamically via JSON data. Ideal for automation, testing, and AI integration workflows.</Description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,8 @@ public virtual int SerializationPriority(Type type, ILogger? logger = null)
ILogger? logger = null)
{
return objType.GetProperties(flags)
.Where(prop => prop.GetCustomAttribute<ObsoleteAttribute>() == null)
.Where(prop => prop.CanRead)
.Where(prop => prop.GetCustomAttribute<ObsoleteAttribute>() == null)
.Where(prop => prop.GetIndexParameters().Length == 0); // Filter out indexer properties
}

Expand Down
75 changes: 37 additions & 38 deletions ReflectorNet/src/Converter/Reflection/LazyReflectionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using com.IvanMurzak.ReflectorNet.Model;
using com.IvanMurzak.ReflectorNet.Utils;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -55,11 +54,9 @@ public LazyReflectionConverter(
_targetType = new Lazy<Type?>(() => TypeUtils.GetType(_targetTypeName));
}

private Type? GetTargetType() => _targetType.Value;

public override int SerializationPriority(Type type, ILogger? logger = null)
{
var targetType = GetTargetType();
var targetType = _targetType.Value;

// If the target type cannot be resolved, this converter is inactive.
if (targetType == null)
Expand All @@ -77,48 +74,50 @@ public override int SerializationPriority(Type type, ILogger? logger = null)
: 0;
}

protected override SerializedMember InternalSerialize(
protected override IEnumerable<PropertyInfo>? GetSerializablePropertiesInternal(
Reflector reflector,
object? obj,
Type type,
string? name = null,
bool recursive = true,
BindingFlags flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
int depth = 0,
Logs? logs = null,
ILogger? logger = null,
SerializationContext? context = null)
Type objType,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
ILogger? logger = null)
{
if (_backingConverter != null)
{
return _backingConverter.Serialize(
reflector: reflector,
obj: obj,
fallbackType: type,
name: name,
recursive: recursive,
flags: flags,
depth: depth,
logs: logs,
logger: logger,
context: context);
return _backingConverter.GetSerializableProperties(reflector, objType, flags, logger);
}

return base.InternalSerialize(
reflector: reflector,
obj: obj,
type: type,
name: name,
recursive: recursive,
flags: flags,
depth: depth,
logs: logs,
logger: logger,
context: context);
return base.GetSerializablePropertiesInternal(reflector, objType, flags, logger);
}

protected override IEnumerable<string> GetIgnoredProperties() => _ignoredProperties;
protected override IEnumerable<FieldInfo>? GetSerializableFieldsInternal(
Reflector reflector,
Type objType,
BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
ILogger? logger = null)
{
if (_backingConverter != null)
{
return _backingConverter.GetSerializableFields(reflector, objType, flags, logger);
}

return base.GetSerializableFieldsInternal(reflector, objType, flags, logger);
}

protected override IEnumerable<string> GetIgnoredFields() => _ignoredFields;
protected override IEnumerable<string> GetIgnoredProperties()
{
foreach (var prop in base.GetIgnoredProperties())
yield return prop;

foreach (var prop in _ignoredProperties)
yield return prop;
}

protected override IEnumerable<string> GetIgnoredFields()
{
foreach (var field in base.GetIgnoredFields())
yield return field;

foreach (var field in _ignoredFields)
yield return field;
}
}
}