Created
April 21, 2020 18:17
-
-
Save dealproc/fadeb1a0e37b4c6aa29558d1415ebf6e to your computer and use it in GitHub Desktop.
System.Text.Json parser for slip-based messaging documents
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
namespace YourNamespace { | |
public class DocumentConverter : JsonConverterFactory { | |
public override bool CanConvert(Type typeToConvert) => typeof(IDocument).IsAssignableFrom(typeToConvert); | |
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) { | |
JsonConverter converter = (JsonConverter) Activator.CreateInstance(typeof(Converter<>).MakeGenericType(new [] { typeToConvert }), | |
BindingFlags.Instance | BindingFlags.Public, | |
binder : null, | |
args : null, | |
culture : null); | |
return converter; | |
} | |
private class Converter<TDocument> : JsonConverter<TDocument> where TDocument : IDocument { | |
public override TDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { | |
var doc = JsonDocument.ParseValue(ref reader); | |
var obj = doc.RootElement; | |
var baseProperties = obj.EnumerateObject().ToList(); | |
var routes = new List<RoutingSlip>(); | |
foreach (var r in baseProperties.Single(p => p.Name.Equals("routes")).Value.EnumerateArray()) { | |
routes.Add(JsonSerializer.Deserialize<RoutingSlip>(r.GetRawText())); | |
} | |
var onCompletedText = baseProperties.Single(p => p.Name.Equals("onCompleted", StringComparison.InvariantCultureIgnoreCase)).Value.GetRawText(); | |
var onErrorText = baseProperties.Single(p => p.Name.Equals("onError", StringComparison.InvariantCultureIgnoreCase)).Value.GetRawText(); | |
var onTimeoutText = baseProperties.Single(p => p.Name.Equals("onTimeout", StringComparison.InvariantCultureIgnoreCase)).Value.GetRawText(); | |
var onCompleted = JsonSerializer.Deserialize<RoutingSlip>(onCompletedText, options); | |
var onError = JsonSerializer.Deserialize<RoutingSlip>(onErrorText, options); | |
var onTimeout = JsonSerializer.Deserialize<RoutingSlip>(onTimeoutText, options); | |
var table = new RoutingTable(routes, onCompleted, onError, onTimeout); | |
var result = (TDocument) Activator.CreateInstance(typeof(TDocument), new [] { table }); | |
result.Id = new DocumentId(baseProperties.Single(p => p.Name.Equals("id", StringComparison.InvariantCultureIgnoreCase)).Value.GetString()); | |
result.CausationId = new DocumentId(baseProperties.Single(p => p.Name.Equals("causationId", StringComparison.InvariantCultureIgnoreCase)).Value.GetString()); | |
result.Expiry = baseProperties.Single(p => p.Name.Equals("expiry", StringComparison.InvariantCultureIgnoreCase)).Value.GetDateTime(); | |
if (baseProperties.Exists(p => p.Name.Equals("exception", StringComparison.InvariantCultureIgnoreCase))) { | |
var exceptionJson = baseProperties | |
.Single(p => p.Name.Equals("exception", StringComparison.InvariantCultureIgnoreCase)); | |
var props = exceptionJson.Value | |
.EnumerateObject() | |
.ToArray(); | |
var exc = (Exception) props.Single(p => p.Name.Equals("type", StringComparison.InvariantCultureIgnoreCase)).Value.GetString().GetInstance(); | |
var excMessageProp = exc.GetType().GetProperty("Message"); | |
excMessageProp.SetValue(exc, props.Single(p => p.Name.Equals("message", StringComparison.InvariantCultureIgnoreCase)).Value.GetString()); | |
exc.Source = props.Single(p => p.Name.Equals("source", StringComparison.InvariantCultureIgnoreCase)).Value.GetString(); | |
var excStackTraceProp = exc.GetType().GetProperty("StackTrace"); | |
excStackTraceProp.SetValue(exc, props.Single(p => p.Name.Equals("stackTrace", StringComparison.InvariantCultureIgnoreCase)).Value.GetString()); | |
exc.HelpLink = props.Single(p => p.Name.Equals("helpLink", StringComparison.InvariantCultureIgnoreCase)).Value.GetString(); | |
var excProp = result.GetType().GetProperty("Exception"); | |
excProp.SetValue(result, exc); | |
} | |
var data = obj.GetProperty("data"); | |
var resultProps = result.GetType().GetProperties(); | |
foreach (var d in data.EnumerateObject()) { | |
var pi = resultProps.SingleOrDefault(p => p.Name.Equals(d.Name, StringComparison.OrdinalIgnoreCase)); | |
if (pi != null) { | |
if(pi.PropertyType.IsPrimitive || pi.PropertyType == typeof(string)){ | |
pi.SetValue(result, TypeDescriptor.GetConverter(pi.PropertyType).ConvertFromInvariantString(d.Value.GetString())); | |
}else{ | |
var pValue = d.Value.GetRawText(); | |
var pObject = JsonSerializer.Deserialize(pValue, pi.PropertyType, options); | |
pi.SetValue(result, pObject); | |
} | |
} | |
} | |
return result; | |
} | |
static readonly string[] excluded_properties = { "id", "causationid", "routes", "exception", "expiry" }; | |
public override void Write(Utf8JsonWriter writer, TDocument value, JsonSerializerOptions options) { | |
Func<string, string> namingStrategy = (s) => options.PropertyNamingPolicy?.ConvertName(s) ?? s; | |
writer.WriteStartObject(); | |
writer.WriteString(namingStrategy(nameof(value.Id)), value.Id.ToString()); | |
writer.WriteString("type", value.GetType().Name); | |
writer.WriteString(namingStrategy(nameof(value.CausationId)), value.CausationId.ToString()); | |
// write the "data" of the document. | |
writer.WriteStartObject("data"); | |
var t = value.GetType(); | |
var props = t.GetProperties() | |
.Where(p => !excluded_properties.Contains(p.Name, StringComparer.OrdinalIgnoreCase)) | |
.ToArray(); | |
foreach (var prop in props) { | |
if (prop.PropertyType.IsPrimitive || prop.PropertyType == typeof(string)) { | |
writer.WriteString(namingStrategy(prop.Name), prop.GetValue(value).ToString()); | |
} else { | |
writer.WritePropertyName(namingStrategy(prop.Name)); | |
JsonSerializer.Serialize(writer, prop.GetValue(value), options); | |
} | |
} | |
writer.WriteEndObject(); | |
// write the "data" of the document. | |
writer.WriteString(namingStrategy(nameof(value.Routes.Direction)), value.Routes.Direction.ToString()); | |
if (value.Exception != null) { | |
writer.WriteStartObject("exception"); | |
writer.WriteString("type", value.Exception.GetType().Name); | |
writer.WriteString("message", value.Exception.Message); | |
writer.WriteString("source", value.Exception.Source); | |
writer.WriteString("stackTrace", value.Exception.StackTrace); | |
writer.WriteString("helpLink", value.Exception.HelpLink); | |
writer.WriteEndObject(); | |
} | |
writer.WriteString("expiry", value.Expiry); | |
writer.WriteStartArray("routes"); | |
foreach (var route in value.Routes.Routes) { | |
JsonSerializer.Serialize(writer, route, options); | |
} | |
writer.WriteEndArray(); | |
writer.WritePropertyName("onCompleted"); | |
JsonSerializer.Serialize(writer, value.Routes.OnCompleted, options); | |
writer.WritePropertyName("onError"); | |
JsonSerializer.Serialize(writer, value.Routes.OnError, options); | |
writer.WritePropertyName("onTimeout"); | |
JsonSerializer.Serialize(writer, value.Routes.OnTimeout, options); | |
writer.WriteEndObject(); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment