There are two packages provided out of the box : One for Newtonsoft.Json and one for System.Text.Json. Be careful : These deserialization libraries sometimes behave differently, it's important to understand the limitations of each method!
The goal is to provide JSON serializers and deserializers for the anyOf/allOf/oneOf use cases. The serializers have the same name between the two packages to ease the transition between the two.
NOTE : The serialization part is not yet implemented.
All these converters are used to deserialize a part of a JSON document into a TypeUnion<TLeft, TRight>
. The behavior differs between the converters chosen
AnyOfJsonConverter<TLeft, TRight>
: The deserialized value can be either aTLeft
, aTRight
or may deserialize as both aTLeft
and aTRight
at the same timeAllOfJsonConverter<TLeft, TRight>
: Throws aJsonSerializationException
(Newtonsoft.Json) orJsonException
(System.Text.Json) if the value fails to deserialize asTLeft
orTRight
. The value must be valid as aTLeft
and aTRight
OneOfJsonConverter<TLeft, TRight>
: Throws aJsonSerializationException
(Newtonsoft.Json) orJsonException
(System.Text.Json) if the value fails to deserialize with bothTLeft
andTRight
type. The value must be valid as eitherTLeft
orTRight
You can use the converters, as any other JsonConverter.
public class TestModel
{
// You can put the converter directly on the property:
[JsonConverter(typeof(OneOfJsonConverter<int, DateTime>))]
public TypeUnion<int, DateTime> DateOrTimestamp { get; set; }
}
#if NET5_0
public record TestRecord(
// You can put the converter on a record's property
[property: JsonConverter(typeof(OneOfJsonConverter<int, DateTime>))]
TypeUnion<int, DateTime> DateOrTimestamp
);
#endif
public TypeUnion<int, DateTime> DeserializeMethodSystemTextJson()
{
// With STJ, as the deserializer options. Useful for example when the TypeUnion<> is the deserialization root
return JsonSerializer.Deserialize<TypeUnion<int, DateTime>>("\"2000-01-01T00:00:00Z\"",
new JsonSerializerOptions()
{
Converters = { new OneOfJsonConverter<int, DateTime>() }
})!;
}
public TypeUnion<int, DateTime> DeserializeMethodNewtonsoftJson()
{
// With Newtonsoft.Json, as the deserializer options. Useful for example when the TypeUnion<> is the deserialization root
// Note: Here, I'm using fully-qualified names with the Newtonsoft namespace. This is only needed because in this example,
// I have mixed STJ and Newtonsoft in the same file. You shouldn't need to be that verbose in your own code (don't mix serializers!)
return Newtonsoft.Json.JsonConvert.DeserializeObject<TypeUnion<int, DateTime>>("\"2000-01-01T00:00:00Z\"",
new Newtonsoft.Json.JsonSerializerSettings()
{
Converters = { new NewtonsoftJson.OneOfJsonConverter<int, DateTime>() }
})!;
}
Both deserializer implementations work in the same simple way:
- Try to deserialize the value as
TLeft
. - If the result is
null
or if an exception is thrown by the deserializer, consider the left part invalid - If the value is not invalid, call the
LeftIsValid
method with the deserialized value. If that method returnsfalse
consider the left part invalid - Repeat the steps 1-3 with
TRight
- Resolve the required rules and throw if necessary (for example if it deserializes as both
Left
andRight
whileOneOfJsonSerializer
is used) - Wrap the result in a new
TypeUnion
instance and return
There are methods you can override in a subclass of any of the Deserializer :
DeserializeAsLeft
/DeserializeAsRight
the deserialize function that can be overriden to handle special deserialization logic.LeftIsValid
/RightIsValid
These may be use to check the deserialized values to ensure they are valid. This is especially useful for System.Text.Json which doesn't fail deserialization ofnon-nullable reference type
when anull
is given in the JSON.
- STJ doesn't fail deserialization on non-nullable reference types
- Newtonsoft.Json interprets strings that looks like dates
// TODO: There are more edge cases, please document them !