-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
Newtonsoft's precedence for converters is as follows (as defined by their docs for converters):
JsonConverter
defined by attribute on a memberJsonConverter
defined by an attribute on a class- any converters passed to the
JsonSerializer
This seems to align pretty well with what I imagine is expected by most developers. However, the precedence used by System.Text.Json is different (as defined by the official docs for converters):
[JsonConverter]
applied to a property.- A converter added to the
Converters
collection. [JsonConverter]
applied to a custom value type or POCO.
This was very unexpected and we found it to be unintuitive. I'm curious as to why an attribute defined on a specific class type would have less priority than a converter added to the global Converters
collection? It seems to go against the general concept of specificity that developers are familiar with.
In addition, this introduces a strange interaction between a JsonConverter
/JsonConverterFactory
and a JsonConverterAttribute
, because it forces the converters to check for a JsonConverterAttribute
on the type they are de/serializing, which shouldn't be their responsibility:
public class SomeConverter<T> : JsonConverter<T> {
public SomeConverter(bool someOption) { ... }
}
public class SomeConverter : JsonConverterFactory {
public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) {
/* In order to be able to de/serialize `type` using `SomeConverterAttribute`,
we're forced to have this factory check if the attribute is set on `type`,
before creating `SomeConverter<T>`. */
}
}
public class SomeConverterAttribute : JsonConverterAttribute {
public bool SomeOption { get; set; }
public override JsonConverter CreateConverter() {
/* Create a `SomeConverter<T>` type, and call it with `SomeOption` */
}
}
[SomeConverter(someOption = true)]
public class SomeClass { ... }
...
serializerOptions.Converters.Add(new SomeConverter());
Even more, it forces a JsonConverterAttribute
and a JsonConverterFactory
to have two different CreateConverter
methods, instead of allowing the attribute to call a factory's CreateConverter
. This is because the factory would need to call the attribute's CreateConverter
if it exists on the type, thereby causing a circular call chain if the attribute's CreateConverter
were to call back to the factory.