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 @@ -41,7 +41,7 @@ public void SerializationPriority_TypeExists_ReturnsHighPriority()
{
// Arrange
var typeName = typeof(TestTarget).FullName!;
var converter = new LazyReflectionConverter(typeName);
var converter = new LazyGenericReflectionConverter(typeName);

// Act
var priority = converter.SerializationPriority(typeof(TestTarget));
Expand All @@ -56,7 +56,7 @@ public void SerializationPriority_TypeDoesNotExist_ReturnsZero()
{
// Arrange
var typeName = "System.NonExistentType.ShouldNotExist";
var converter = new LazyReflectionConverter(typeName);
var converter = new LazyGenericReflectionConverter(typeName);

// Act
var priority = converter.SerializationPriority(typeof(TestTarget));
Expand All @@ -71,7 +71,7 @@ public void SerializationPriority_DerivedType_ReturnsPositivePriority()
{
// Arrange
var typeName = typeof(TestTarget).FullName!;
var converter = new LazyReflectionConverter(typeName);
var converter = new LazyGenericReflectionConverter(typeName);

// Act
var exactMatchPriority = converter.SerializationPriority(typeof(TestTarget));
Expand All @@ -90,7 +90,7 @@ public void Serialize_IgnoresConfiguredProperties()
// Arrange
var typeName = typeof(TestTarget).FullName!;
var ignoredProps = new[] { "Secret" };
var converter = new LazyReflectionConverter(typeName, ignoredProperties: ignoredProps);
var converter = new LazyGenericReflectionConverter(typeName, ignoredProperties: ignoredProps);
var reflector = new Reflector();

// Register manually to ensure it's used
Expand Down Expand Up @@ -119,7 +119,7 @@ public void Serialize_IgnoresConfiguredFields()
// Arrange
var typeName = typeof(TestTargetWithFields).FullName!;
var ignoredFields = new[] { "SecretField" };
var converter = new LazyReflectionConverter(typeName, ignoredFields: ignoredFields);
var converter = new LazyGenericReflectionConverter(typeName, ignoredFields: ignoredFields);
var reflector = new Reflector();

// Register manually to ensure it's used
Expand All @@ -145,9 +145,9 @@ public void Serialize_IgnoresConfiguredFields()
[Fact]
public void Constructor_NullOrEmptyTypeName_ThrowsArgumentException()
{
Assert.Throws<ArgumentException>(() => new LazyReflectionConverter(null!));
Assert.Throws<ArgumentException>(() => new LazyReflectionConverter(""));
Assert.Throws<ArgumentException>(() => new LazyReflectionConverter(" "));
Assert.Throws<ArgumentException>(() => new LazyGenericReflectionConverter(null!));
Assert.Throws<ArgumentException>(() => new LazyGenericReflectionConverter(""));
Assert.Throws<ArgumentException>(() => new LazyGenericReflectionConverter(" "));
}

[Fact]
Expand All @@ -156,7 +156,7 @@ public void Serialize_DelegatesToBackingConverter()
// Arrange
var typeName = typeof(TestTarget).FullName!;
var mockConverter = new MockConverter();
var converter = new LazyReflectionConverter(typeName, backingConverter: mockConverter);
var converter = new LazyGenericReflectionConverter(typeName, backingConverter: mockConverter);
var reflector = new Reflector();
reflector.Converters.Add(converter);

Expand All @@ -183,7 +183,7 @@ public void Serialize_DelegatesAndFilters()
var mockConverter = new MockConverter(); // Serializes everything normally because it returns all properties

// Should ignore "Secret" even though delegated
var converter = new LazyReflectionConverter(
var converter = new LazyGenericReflectionConverter(
typeName,
backingConverter: mockConverter,
ignoredProperties: new[] { "Secret" });
Expand All @@ -210,16 +210,16 @@ public void Serialize_DelegatesAndFilters()
[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();
// 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);
var converter = new LazyGenericReflectionConverter(
typeName,
ignoredProperties: new[] { "Test" },
backingConverter: mockConverter);

Assert.NotNull(converter);
Assert.NotNull(converter);
}

class MockConverter : GenericReflectionConverter<TestTarget>
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.1</Version>
<Version>3.9.0</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
26 changes: 26 additions & 0 deletions ReflectorNet/src/Converter/IReflectionConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,34 @@ namespace com.IvanMurzak.ReflectorNet.Converter
{
public interface IReflectionConverter
{
/// <summary>
/// Gets a value indicating whether this converter supports direct value setting operations.
/// When true, the converter can handle primitive-style value assignments.
/// When false, the converter only supports field and property-based population.
/// </summary>
bool AllowSetValue { get; }

/// <summary>
/// Gets a value indicating whether this converter supports cascading serialization operations.
/// When true, nested objects and collections are recursively serialized.
/// When false, only shallow serialization is performed.
/// </summary>
bool AllowCascadeSerialization { get; }

/// <summary>
/// Gets a value indicating whether this converter should recursively convert field values.
/// When true, field values that are complex objects are serialized recursively.
/// When false, field values are serialized as simple JSON representations.
/// </summary>
bool AllowCascadeFieldsConversion { get; }

/// <summary>
/// Gets a value indicating whether this converter should recursively convert property values.
/// When true, property values that are complex objects are serialized recursively.
/// When false, property values are serialized as simple JSON representations.
/// </summary>
bool AllowCascadePropertiesConversion { get; }

int SerializationPriority(Type type, ILogger? logger = null);

object? Deserialize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,56 @@ namespace com.IvanMurzak.ReflectorNet.Converter
/// This is useful for optional dependencies where the target type might not be present at runtime.
/// If the type is not found, this converter will remain inactive (priority 0).
/// </summary>
public class LazyReflectionConverter : GenericReflectionConverter<object>
public class LazyGenericReflectionConverter : LazyGenericReflectionConverter<object>
{
/// <summary>
/// Initializes a new instance of the <see cref="LazyGenericReflectionConverter"/> class.
/// </summary>
/// <param name="targetTypeName">The full name of the type to handle.</param>
/// <param name="ignoredProperties">Optional list of property names to ignore during serialization.</param>
/// <param name="ignoredFields">Optional list of field names to ignore during serialization.</param>
/// <param name="backingConverter">Optional converter to delegate serialization to.</param>
public LazyGenericReflectionConverter(
string targetTypeName,
IEnumerable<string>? ignoredProperties = null,
IEnumerable<string>? ignoredFields = null,
IReflectionConverter? backingConverter = null)
: base(
targetTypeName: targetTypeName,
ignoredProperties: ignoredProperties,
ignoredFields: ignoredFields,
backingConverter: backingConverter)
{
// empty
}
}

/// <summary>
/// A reflection converter that resolves its target type lazily by name.
/// This is useful for optional dependencies where the target type might not be present at runtime.
/// If the type is not found, this converter will remain inactive (priority 0).
/// </summary>
public class LazyGenericReflectionConverter<T> : GenericReflectionConverter<T>
{
private readonly string _targetTypeName;
private readonly HashSet<string> _ignoredProperties;
private readonly HashSet<string> _ignoredFields;
private readonly IReflectionConverter? _backingConverter;
private readonly Lazy<Type?> _targetType;

public override bool AllowSetValue => _backingConverter?.AllowSetValue ?? base.AllowSetValue;
public override bool AllowCascadeSerialization => _backingConverter?.AllowCascadeSerialization ?? base.AllowCascadeSerialization;
public override bool AllowCascadeFieldsConversion => _backingConverter?.AllowCascadeFieldsConversion ?? base.AllowCascadeFieldsConversion;
public override bool AllowCascadePropertiesConversion => _backingConverter?.AllowCascadePropertiesConversion ?? base.AllowCascadePropertiesConversion;

/// <summary>
/// Initializes a new instance of the <see cref="LazyReflectionConverter"/> class.
/// Initializes a new instance of the <see cref="LazyGenericReflectionConverter"/> class.
/// </summary>
/// <param name="targetTypeName">The full name of the type to handle.</param>
/// <param name="ignoredProperties">Optional list of property names to ignore during serialization.</param>
/// <param name="ignoredFields">Optional list of field names to ignore during serialization.</param>
/// <param name="backingConverter">Optional converter to delegate serialization to.</param>
public LazyReflectionConverter(
public LazyGenericReflectionConverter(
string targetTypeName,
IEnumerable<string>? ignoredProperties = null,
IEnumerable<string>? ignoredFields = null,
Expand Down