Skip to content

Commit a48a187

Browse files
Serialization work and tests, still not cleaned up
1 parent 7e4aa16 commit a48a187

12 files changed

Lines changed: 427 additions & 274 deletions

src/OneBitSoftware.Utilities.OperationResult/OperationResultJsonConverter.cs

Lines changed: 111 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77

88
namespace OneBitSoftware.Utilities
99
{
10+
// TODO: comments: used to read an OperationResult, because we don't know what operation errors exist in the JSON
1011
public class OperationResultJsonConverter : JsonConverter<OperationResult>
1112
{
13+
private readonly Dictionary<string, Type> _valueMappings = new Dictionary<string, Type>();
14+
private readonly Dictionary<Type, string> _typeMappings = new Dictionary<Type, string>();
15+
1216
public OperationResultJsonConverter()
1317
{
18+
this.AddMapping("operation_error", typeof(OperationError));
1419
}
1520

1621
public override bool CanConvert(Type typeToConvert)
@@ -24,6 +29,7 @@ public override bool CanConvert(Type typeToConvert)
2429
try
2530
{
2631
var serializationOptions = this.ConstructSafeFallbackOptions(options);
32+
serializationOptions.Converters.Add(new ReadOnlyPartialConverter(this));
2733
return JsonSerializer.Deserialize(ref reader, typeToConvert, serializationOptions) as OperationResult;
2834
}
2935
catch (Exception ex)
@@ -32,6 +38,27 @@ public override bool CanConvert(Type typeToConvert)
3238
}
3339
}
3440

41+
protected bool AddMapping(string typeValue, Type type)
42+
{
43+
if (string.IsNullOrWhiteSpace(typeValue) || type is null) return false;
44+
if (this._valueMappings.ContainsKey(typeValue) || this._typeMappings.ContainsKey(type)) return false;
45+
46+
this._valueMappings[typeValue] = type;
47+
this._typeMappings[type] = typeValue;
48+
return true;
49+
}
50+
51+
private Type GetType(JsonElement typeElement)
52+
{
53+
if (typeElement.ValueKind != JsonValueKind.String) return null;
54+
55+
var stringValue = typeElement.GetString();
56+
if (string.IsNullOrWhiteSpace(stringValue)) return null;
57+
58+
this._valueMappings.TryGetValue(stringValue, out var type);
59+
return type;
60+
}
61+
3562
public override void Write(Utf8JsonWriter writer, OperationResult value, JsonSerializerOptions options)
3663
{
3764
if (value is null)
@@ -50,41 +77,89 @@ private JsonSerializerOptions ConstructSafeFallbackOptions(JsonSerializerOptions
5077
return fallbackSerializationOptions;
5178
}
5279

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-
//}
80+
private class ReadOnlyPartialConverter : JsonConverter<OperationResult>
81+
{
82+
private readonly OperationResultJsonConverter _operationResultConverter;
83+
84+
internal ReadOnlyPartialConverter(OperationResultJsonConverter operationResultConverter)
85+
{
86+
this._operationResultConverter = operationResultConverter ?? throw new ArgumentNullException(nameof(operationResultConverter));
87+
}
88+
89+
public override bool CanConvert(Type typeToConvert) => typeToConvert == typeof(OperationResult);
90+
91+
public override OperationResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
92+
{
93+
// We copy the value here so we can easily reuse the reader for the subsequent deserialization.
94+
var readerRestore = reader;
95+
96+
// Get the `type` value by parsing the JSON string into a JsonDocument.
97+
var jsonDocument = JsonDocument.ParseValue(ref reader);
98+
99+
try
100+
{
101+
var fallbackDeserializationOptions = this.ConstructSafeFallbackOptions(options);
102+
103+
// Deserialize the JSON to the specified type.
104+
var deserializeResult = JsonSerializer.Deserialize(ref readerRestore, typeToConvert, fallbackDeserializationOptions);
105+
var operationResult = deserializeResult as OperationResult;
106+
107+
if (jsonDocument.RootElement.TryGetProperty("Errors", out var errors))
108+
{
109+
foreach (var item in errors.EnumerateArray())
110+
{
111+
if (item.TryGetProperty("type", out var typeProperty))
112+
{
113+
var returnType = this._operationResultConverter.GetType(typeProperty);
114+
115+
AppendErrorType(item, returnType, operationResult, options);
116+
}
117+
else
118+
{
119+
operationResult.AppendError(this.ToObject<OperationError>(item));
120+
}
121+
}
122+
}
123+
124+
125+
return operationResult;
126+
}
127+
catch (Exception ex)
128+
{
129+
throw new InvalidOperationException("Invalid JSON in request.", ex);
130+
}
131+
132+
}
133+
134+
public T ToObject<T>(JsonElement element)
135+
{
136+
var json = element.GetRawText();
137+
return JsonSerializer.Deserialize<T>(json);
138+
}
139+
140+
public virtual void AppendErrorType(JsonElement element, Type mappedType, OperationResult operationResult, JsonSerializerOptions options)
141+
{
142+
if (mappedType.Equals("operation_error"))
143+
{
144+
operationResult.AppendError(ToObject<OperationError>(element));
145+
}
146+
else
147+
{
148+
var json = element.GetRawText();
149+
var deserializeResult = JsonSerializer.Deserialize(json, mappedType, options);
150+
operationResult.AppendError(error: deserializeResult as IOperationError);
151+
}
152+
}
153+
154+
// TODO
155+
private JsonSerializerOptions ConstructSafeFallbackOptions(JsonSerializerOptions options)
156+
{
157+
var fallbackSerializationOptions = new JsonSerializerOptions(options);
158+
fallbackSerializationOptions.Converters.Remove(this);
159+
return fallbackSerializationOptions;
160+
}
161+
162+
public override void Write(Utf8JsonWriter writer, OperationResult value, JsonSerializerOptions options) => throw new InvalidOperationException("The `Read only partial converter` cannot be used for serialization.");
163+
}
89164
}
90165
}

src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorListConverter.cs

Lines changed: 0 additions & 141 deletions
This file was deleted.

src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorConverter.cs renamed to src/OneBitSoftware.Utilities.OperationResult/PolymorphicOperationErrorSerializer.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@
77

88
namespace OneBitSoftware.Utilities
99
{
10-
public class PolymorphicOperationErrorConverter<T> : JsonConverter<T>
10+
// TODO: comments - used to serialize a custom OperationError
11+
public class PolymorphicOperationErrorSerializer<T> : JsonConverter<T>
1112
where T : IOperationError
1213
{
1314
private readonly Dictionary<string, Type> _valueMappings = new Dictionary<string, Type>();
1415
private readonly Dictionary<Type, string> _typeMappings = new Dictionary<Type, string>();
1516
protected virtual string TypePropertyName => "type";
1617

17-
public PolymorphicOperationErrorConverter()
18+
public PolymorphicOperationErrorSerializer()
1819
{
1920
// Define the base OperationError custom type mapping discriminator
2021
this.AddMapping("operation_error", typeof(OperationError));
@@ -30,6 +31,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial
3031
{
3132
try
3233
{
34+
// Should not be used, write-only.
3335
// Deserialize the JSON to the specified type.
3436
var serializationOptions = this.ConstructSafeFallbackOptions(options);
3537
serializationOptions.Converters.Add(new ReadOnlyPartialConverter(this));
@@ -98,9 +100,9 @@ private JsonSerializerOptions ConstructSafeFallbackOptions(JsonSerializerOptions
98100

99101
private class ReadOnlyPartialConverter : JsonConverter<T>
100102
{
101-
private readonly PolymorphicOperationErrorConverter<T> _polymorphicConverter;
103+
private readonly PolymorphicOperationErrorSerializer<T> _polymorphicConverter;
102104

103-
internal ReadOnlyPartialConverter(PolymorphicOperationErrorConverter<T> polymorphicConverter)
105+
internal ReadOnlyPartialConverter(PolymorphicOperationErrorSerializer<T> polymorphicConverter)
104106
{
105107
this._polymorphicConverter = polymorphicConverter ?? throw new ArgumentNullException(nameof(polymorphicConverter));
106108
}

tests/OneBitSoftware.Utilities.OperationResultTests/CustomError.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
namespace OneBitSoftware.Utilities.OperationResultTests
1+
using OneBitSoftware.Utilities.Errors;
2+
3+
namespace OneBitSoftware.Utilities.OperationResultTests
24
{
3-
internal class CustomError
5+
internal class CustomError : OperationError
46
{
57
public string CustomProperty { get; set; } = null!;
68
}

tests/OneBitSoftware.Utilities.OperationResultTests/CustomErrorPolymorphicConverter.cs

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)