Json.Net 自定义转换器的自递归
Json.Net Self Recurse on Custom Converter
问题的细节可能有点长,所以我会在开头简短地描述一下:如何强制Json.Net使用它的默认对象序列化器(或忽略换句话说,一个特定的自定义转换器),但在反序列化对象时仍将设置保留在 JsonSerializer
中?
为我糟糕的英语道歉,描述可能有点模棱两可和令人困惑。我会用我的详细场景来解释。
在处理 HTTP 响应时,我们有时会遇到这样的场景,即一个对象是其父对象的唯一子对象,使得父对象在某种程度上成为无意义的 object wrapper。在一些糟糕的设计中,可能会有多层这样的包装器。如果我们想要在不自定义的情况下正确反序列化这样的 JSON,我们必须按照结构来定义那些包装器 classes,这绝对是毫无意义和烦人的,因此我想出了创建一个通用的想法-目的 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
放在属性和字段上时,它工作正常。但是在注解class的时候出现问题:反序列化过程陷入了递归循环
我调试了 Json.Net 框架,并意识到当为 class 指定自定义转换器时,Json.Net 将始终使用此转换器来处理此 class 的序列化,除非更高 -优先级属性(如放置在属性上的 JsonConverterAttribute)被注释。因此,在我的转换器中,我放置注释的行最终将导致递归。
如果你了解了这个转换器的用途,很容易发现这个转换器只是一个中间件:添加或删除包装对象,并继续原来的序列化过程。
那么,如何才能继续“原始”的序列化过程,而不是再次陷入转换器本身呢?
我对 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;
}
}
使用反射调用内部方法有风险,不推荐使用,但与中的其他答案相比,这种方法会充分利用序列化程序设置。如果转换器是通用的,就像我正在尝试实现的 ObjectWrapperConverter
一样,这将导致最少的意外结果,因为 Newtonsoft.Json 有大量的设置供用户自定义行为。
问题的细节可能有点长,所以我会在开头简短地描述一下:如何强制Json.Net使用它的默认对象序列化器(或忽略换句话说,一个特定的自定义转换器),但在反序列化对象时仍将设置保留在 JsonSerializer
中?
为我糟糕的英语道歉,描述可能有点模棱两可和令人困惑。我会用我的详细场景来解释。
在处理 HTTP 响应时,我们有时会遇到这样的场景,即一个对象是其父对象的唯一子对象,使得父对象在某种程度上成为无意义的 object wrapper。在一些糟糕的设计中,可能会有多层这样的包装器。如果我们想要在不自定义的情况下正确反序列化这样的 JSON,我们必须按照结构来定义那些包装器 classes,这绝对是毫无意义和烦人的,因此我想出了创建一个通用的想法-目的 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
放在属性和字段上时,它工作正常。但是在注解class的时候出现问题:反序列化过程陷入了递归循环
我调试了 Json.Net 框架,并意识到当为 class 指定自定义转换器时,Json.Net 将始终使用此转换器来处理此 class 的序列化,除非更高 -优先级属性(如放置在属性上的 JsonConverterAttribute)被注释。因此,在我的转换器中,我放置注释的行最终将导致递归。
如果你了解了这个转换器的用途,很容易发现这个转换器只是一个中间件:添加或删除包装对象,并继续原来的序列化过程。
那么,如何才能继续“原始”的序列化过程,而不是再次陷入转换器本身呢?
我对 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;
}
}
使用反射调用内部方法有风险,不推荐使用,但与ObjectWrapperConverter
一样,这将导致最少的意外结果,因为 Newtonsoft.Json 有大量的设置供用户自定义行为。