As can be seen from the reference source, JsonConverter<T>.CanConvert(Type typeToConvert)
is not sealed:
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(T);
}
Thus it is possible for applications to override this method and return true for some type assignable to T
, e.g.:
public override bool CanConvert(Type typeToConvert) => typeof(T).IsAssignableFrom(typeToConvert);
In such a situation, in Read (ref reader, Type typeToConvert, options)
, typeToConvert
will be the declared type of the value being deserialized while T
is the type the converter can convert. You will need to use reflection (e.g. (T)Activator.CreateInstance(typeToConvert)
) to construct the instance being deserialized.
To take a concrete example, imagine you are dealing with the situation from How to handle both a single item and an array for the same property using System.Text.Json? and need to deserialize a JSON value that is sometimes an array and sometimes a single item to a Collection<string>
. To do this, you define the following JsonConverter<Collection<TValue>>
:
public class SingleOrArrayCollectionConverter<TItem> : JsonConverter<Collection<TItem>>
{
public SingleOrArrayCollectionConverter() : this(true) { }
public SingleOrArrayCollectionConverter(bool canWrite) => CanWrite = canWrite;
public bool CanWrite { get; }
public override bool CanConvert(Type typeToConvert) => typeof(Collection<TItem>).IsAssignableFrom(typeToConvert);
public override Collection<TItem> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var list = typeToConvert == typeof(Collection<TItem>) ? new Collection<TItem>() : (Collection<TItem>)Activator.CreateInstance(typeToConvert);
switch (reader.TokenType)
{
case JsonTokenType.StartArray:
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndArray)
break;
list.Add(JsonSerializer.Deserialize<TItem>(ref reader, options));
}
break;
default:
list.Add(JsonSerializer.Deserialize<TItem>(ref reader, options));
break;
}
return list;
}
public override void Write(Utf8JsonWriter writer, Collection<TItem> value, JsonSerializerOptions options)
{
if (CanWrite && value.Count == 1)
JsonSerializer.Serialize(writer, value.First(), options);
else
{
writer.WriteStartArray();
foreach (var item in value)
JsonSerializer.Serialize(writer, item, options);
writer.WriteEndArray();
}
}
}
Then the converter can be used to deserialize to any subclass of Collection<string>
such as ObservableCollection<string>
. If you have some JSON that might be a single string or an array of strings, you may do:
var json = @"""hello"""; // A JSON string primitive "hello"
var options = new JsonSerializerOptions
{
Converters = { new SingleOrArrayCollectionConverter<string>() },
};
var collection = JsonSerializer.Deserialize<ObservableCollection<string>>(json, options);
Notes:
If you do override CanConvert
, it is your responsibility to ensure that it only returns true
for types assignable to T
. In cases where the types are not compatible, System.Text.Json will throw an InvalidOperationException
such as:
System.InvalidOperationException: The converter 'TConverter' is not compatible with the type 'TActualValue'.
For type hierarchies that include open generics, you must use the factory pattern to create specific JsonConverter<T>
instances for each concrete type to be deserialized. How to handle both a single item and an array for the same property using System.Text.Json? shows one such factory which generates a SingleOrArrayConverter<TItem>
converter for all List<TItem>
types.
You may opt to use the factory pattern for type hierarchies with no open types as well. The factory pattern would, for instance, allow you to manufacture converters only for subtypes of some type T
that have a public parameterless constructor (i.e. that satisfy the where T : new()
constraint). But for class hierarchies with no open generics, the factory pattern is optional rather than required.
For an example of a JsonConverter<T>
that overrides CanConvert
and deserializes a polymorphic type hierarchy, see this answer to Is polymorphic deserialization possible in System.Text.Json? by ahsonkhan.
Demo fiddle here.