System.Text.Json 自定义转换器,用于启用私有 setter 属性的反序列化

问题描述 投票:0回答:1

我需要在我的项目中序列化和反序列化许多不同的类。 其中许多类都具有私有或内部设置器的属性,但对我来说,反序列化这些属性也很重要。

使用参数化构造函数不是一个选项,因为我需要在 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;}
json .net deserialization converters system.text.json
1个回答
0
投票

您可以使用自定义的 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 的序列化属性。

备注:

演示小提琴这里

© www.soinside.com 2019 - 2024. All rights reserved.