【问题标题】:Json.Net Self Recurse on Custom Converter自定义转换器上的 Json.Net 自递归
【发布时间】:2021-08-04 05:50:43
【问题描述】:

问题的细节可能有点长,所以我在开头简单描述一下:如何强制Json.Net使用其默认的对象序列化器(或者忽略其他的特定的自定义转换器)字),但在反序列化对象时仍将设置保留在 JsonSerializer 中?
为我糟糕的英语道歉,描述可能有点模棱两可和令人困惑。我会用我的详细场景来解释它。
在处理 HTTP 响应时,我们有时会遇到对象是其父对象的唯一子对象的场景,这使得父对象在某种程度上成为了无意义的对象包装器。在一些糟糕的设计中,可能有多个级别的此类包装器。如果我们想在不自定义的情况下正确反序列化这样的 JSON,我们必须按照结构来定义那些包装类,这绝对是没有意义和烦人的,因此我想出了创建一个通用 ObjectWrapperConverter 的想法.代码如下:

public class ObjectWrapperConverter<T> : ObjectWrapperConverterBase<T> {
    public ObjectWrapperConverter(string propertyName) : this(propertyName, Array.Empty<JsonConverter>()) { }

    public ObjectWrapperConverter(string propertyName, params JsonConverter[] converters) {
        PropertyName = propertyName;
        Converters = converters;
    }

    public override string PropertyName { get; }

    public override JsonConverter[] Converters { get; }
}

public abstract class ObjectWrapperConverterBase<T> : JsonConverter<T> {
    public abstract string PropertyName { get; }

    public abstract JsonConverter[] Converters { get; }

    public sealed override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) {
        writer.WriteStartObject();
        writer.WritePropertyName(PropertyName);
        serializer.Converters.AddRange(Converters);
        writer.WriteValue(value, serializer);
        serializer.Converters.RemoveRange(Converters);
        writer.WriteEndObject();
    }

    public sealed override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) {
        var token = JToken.Load(reader);
        if (token.Type != JTokenType.Object)
            throw new JTokenTypeException(token, JTokenType.Object);
        var obj = token as JObject;
        var prop = obj!.Property(PropertyName);
        if (prop is null)
            throw new JTokenException(token, $"Property \"{PropertyName}\" not found");
        serializer.Converters.AddRange(Converters);
        var result = prop.Value.ToObject<T>(serializer);//BUG: recurse when applying JsonConverterAttribute to a class
        serializer.Converters.RemoveRange(Converters);
        return result;
    }
}

当我将JsonConverterAttribute 放在属性和字段上时,它工作正常。但是在注解类的时候,问题就出现了:反序列化过程陷入了递归循环。
我调试到 Json.Net 框架,并意识到当为类指定自定义转换器时,Json.Net 将始终使用此转换器来处理此类的序列化,除非更高优先级的属性(如放置在属性上的 JsonConverterAttribute)被注释。因此,在我的转换器中,我发表评论的那一行最终会导致递归。 如果您了解了这个转换器的用途,很容易发现这个转换器只是一个中间件:添加或移除包装器对象,然后继续原来的序列化过程。
那么,如何才能继续“原始”序列化过程,而不是再次陷入转换器本身呢?

【问题讨论】:

标签: c# serialization json.net


【解决方案1】:

我对 Newtonsoft.Json 框架进行了更深入的探索,发现了它如何在没有转换器的情况下序列化和反序列化对象。
Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal
Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeValue
因为这些类和方法是内部的,所以我必须使用反射来调用它们。因此我将其封装成两个扩展方法:

public static class NewtonsoftExtensions{
    private static Type JsonSerializerInternalReader { get; } = typeof(JsonSerializer).Assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerInternalReader");

    private static Type JsonSerializerInternalWriter { get; } = typeof(JsonSerializer).Assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerInternalWriter");

    private static MethodInfo CreateValueInternal { get; } = JsonSerializerInternalReader.GetMethod("CreateValueInternal", BindingFlags.NonPublic | BindingFlags.Instance);

    private static MethodInfo SerializeValue { get; } = JsonSerializerInternalWriter.GetMethod("SerializeValue", BindingFlags.NonPublic | BindingFlags.Instance);

    public object DeserializeWithoutContractConverter(this JsonSerializer serializer, JsonReader reader, Type objectType) {
        var contract = serializer.ContractResolver.ResolveContract(objectType);
        var converter = contract.Converter;
        contract.Converter = null;
        object internalReader = Activator.CreateInstance(JsonSerializerInternalReader, serializer);
        object result = CreateValueInternal.Invoke(internalReader, reader, objectType, contract, null, null, null, null);
        contract.Converter = converter; //DefaultContractResolver caches the contract of each type, thus we need to restore the original converter for future use
        return result;
    }

    public void SerializeWithoutContractConverter(this JsonSerializer serializer, JsonWriter writer, object value) {
        var contract = serializer.ContractResolver.ResolveContract(value.GetType());
        var converter = contract.Converter;
        contract.Converter = null;
        object internalWriter = Activator.CreateInstance(JsonSerializerInternalWriter, serializer);
        SerializeValue.Invoke(internalWriter, writer, value, contract, null, null, null);
        contract.Converter = converter;
    }
}

使用反射来调用内部方法是有风险的,不应该推荐,但与JSON.Net throws StackOverflowException when using [JsonConvert()]中的其他答案相比,这种方法会充分利用序列化器的设置。如果转换器是通用的,比如我正在尝试实现的ObjectWrapperConverter,这将导致最少的意外结果,因为 Newtonsoft.Json 有大量设置供用户自定义行为。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-22
    • 2011-09-18
    • 2023-04-07
    • 2013-08-20
    相关资源
    最近更新 更多