Last active
August 11, 2017 19:36
-
-
Save Andrew-Hanlon/ea9ba203e4ecfef3446f to your computer and use it in GitHub Desktop.
Json.net WrappedJsonConverter allowing TypeNameHandling and converters to work together.
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.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