From 8c392d58d14cfc59d07d2d79e56f6030c5b38d2f Mon Sep 17 00:00:00 2001 From: Ivan Murzak Date: Sun, 18 Jan 2026 10:56:49 -0800 Subject: [PATCH 1/2] test: enhance LazyReflectionConverterTests with serialization and filtering scenarios --- .../LazyReflectionConverterTests.cs | 74 ++++++++++++++---- .../Base/BaseReflectionConverter.cs | 2 +- .../Reflection/LazyReflectionConverter.cs | 75 +++++++++---------- 3 files changed, 98 insertions(+), 53 deletions(-) diff --git a/ReflectorNet.Tests/src/ReflectorTests/LazyReflectionConverterTests.cs b/ReflectorNet.Tests/src/ReflectorTests/LazyReflectionConverterTests.cs index ab16667a..1aaf5275 100644 --- a/ReflectorNet.Tests/src/ReflectorTests/LazyReflectionConverterTests.cs +++ b/ReflectorNet.Tests/src/ReflectorTests/LazyReflectionConverterTests.cs @@ -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 { - public bool WasCalled { get; private set; } + public bool MetadataWasAccessed { get; private set; } - protected override SerializedMember InternalSerialize( + protected override IEnumerable? 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); } } } diff --git a/ReflectorNet/src/Converter/Reflection/Base/BaseReflectionConverter.cs b/ReflectorNet/src/Converter/Reflection/Base/BaseReflectionConverter.cs index e2767550..bf6e37a8 100644 --- a/ReflectorNet/src/Converter/Reflection/Base/BaseReflectionConverter.cs +++ b/ReflectorNet/src/Converter/Reflection/Base/BaseReflectionConverter.cs @@ -218,8 +218,8 @@ public virtual int SerializationPriority(Type type, ILogger? logger = null) ILogger? logger = null) { return objType.GetProperties(flags) - .Where(prop => prop.GetCustomAttribute() == null) .Where(prop => prop.CanRead) + .Where(prop => prop.GetCustomAttribute() == null) .Where(prop => prop.GetIndexParameters().Length == 0); // Filter out indexer properties } diff --git a/ReflectorNet/src/Converter/Reflection/LazyReflectionConverter.cs b/ReflectorNet/src/Converter/Reflection/LazyReflectionConverter.cs index 92454d9e..fa4a3fdb 100644 --- a/ReflectorNet/src/Converter/Reflection/LazyReflectionConverter.cs +++ b/ReflectorNet/src/Converter/Reflection/LazyReflectionConverter.cs @@ -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; @@ -55,11 +54,9 @@ public LazyReflectionConverter( _targetType = new Lazy(() => 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) @@ -77,48 +74,50 @@ public override int SerializationPriority(Type type, ILogger? logger = null) : 0; } - protected override SerializedMember InternalSerialize( + protected override IEnumerable? 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 GetIgnoredProperties() => _ignoredProperties; + protected override IEnumerable? 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 GetIgnoredFields() => _ignoredFields; + protected override IEnumerable GetIgnoredProperties() + { + foreach (var prop in base.GetIgnoredProperties()) + yield return prop; + + foreach (var prop in _ignoredProperties) + yield return prop; + } + + protected override IEnumerable GetIgnoredFields() + { + foreach (var field in base.GetIgnoredFields()) + yield return field; + + foreach (var field in _ignoredFields) + yield return field; + } } } From 064b2c88e6d2bf35f4e7e60f8033840dace7b887 Mon Sep 17 00:00:00 2001 From: Ivan Murzak Date: Sun, 18 Jan 2026 10:57:28 -0800 Subject: [PATCH 2/2] chore: bump version to 3.8.1 in project file --- ReflectorNet/ReflectorNet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ReflectorNet/ReflectorNet.csproj b/ReflectorNet/ReflectorNet.csproj index a94379ca..b50f87cf 100644 --- a/ReflectorNet/ReflectorNet.csproj +++ b/ReflectorNet/ReflectorNet.csproj @@ -9,7 +9,7 @@ com.IvanMurzak.ReflectorNet - 3.8.0 + 3.8.1 Ivan Murzak Copyright © Ivan Murzak 2025 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.