我需要在我的项目中序列化和反序列化许多不同的类。 其中许多类都具有私有或内部设置器的属性,但对我来说,反序列化这些属性也很重要。
使用参数化构造函数不是一个选项,因为我需要在 Json 往返期间保留引用。
添加 [JsonInclude] 可以,但随后我必须将其添加到数百个属性中。
所以我尝试编写一个自定义转换器,它只执行默认的反序列化,除了使用所有属性设置器。 但是我的所有版本最终都会出现一些错误,一旦反序列化期间不保留引用,有时递归转换器调用会导致堆栈溢出异常....
有人创造过类似的东西吗? 或者有没有简单的方法可以做到这一点?
更新: 我使用的是.net8,并且没有计划生成代码源。
这是我当前版本的转换器:
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options){
var newOptions = new JsonSerializerOptions(options);
newOptions.Converters.Clear();
Dictionary<string, object>? dict = JsonSerializer.Deserialize<Dictionary<string, object>?>(ref reader, newOptions);
if (dict != null)
{
T? obj = (T?)Activator.CreateInstance(typeof(T), true);
foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (dict.TryGetValue(prop.Name, out var value))
if (prop.CanWrite)
{
object? convertedValue;
if (value is JsonElement jsonElement)
{
if (prop.PropertyType == typeof(int))
{
convertedValue = jsonElement.GetInt32();
}
else if (prop.PropertyType == typeof(string))
{
convertedValue = jsonElement.GetString();
}
else if (prop.PropertyType == typeof(double))
{
convertedValue = jsonElement.GetDouble();
}
else if (prop.PropertyType == typeof(bool))
{
convertedValue = jsonElement.GetBoolean();
}
else
{
convertedValue = jsonElement.Deserialize(prop.PropertyType, options);
}
}
else
{
convertedValue = Convert.ChangeType(value, prop.PropertyType);
}
prop.SetValue(obj, convertedValue);
}
}
return obj;
}
return default;}
您可以使用自定义的 typeInfo 修饰符,而不是使用转换器,为所有序列化属性添加对私有设置器的调用。
首先添加以下扩展方法:
public static partial class JsonExtensions
{
public static void DeserializePrivateSetters(JsonTypeInfo typeInfo)
{
if (typeInfo.Kind != JsonTypeInfoKind.Object)
return;
// Add a Set function for all properties that have a Get function and an underlying set MethodInfo even if private
foreach (var property in typeInfo.Properties)
if (property.Get != null && property.Set == null
&& property.GetPropertyInfo() is {} info
&& info.GetSetMethod(true) is {} setMethod)
{
property.Set = CreateSetter(typeInfo.Type, setMethod);
}
}
static Action<object,object?>? CreateSetter(Type type, MethodInfo? method)
{
if (method == null)
return null;
var myMethod = typeof(JsonExtensions).GetMethod(nameof(JsonExtensions.CreateSetterGeneric), BindingFlags.NonPublic | BindingFlags.Static)!;
return (Action<object,object?>)(myMethod.MakeGenericMethod(new [] { type, method.GetParameters().Single().ParameterType }).Invoke(null, new[] { method })!);
}
static Action<object,object?>? CreateSetterGeneric<TObject, TValue>(MethodInfo method)
{
if (method == null)
throw new ArgumentNullException();
if (typeof(TObject).IsValueType)
{
// TODO: find a performant way to do this. Possibilities:
// Box<T> from Microsoft.Toolkit.HighPerformance
// https://stackoverflow.com/questions/18937935/how-to-mutate-a-boxed-struct-using-il
return (o, v) => method.Invoke(o, new [] { v });
}
else
{
var func = (Action<TObject, TValue?>)Delegate.CreateDelegate(typeof(Action<TObject, TValue?>), method);
return (o, v) => func((TObject)o, (TValue?)v);
}
}
static PropertyInfo? GetPropertyInfo(this JsonPropertyInfo property) => (property.AttributeProvider as PropertyInfo);
}
然后按如下方式设置您的选项:
var options = new JsonSerializerOptions
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
.WithAddedModifier(JsonExtensions.DeserializePrivateSetters),
// Add any other options as needed. E.g. you mentioned using reference preservation.
ReferenceHandler = ReferenceHandler.Preserve,
WriteIndented = true,
};
JsonPropertyInfo.Set
回调将被添加到 JsonTypeInfo
合约中,用于所有具有私有或内部 setter 的序列化属性。
备注:
JsonTypeInfo
修饰符方法可自动与引用保存和对多态性的内置支持一起工作。
为了进行比较,如果您尝试使用
JsonConverter
实现相同的功能,则需要在转换器的 Read()
和 Write()
方法中手动支持引用保存和多态性。
在Native AOT应用程序中使用源生成时,该方法可能不起作用。 对
MethodInfo.MakeGenericMethod()
和 Delegate.CreateDelegate()
的调用可能会失败。
有关相关问题,请参阅 获取 .NET Core JsonSerializer 以序列化私有成员。
演示小提琴这里