Skip to content

Commit 137d400

Browse files
committed
feat: Implement blacklisting for types in the Reflector and enhance serialization logging
1 parent f404415 commit 137d400

8 files changed

Lines changed: 138 additions & 32 deletions

File tree

ReflectorNet.Tests/src/ReflectorTests/DeserializationTests.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,8 @@ void DeserializeNullValue(Type type)
5151
// Arrange
5252
var reflector = new Reflector();
5353
// var jsonElement = JsonSerializer.ToJsonElement(null);
54-
var serializedMember = SerializedMember.FromValue(
55-
reflector: reflector,
54+
var serializedMember = SerializedMember.Null(
5655
type: type,
57-
value: null,
5856
name: TypeUtils.GetTypeShortName(type));
5957

6058
// Act

ReflectorNet/ReflectorNet.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<!-- NuGet Package Information -->
1111
<PackageId>com.IvanMurzak.ReflectorNet</PackageId>
12-
<Version>3.2.1</Version>
12+
<Version>3.2.2</Version>
1313
<Authors>Ivan Murzak</Authors>
1414
<Copyright>Copyright © Ivan Murzak 2025</Copyright>
1515
<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>

ReflectorNet/src/Converter/Reflection/ArrayReflectionConverter.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ protected override SerializedMember InternalSerialize(
7676

7777
foreach (var element in enumerable)
7878
{
79+
if (logger?.IsEnabled(LogLevel.Trace) == true)
80+
logger.LogTrace("{padding} Serializing item '{index}' of type '{type}' in '{objType}'.\nPath: {path}",
81+
StringUtils.GetPadding(depth), index, element?.GetType().GetTypeId() ?? elementType?.GetTypeId(), obj.GetType().GetTypeId(), context?.GetPath(obj));
82+
7983
serializedList.Add(reflector.Serialize(
8084
element,
8185
fallbackType: element?.GetType() ?? elementType,

ReflectorNet/src/Converter/Reflection/Base/BaseReflectionConverter.Serialize.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Linq;
1111
using System.Reflection;
1212
using com.IvanMurzak.ReflectorNet.Model;
13+
using com.IvanMurzak.ReflectorNet.Utils;
1314
using Microsoft.Extensions.Logging;
1415

1516
namespace com.IvanMurzak.ReflectorNet.Converter
@@ -63,9 +64,25 @@ public virtual SerializedMember Serialize(
6364
foreach (var field in fields)
6465
{
6566
if (GetIgnoredFields().Contains(field.Name))
67+
{
68+
if (logger?.IsEnabled(LogLevel.Trace) == true)
69+
logger.LogTrace("{padding} Skipping serialization of field '{fieldName}' in '{objType}' because it is ignored.\nPath: {path}",
70+
StringUtils.GetPadding(depth + 1), field.Name, objType.GetTypeId(), context?.GetPath(obj));
71+
continue;
72+
}
73+
if (reflector.Converters.IsTypeBlacklisted(field.FieldType))
74+
{
75+
if (logger?.IsEnabled(LogLevel.Trace) == true)
76+
logger.LogTrace("{padding} Skipping serialization of field '{fieldName}' of type '{type}' in '{objType}' because its type is blacklisted.\nPath: {path}",
77+
StringUtils.GetPadding(depth + 1), field.Name, field.FieldType.GetTypeId(), objType.GetTypeId(), context?.GetPath(obj));
6678
continue;
79+
}
6780
try
6881
{
82+
if (logger?.IsEnabled(LogLevel.Trace) == true)
83+
logger.LogTrace("{padding} Serializing field '{fieldName}' of type '{type}' in '{objType}'.\nPath: {path}",
84+
StringUtils.GetPadding(depth + 1), field.Name, field.FieldType.GetTypeId(), objType.GetTypeId(), context?.GetPath(obj));
85+
6986
var value = field.GetValue(obj);
7087
var fieldType = field.FieldType;
7188

@@ -112,9 +129,25 @@ public virtual SerializedMember Serialize(
112129
foreach (var prop in properties)
113130
{
114131
if (GetIgnoredProperties().Contains(prop.Name))
132+
{
133+
if (logger?.IsEnabled(LogLevel.Trace) == true)
134+
logger.LogTrace("{padding} Skipping serialization of property '{propertyName}' in '{objType}' because it is ignored.\nPath: {path}",
135+
StringUtils.GetPadding(depth + 1), prop.Name, objType.GetTypeId(), context?.GetPath(obj));
136+
continue;
137+
}
138+
if (reflector.Converters.IsTypeBlacklisted(prop.PropertyType))
139+
{
140+
if (logger?.IsEnabled(LogLevel.Trace) == true)
141+
logger.LogTrace("{padding} Skipping serialization of property '{propertyName}' of type '{type}' in '{objType}' because its type is blacklisted.\nPath: {path}",
142+
StringUtils.GetPadding(depth + 1), prop.Name, prop.PropertyType.GetTypeId(), objType.GetTypeId(), context?.GetPath(obj));
115143
continue;
144+
}
116145
try
117146
{
147+
if (logger?.IsEnabled(LogLevel.Trace) == true)
148+
logger.LogTrace("{padding} Serializing property '{propertyName}' of type '{type}' in '{objType}'.\nPath: {path}",
149+
StringUtils.GetPadding(depth + 1), prop.Name, prop.PropertyType.GetTypeId(), objType.GetTypeId(), context?.GetPath(obj));
150+
118151
var value = prop.GetValue(obj);
119152
var propType = prop.PropertyType;
120153

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* ReflectorNet
3+
* Author: Ivan Murzak (https://github.com/IvanMurzak)
4+
* Copyright (c) 2025 Ivan Murzak
5+
* Licensed under the Apache License, Version 2.0. See LICENSE file in the project root for full license information.
6+
*/
7+
8+
using System;
9+
using System.Text.Json;
10+
using System.Text.Json.Nodes;
11+
using com.IvanMurzak.ReflectorNet.Utils;
12+
13+
namespace com.IvanMurzak.ReflectorNet.Model
14+
{
15+
public partial class SerializedMember
16+
{
17+
public static SerializedMember FromReference(string path, string? name)
18+
{
19+
var jsonObject = new JsonObject { [JsonSchema.Ref] = path };
20+
21+
return new SerializedMember
22+
{
23+
name = name,
24+
typeName = JsonSchema.Reference,
25+
valueJsonElement = jsonObject.ToJsonElement()
26+
};
27+
}
28+
29+
public static SerializedMember Null(Type type, string? name = null)
30+
=> new SerializedMember(type, name);
31+
32+
public static SerializedMember FromJson(Type type, JsonElement json, string? name = null)
33+
=> new SerializedMember(type, name).SetJsonValue(json);
34+
35+
public static SerializedMember FromJson(Type type, string? json, string? name = null)
36+
=> new SerializedMember(type, name).SetJsonValue(json);
37+
38+
public static SerializedMember FromValue(Reflector reflector, Type type, object? value, string? name = null)
39+
=> new SerializedMember(type, name).SetValue(reflector, value);
40+
41+
public static SerializedMember FromValue<T>(Reflector reflector, T? value, string? name = null)
42+
=> new SerializedMember(typeof(T), name).SetValue(reflector, value);
43+
}
44+
}

ReflectorNet/src/Model/SerializedMember.cs

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@
99
using System.ComponentModel;
1010
using System.Linq;
1111
using System.Text.Json;
12-
using System.Text.Json.Nodes;
1312
using System.Text.Json.Serialization;
1413
using com.IvanMurzak.ReflectorNet.Utils;
1514

1615
namespace com.IvanMurzak.ReflectorNet.Model
1716
{
1817
[Serializable]
19-
public class SerializedMember
18+
public partial class SerializedMember
2019
{
2120
public const string ValueName = "value";
2221

@@ -54,18 +53,6 @@ public SerializedMember SetName(string? name)
5453
return this;
5554
}
5655

57-
public static SerializedMember FromReference(string path, string? name)
58-
{
59-
var jsonObject = new JsonObject { [JsonSchema.Ref] = path };
60-
61-
return new SerializedMember
62-
{
63-
name = name,
64-
typeName = JsonSchema.Reference,
65-
valueJsonElement = jsonObject.ToJsonElement()
66-
};
67-
}
68-
6956
public SerializedMember? GetField(string name)
7057
=> fields?.FirstOrDefault(x => x.name == name);
7158

@@ -139,17 +126,5 @@ public SerializedMember SetJsonValue(JsonElement jsonElement)
139126
valueJsonElement = jsonElement;
140127
return this;
141128
}
142-
143-
public static SerializedMember FromJson(Type type, JsonElement json, string? name = null)
144-
=> new SerializedMember(type, name).SetJsonValue(json);
145-
146-
public static SerializedMember FromJson(Type type, string? json, string? name = null)
147-
=> new SerializedMember(type, name).SetJsonValue(json);
148-
149-
public static SerializedMember FromValue(Reflector reflector, Type type, object? value, string? name = null)
150-
=> new SerializedMember(type, name).SetValue(reflector, value);
151-
152-
public static SerializedMember FromValue<T>(Reflector reflector, T? value, string? name = null)
153-
=> new SerializedMember(typeof(T), name).SetValue(reflector, value);
154129
}
155130
}

ReflectorNet/src/Reflector/Reflector.Registry.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public partial class Reflector
3838
public class Registry
3939
{
4040
ConcurrentBag<IReflectionConverter> _serializers = new ConcurrentBag<IReflectionConverter>();
41+
ConcurrentDictionary<Type, byte> _blacklistedTypes = new ConcurrentDictionary<Type, byte>();
4142

4243
/// <summary>
4344
/// Initializes a new Registry instance with default converters for common .NET types.
@@ -90,6 +91,44 @@ public void Remove<T>() where T : IReflectionConverter
9091
/// <returns>A read-only list containing all registered converters.</returns>
9192
public IReadOnlyList<IReflectionConverter> GetAllSerializers() => _serializers.ToList();
9293

94+
/// <summary>
95+
/// Adds a type to the blacklist, preventing it from being processed by any converter.
96+
/// </summary>
97+
/// <param name="type"></param>
98+
public void BlacklistType(Type type)
99+
{
100+
if (type == null)
101+
return;
102+
103+
_blacklistedTypes.TryAdd(type, 0);
104+
}
105+
106+
/// <summary>
107+
/// Checks if a type is blacklisted.
108+
/// </summary>
109+
/// <param name="type"></param>
110+
/// <returns>True if the type is blacklisted; otherwise, false.</returns>
111+
public bool IsTypeBlacklisted(Type type)
112+
{
113+
return _blacklistedTypes.ContainsKey(type);
114+
}
115+
116+
/// <summary>
117+
/// Removes a type from the blacklist.
118+
/// </summary>
119+
/// <param name="type"></param>
120+
/// <returns></returns>
121+
public bool RemoveBlacklistedType(Type type)
122+
{
123+
return _blacklistedTypes.TryRemove(type, out _);
124+
}
125+
126+
/// <summary>
127+
/// Returns a read-only list of all blacklisted types.
128+
/// </summary>
129+
/// <returns>A read-only list containing all blacklisted types.</returns>
130+
public IReadOnlyList<Type> GetAllBlacklistedTypes() => _blacklistedTypes.Keys.ToList();
131+
93132
/// <summary>
94133
/// Finds all converters that can handle the specified type, ordered by priority.
95134
/// This method implements the core converter selection algorithm by querying

ReflectorNet/src/Reflector/Reflector.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,19 @@ public SerializedMember Serialize(
107107
if (type == null)
108108
throw new ArgumentException(error);
109109

110+
if (Converters.IsTypeBlacklisted(type))
111+
{
112+
if (logger?.IsEnabled(LogLevel.Trace) == true)
113+
logger.LogTrace($"{StringUtils.GetPadding(depth)} Serialize. Type '{type.GetTypeId()}' is blacklisted, skipping.");
114+
return SerializedMember.Null(type, name);
115+
}
116+
110117
var converter = Converters.GetConverter(type);
111118
if (converter == null)
112-
throw new ArgumentException($"Type '{type.GetTypeId().ValueOrNull()}' not supported for serialization.");
119+
throw new ArgumentException($"Failed to serialize '{name.ValueOrNull()}'. Type '{type.GetTypeId().ValueOrNull()}' not supported for serialization.");
113120

114121
if (logger?.IsEnabled(LogLevel.Trace) == true)
115-
logger.LogTrace($"{StringUtils.GetPadding(depth)} Serialize. {converter.GetType().GetTypeShortName()} used for type='{type.GetTypeShortName()}', name='{name.ValueOrNull()}'");
122+
logger.LogTrace($"{StringUtils.GetPadding(depth)} Serialize '{name.ValueOrNull()}' of type '{type.GetTypeId()}'. Converter: {converter.GetType().GetTypeShortName()}");
116123

117124
return converter.Serialize(
118125
this,
@@ -239,6 +246,12 @@ public SerializedMember Serialize(
239246
throw new ArgumentException(error);
240247
}
241248

249+
if (Converters.IsTypeBlacklisted(type))
250+
{
251+
logger?.LogTrace($"{padding} Deserialize. Type '{type.GetTypeShortName()}' is blacklisted, skipping.");
252+
return GetDefaultValue(type);
253+
}
254+
242255
var converter = Converters.GetConverter(type);
243256
if (converter == null)
244257
throw new ArgumentException($"[Error] Type '{type?.GetTypeId().ValueOrNull()}' not supported for deserialization.");

0 commit comments

Comments
 (0)