Skip to content

Instantly share code, notes, and snippets.

@Andrew-Hanlon
Last active August 11, 2017 19:36
Show Gist options
  • Save Andrew-Hanlon/ea9ba203e4ecfef3446f to your computer and use it in GitHub Desktop.
Save Andrew-Hanlon/ea9ba203e4ecfef3446f to your computer and use it in GitHub Desktop.
Json.net WrappedJsonConverter allowing TypeNameHandling and converters to work together.
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Engdata.Common
{
public abstract class JsonConverter<T> : JsonConverter
{
public static JsonConverter<T> From(Func<JsonReader, T, JsonSerializer, T> readFunc,
Action<JsonWriter, T, JsonSerializer> writeFunc)
{
return new SimpleJsonConverter<T>(readFunc, writeFunc);
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public abstract T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer);
public override sealed object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return ReadJson(reader, (T)existingValue, serializer);
}
public abstract void WriteJson(JsonWriter writer, T value, JsonSerializer serializer);
public override sealed void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
WriteJson(writer, (T)value, serializer);
}
}
public class SimpleJsonConverter<T> : JsonConverter<T>
{
private readonly Func<JsonReader, T, JsonSerializer, T> _readFunc;
private readonly Action<JsonWriter, T, JsonSerializer> _writeFunc;
public SimpleJsonConverter(Func<JsonReader, T, JsonSerializer, T> readFunc, Action<JsonWriter, T, JsonSerializer> writeFunc)
{
_readFunc = readFunc;
_writeFunc = writeFunc;
}
public override sealed T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer)
{
return _readFunc(reader, existingValue, serializer);
}
public override sealed void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
_writeFunc(writer, value, serializer);
}
}
public class WrappedJsonConverter<T> : JsonConverter<T> where T : class
{
[ThreadStatic]
private static bool _canWrite = true;
[ThreadStatic]
private static bool _canRead = true;
public override bool CanWrite
{
get
{
if (_canWrite)
return true;
_canWrite = true;
return false;
}
}
public override bool CanRead
{
get
{
if (_canRead)
return true;
_canRead = true;
return false;
}
}
public override T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
JToken token;
T value;
if (!jsonObject.TryGetValue("$wrappedType", out token))
{
//The static _canRead is a terrible hack to get around the serialization loop...
_canRead = false;
value = jsonObject.ToObject<T>(serializer);
_canRead = true;
return value;
}
var typeName = jsonObject.GetValue("$wrappedType").Value<string>();
var type = JsonExtensions.GetTypeFromJsonTypeName(typeName, serializer.Binder);
var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanRead);
var wrappedObjectReader = jsonObject.GetValue("$wrappedValue").CreateReader();
wrappedObjectReader.Read();
if (converter == null)
{
_canRead = false;
value = (T)serializer.Deserialize(wrappedObjectReader, type);
_canRead = true;
}
else
{
value = (T)converter.ReadJson(wrappedObjectReader, type, existingValue, serializer);
}
return value;
}
public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer)
{
var type = value.GetType();
var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanWrite);
if (converter == null)
{
//This is a terrible hack to get around the serialization loop...
_canWrite = false;
serializer.Serialize(writer, value, type);
_canWrite = true;
return;
}
writer.WriteStartObject();
{
writer.WritePropertyName("$wrappedType");
writer.WriteValue(type.GetJsonSimpleTypeName());
writer.WritePropertyName("$wrappedValue");
converter.WriteJson(writer, value, serializer);
}
writer.WriteEndObject();
}
}
public static class JsonExtensions
{
//Mostly taken from Json.net source
public static string GetJsonSimpleTypeName(this Type type)
{
var fullyQualifiedTypeName = type.AssemblyQualifiedName;
var builder = new StringBuilder();
// loop through the type name and filter out qualified assembly details from nested type names
bool writingAssemblyName = false;
bool skippingAssemblyDetails = false;
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
{
char current = fullyQualifiedTypeName[i];
switch (current)
{
case '[':
writingAssemblyName = false;
skippingAssemblyDetails = false;
builder.Append(current);
break;
case ']':
writingAssemblyName = false;
skippingAssemblyDetails = false;
builder.Append(current);
break;
case ',':
if (!writingAssemblyName)
{
writingAssemblyName = true;
builder.Append(current);
}
else
{
skippingAssemblyDetails = true;
}
break;
default:
if (!skippingAssemblyDetails)
builder.Append(current);
break;
}
}
return builder.ToString();
}
//Mostly taken from Json.net source
public static Type GetTypeFromJsonTypeName(string jsonTypeName, SerializationBinder binder)
{
string typeName, assemblyName;
int? assemblyDelimiterIndex = GetAssemblyDelimiterIndex(jsonTypeName);
if (assemblyDelimiterIndex != null)
{
typeName = jsonTypeName.Substring(0, assemblyDelimiterIndex.Value).Trim();
assemblyName = jsonTypeName.Substring(assemblyDelimiterIndex.Value + 1, jsonTypeName.Length - assemblyDelimiterIndex.Value - 1).Trim();
}
else
{
typeName = jsonTypeName;
assemblyName = null;
}
return binder.BindToType(assemblyName, typeName);
}
//Taken from Json.net source
private static int? GetAssemblyDelimiterIndex(string fullyQualifiedTypeName)
{
// we need to get the first comma following all surrounded in brackets because of generic types
// e.g. System.Collections.Generic.Dictionary`2[[System.String, mscorlib,Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
int scope = 0;
for (int i = 0; i < fullyQualifiedTypeName.Length; i++)
{
char current = fullyQualifiedTypeName[i];
switch (current)
{
case '[':
scope++;
break;
case ']':
scope--;
break;
case ',':
if (scope == 0)
return i;
break;
}
}
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment