Skip to content

Commit 7e4aa16

Browse files
json converters
1 parent c57441b commit 7e4aa16

3 files changed

Lines changed: 238 additions & 1 deletion

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Collections.Generic;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using OneBitSoftware.Utilities.Errors;
7+
8+
namespace OneBitSoftware.Utilities
9+
{
10+
public class OperationResultJsonConverter : JsonConverter<OperationResult>
11+
{
12+
public OperationResultJsonConverter()
13+
{
14+
}
15+
16+
public override bool CanConvert(Type typeToConvert)
17+
{
18+
if (typeToConvert is null) return false;
19+
return typeof(OperationResult) == typeToConvert;
20+
}
21+
22+
public override OperationResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
23+
{
24+
try
25+
{
26+
var serializationOptions = this.ConstructSafeFallbackOptions(options);
27+
return JsonSerializer.Deserialize(ref reader, typeToConvert, serializationOptions) as OperationResult;
28+
}
29+
catch (Exception ex)
30+
{
31+
throw new InvalidOperationException("Invalid JSON conversion in OperationResultJsonConverter.", ex);
32+
}
33+
}
34+
35+
public override void Write(Utf8JsonWriter writer, OperationResult value, JsonSerializerOptions options)
36+
{
37+
if (value is null)
38+
{
39+
writer.WriteNullValue();
40+
return;
41+
}
42+
var fallbackSerializationOptions = this.ConstructSafeFallbackOptions(options);
43+
JsonSerializer.Serialize(writer, value, value.GetType(), fallbackSerializationOptions);
44+
}
45+
46+
private JsonSerializerOptions ConstructSafeFallbackOptions(JsonSerializerOptions options)
47+
{
48+
var fallbackSerializationOptions = new JsonSerializerOptions(options);
49+
fallbackSerializationOptions.Converters.Remove(this);
50+
return fallbackSerializationOptions;
51+
}
52+
53+
//private class ReadOnlyPartialConverter : JsonConverter<OperationResult>
54+
//{
55+
// private readonly OperationResultJsonConverter _operationResultConverter;
56+
57+
// internal ReadOnlyPartialConverter(OperationResultJsonConverter operationResultConverter)
58+
// {
59+
// this._operationResultConverter = operationResultConverter ?? throw new ArgumentNullException(nameof(operationResultConverter));
60+
// }
61+
62+
// public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(OperationResult);
63+
64+
// public override OperationResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
65+
// {
66+
// // We copy the value here so we can easily reuse the reader for the subsequent deserialization.
67+
// var readerRestore = reader;
68+
69+
// // Get the `type` value by parsing the JSON string into a JsonDocument.
70+
// var jsonDocument = JsonDocument.ParseValue(ref reader);
71+
// jsonDocument.RootElement.TryGetProperty(this._operationResultConverter.TypePropertyName, out var typeElement);
72+
73+
// var returnType = this._operationResultConverter.GetType(typeElement);
74+
// if (returnType is null) throw new InvalidOperationException("The received JSON cannot be deserialized to any known type.");
75+
76+
// try
77+
// {
78+
// // Deserialize the JSON to the specified type.
79+
// return (OperationResult)JsonSerializer.Deserialize(ref readerRestore, returnType, options);
80+
// }
81+
// catch (Exception ex)
82+
// {
83+
// throw new InvalidOperationException("Invalid JSON in request.", ex);
84+
// }
85+
// }
86+
87+
// public override void Write(Utf8JsonWriter writer, OperationResult value, JsonSerializerOptions options) => throw new InvalidOperationException("The `Read only partial converter` cannot be used for serialization.");
88+
//}
89+
}
90+
}

src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorConverter.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ public class PolymorphicOperationErrorConverter<T> : JsonConverter<T>
1414
private readonly Dictionary<Type, string> _typeMappings = new Dictionary<Type, string>();
1515
protected virtual string TypePropertyName => "type";
1616

17+
public PolymorphicOperationErrorConverter()
18+
{
19+
// Define the base OperationError custom type mapping discriminator
20+
this.AddMapping("operation_error", typeof(OperationError));
21+
}
22+
1723
public override bool CanConvert(Type typeToConvert)
1824
{
1925
if (typeToConvert is null) return false;
20-
return typeof(OperationError).IsAssignableFrom(typeToConvert);
26+
return typeof(IOperationError).IsAssignableFrom(typeToConvert);
2127
}
2228

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

0 commit comments

Comments
 (0)