-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPolymorphicOperationErrorSerializer.cs
More file actions
138 lines (112 loc) · 5.89 KB
/
PolymorphicOperationErrorSerializer.cs
File metadata and controls
138 lines (112 loc) · 5.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using OneBitSoftware.Utilities.Errors;
namespace OneBitSoftware.Utilities
{
// TODO: comments - used to serialize a custom OperationError
public class PolymorphicOperationErrorSerializer<T> : JsonConverter<T>
where T : IOperationError
{
private readonly Dictionary<string, Type> _valueMappings = new Dictionary<string, Type>();
private readonly Dictionary<Type, string> _typeMappings = new Dictionary<Type, string>();
protected virtual string TypePropertyName => "type";
public PolymorphicOperationErrorSerializer()
{
// Define the base OperationError custom type mapping discriminator
this.AddMapping("operation_error", typeof(OperationError));
}
public override bool CanConvert(Type typeToConvert)
{
if (typeToConvert is null) return false;
return typeof(IOperationError).IsAssignableFrom(typeToConvert);
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
try
{
// Should not be used, write-only.
// Deserialize the JSON to the specified type.
var serializationOptions = this.ConstructSafeFallbackOptions(options);
serializationOptions.Converters.Add(new ReadOnlyPartialConverter(this));
return (T)JsonSerializer.Deserialize(ref reader, typeToConvert, serializationOptions);
}
catch (Exception ex)
{
throw new InvalidOperationException("Invalid JSON in request.", ex);
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
return;
}
if (this._typeMappings.TryGetValue(value.GetType(), out var typeValue) == false) throw new InvalidOperationException($"Model of type {value.GetType()} cannot be successfully serialized.");
var tempBufferWriter = new ArrayBufferWriter<byte>();
var tempWriter = new Utf8JsonWriter(tempBufferWriter); // TODO: dispose with using var
var fallbackDeserializationOptions = this.ConstructSafeFallbackOptions(options);
JsonSerializer.Serialize(tempWriter, value, value.GetType(), fallbackDeserializationOptions);
tempWriter.Flush();
var jsonDocument = JsonDocument.Parse(tempBufferWriter.WrittenMemory);
writer.WriteStartObject();
writer.WriteString(this.TypePropertyName, typeValue);
foreach (var property in jsonDocument.RootElement.EnumerateObject()) property.WriteTo(writer);
writer.WriteEndObject();
}
protected bool AddMapping(string typeValue, Type type)
{
if (string.IsNullOrWhiteSpace(typeValue) || type is null) return false;
if (this._valueMappings.ContainsKey(typeValue) || this._typeMappings.ContainsKey(type)) return false;
this._valueMappings[typeValue] = type;
this._typeMappings[type] = typeValue;
return true;
}
private Type GetType(JsonElement typeElement)
{
if (typeElement.ValueKind != JsonValueKind.String) return null;
var stringValue = typeElement.GetString();
if (string.IsNullOrWhiteSpace(stringValue)) return null;
this._valueMappings.TryGetValue(stringValue, out var type);
return type;
}
private JsonSerializerOptions ConstructSafeFallbackOptions(JsonSerializerOptions options)
{
var fallbackSerializationOptions = new JsonSerializerOptions(options);
fallbackSerializationOptions.Converters.Remove(this);
return fallbackSerializationOptions;
}
private class ReadOnlyPartialConverter : JsonConverter<T>
{
private readonly PolymorphicOperationErrorSerializer<T> _polymorphicConverter;
internal ReadOnlyPartialConverter(PolymorphicOperationErrorSerializer<T> polymorphicConverter)
{
this._polymorphicConverter = polymorphicConverter ?? throw new ArgumentNullException(nameof(polymorphicConverter));
}
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(T);
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// We copy the value here so we can easily reuse the reader for the subsequent deserialization.
var readerRestore = reader;
// Get the `type` value by parsing the JSON string into a JsonDocument.
var jsonDocument = JsonDocument.ParseValue(ref reader);
jsonDocument.RootElement.TryGetProperty(this._polymorphicConverter.TypePropertyName, out var typeElement);
var returnType = this._polymorphicConverter.GetType(typeElement);
if (returnType is null) throw new InvalidOperationException("The received JSON cannot be deserialized to any known type.");
try
{
// Deserialize the JSON to the specified type.
return (T)JsonSerializer.Deserialize(ref readerRestore, returnType, options);
}
catch (Exception ex)
{
throw new InvalidOperationException("Invalid JSON in request.", ex);
}
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => throw new InvalidOperationException("The `Read only partial converter` cannot be used for serialization.");
}
}
}