The details of the problem might be a little long, so I'll describe it in short at the beginning: How to force Json.Net to use its default object serializer(or ignore a specific custom converter in other words), but still keep the settings in a JsonSerializer
, when deserializing an object?
Apologize for my poor English, the description may be kind of ambiguous and confusing. I'll explain it with my detailed scenario.
When dealing with HTTP responses, we sometimes encounter a scenario that an object is the only child of its parent, making the parent object a meaningless object wrapper to some extent. In some poor designs, there could be multiple levels of such wrappers. If we want such JSON deserialized properly without customizing, we have to follow the structure to define those wrapper classes, which is definitely pointless and annoying, thus I came up with the idea to create a general-purpose ObjectWrapperConverter. Here's the code:
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;
}
}
It works fine when I put JsonConverterAttribute
on properties and fields. But when annotating class, problem occurs: the deserialization process fall into a recursive loop.
I debugged into Json.Net framework, and realized that when specifying a custom converter for a class, Json.Net will always use this converter to handle the serialization of this class unless higher-priority attribute (like JsonConverterAttribute placed on properties) is annotated. Thus, in my converter, the line where I put a comment will finally lead to a recurse.
If you've understood the purpose of this converter, it's easy to find out that this converter is just a middleware: add or remove the wrapper object, and continue the original serialization process.
So, how can I continue the "original" serialization process instead of falling into the converter itself again?