You are here: Start » AVL.NET » JSON Serialization
JSON Serialization
Introduction
Most of the simple Avl.NET types serializes to seamlessly to the JSON format. However, there are some exceptions and special cases to consider
when serializing Atl.Optional<T> and
Atl.Conditional<T> types.
One would expect that both of these types would serialize to either the value they contain or to null if they do not contain a value.
Instead, when they do not contain a value, serialization throws an InvalidOperationException exception.
Action action = () => JsonSerializer.Serialize(Conditional.Empty<Point2D>()); action.Should().Throw<InvalidOperationException>();
To resolve this, custom converters must be used.
Converter for System.Text.Json
The following example demonstrates a custom converter factory for handling both Atl.Optional<T> and Atl.Conditional<T> types when using System.Text.Json.JsonSerializer:
public class AtlNullableConverterFactory : JsonConverterFactory { public override bool CanConvert(Type typeToConvert) { if (!typeToConvert.IsGenericType) return false; return typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>) || typeToConvert.GetGenericTypeDefinition() == typeof(Conditional<>); } public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) { Type valueType = typeToConvert.GetGenericArguments()[0]; Type converterType = typeToConvert.GetGenericTypeDefinition() == typeof(Optional<>) ? typeof(AtlOptionalConverter<>).MakeGenericType(valueType) : typeof(AtlConditionalConverter<>).MakeGenericType(valueType); return (JsonConverter?)Activator.CreateInstance(converterType); } private class AtlOptionalConverter<T> : JsonConverter<Optional<T>> { public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.Null && JsonSerializer.Deserialize<T>(ref reader, options) is T value) return Optional.WithValue(value); else return Optional.Empty<T>(); } public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options) { if (value.HasValue) JsonSerializer.Serialize(writer, value.Value, options); else writer.WriteNullValue(); } } private class AtlConditionalConverter<T> : JsonConverter<Conditional<T>> { public override Conditional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType != JsonTokenType.Null && JsonSerializer.Deserialize<T>(ref reader, options) is T value) return Conditional.WithValue(value); else return Conditional.Empty<T>(); } public override void Write(Utf8JsonWriter writer, Conditional<T> value, JsonSerializerOptions options) { if (value.HasValue) JsonSerializer.Serialize(writer, value.Value, options); else writer.WriteNullValue(); } } }
Usage example
With the custom converter factory in place, one can now serialize and deserialize Atl.Optional<T> and Atl.Conditional<T> types seamlessly. The following example demonstrates this usage:
JsonSerializerOptions options = new() { Converters = { new AtlNullableConverterFactory() } }; JsonSerializer .Serialize( Conditional.WithValue<Point2D>(new(1.5f, 2.5f)), options) .Should() .Be(@"{""X"":1.5,""Y"":2.5}"); JsonSerializer .Serialize( Optional.WithValue<Point2D>(new(1.5f, 2.5f)), options ) .Should() .Be(@"{""X"":1.5,""Y"":2.5}"); JsonSerializer .Serialize( Conditional.Empty<Point2D>(), options ) .Should() .Be("null"); var conditionalWithValue = JsonSerializer.Deserialize<Conditional<Point2D>>(@"{""X"":1.5,""Y"":2.5}", options); conditionalWithValue.Should().NotBeNull(); conditionalWithValue.HasValue.Should().BeTrue(); conditionalWithValue.Value.Should().Be(new Point2D(1.5f, 2.5f)); var nullConditional = JsonSerializer.Deserialize<Conditional<Point2D>>("null", options); nullConditional.Should().BeNull();
| Previous: Settings example | Next: Function Reference |
