Skip to content

Instantly share code, notes, and snippets.

@dealproc
Created April 21, 2020 18:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dealproc/fadeb1a0e37b4c6aa29558d1415ebf6e to your computer and use it in GitHub Desktop.
Save dealproc/fadeb1a0e37b4c6aa29558d1415ebf6e to your computer and use it in GitHub Desktop.
System.Text.Json parser for slip-based messaging documents
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