2015-05-18 00:14:00 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Serialization ;
using Newtonsoft.Json.Linq ;
using System.Reflection ;
namespace Jellyfish.Virtu
{
public class ArrayConverter : JsonConverter
{
// JSON.NET cannot, when reading, use PreserveReferencesHandling on arrays, although it fully supports it on writing.
2015-05-19 21:39:29 +00:00
// Doing so while being able to fully preserve circular references would require storing the length of the array,
// or reading ahead in the JSON to compute the length. For arrays that could contain reference types, we choose the latter.
// For arrays of primitive types, there is no issue.
2015-05-18 00:14:00 +00:00
// TODO: on serialization, the type of the object is available, but is the expected type (ie, the one that we'll be fed during deserialization) available?
// need this to at least detect covariance cases...
public override bool CanConvert ( Type objectType )
{
if ( ! typeof ( Array ) . IsAssignableFrom ( objectType ) )
return false ;
if ( objectType . GetArrayRank ( ) > 1 )
throw new NotImplementedException ( ) ;
return true ;
}
public override bool CanRead { get { return true ; } }
public override bool CanWrite { get { return true ; } }
2015-05-19 01:20:35 +00:00
private JsonSerializer bareserializer = new JsonSerializer ( ) ; // full default settings, separate context
2015-05-18 00:14:00 +00:00
private static void ReadExpectType ( JsonReader reader , JsonToken expected )
{
if ( ! reader . Read ( ) )
throw new InvalidOperationException ( ) ;
if ( reader . TokenType ! = expected )
throw new InvalidOperationException ( ) ;
}
public override object ReadJson ( JsonReader reader , Type objectType , object existingValue , JsonSerializer serializer )
{
if ( reader . TokenType = = JsonToken . Null )
return null ;
2015-05-19 01:20:35 +00:00
else if ( reader . TokenType ! = JsonToken . StartObject )
2015-05-18 00:14:00 +00:00
throw new InvalidOperationException ( ) ;
ReadExpectType ( reader , JsonToken . PropertyName ) ;
2015-05-19 01:20:35 +00:00
string prop = reader . Value . ToString ( ) ;
ReadExpectType ( reader , JsonToken . String ) ;
2015-05-19 21:39:29 +00:00
string id = reader . Value . ToString ( ) ;
2015-05-19 01:20:35 +00:00
if ( prop = = "$ref" )
2015-05-18 00:14:00 +00:00
{
2015-05-19 21:39:29 +00:00
object ret = serializer . ReferenceResolver . ResolveReference ( serializer , id ) ;
2015-05-19 01:20:35 +00:00
ReadExpectType ( reader , JsonToken . EndObject ) ;
2015-05-19 21:39:29 +00:00
return ret ;
2015-05-19 01:20:35 +00:00
}
else if ( prop = = "$id" )
{
ReadExpectType ( reader , JsonToken . PropertyName ) ;
2015-05-19 21:39:29 +00:00
prop = reader . Value . ToString ( ) ;
if ( prop = = "$length" ) // complex array
{
ReadExpectType ( reader , JsonToken . Integer ) ;
int length = Convert . ToInt32 ( reader . Value ) ;
ReadExpectType ( reader , JsonToken . PropertyName ) ;
if ( reader . Value . ToString ( ) ! = "$values" )
throw new InvalidOperationException ( ) ;
Type elementType = objectType . GetElementType ( ) ;
Array ret = Array . CreateInstance ( elementType , length ) ;
// must register reference before deserializing elements to handle possible circular references
serializer . ReferenceResolver . AddReference ( serializer , id , ret ) ;
int index = 0 ;
ReadExpectType ( reader , JsonToken . StartArray ) ;
while ( true )
{
if ( ! reader . Read ( ) )
throw new InvalidOperationException ( ) ;
if ( reader . TokenType = = JsonToken . EndArray )
break ;
ret . SetValue ( serializer . Deserialize ( reader , elementType ) , index + + ) ;
}
ReadExpectType ( reader , JsonToken . EndObject ) ;
return ret ;
}
else if ( prop = = "$values" ) // simple array
{
if ( ! reader . Read ( ) )
throw new InvalidOperationException ( ) ;
object ret = bareserializer . Deserialize ( reader , objectType ) ;
// OK to add this after deserializing, as arrays of primitive types can't contain backrefs
serializer . ReferenceResolver . AddReference ( serializer , id , ret ) ;
ReadExpectType ( reader , JsonToken . EndObject ) ;
return ret ;
}
else
{
2015-05-18 00:14:00 +00:00
throw new InvalidOperationException ( ) ;
2015-05-19 21:39:29 +00:00
}
2015-05-19 01:20:35 +00:00
}
else
{
throw new InvalidOperationException ( ) ;
2015-05-18 00:14:00 +00:00
}
}
public override void WriteJson ( JsonWriter writer , object value , JsonSerializer serializer )
{
2015-05-19 01:20:35 +00:00
if ( serializer . ReferenceResolver . IsReferenced ( serializer , value ) )
2015-05-18 00:14:00 +00:00
{
writer . WriteStartObject ( ) ;
2015-05-19 21:39:29 +00:00
2015-05-19 01:20:35 +00:00
writer . WritePropertyName ( "$ref" ) ;
writer . WriteValue ( serializer . ReferenceResolver . GetReference ( serializer , value ) ) ;
2015-05-19 21:39:29 +00:00
2015-05-18 00:14:00 +00:00
writer . WriteEndObject ( ) ;
}
else
{
writer . WriteStartObject ( ) ;
2015-05-19 21:39:29 +00:00
2015-05-19 01:20:35 +00:00
writer . WritePropertyName ( "$id" ) ;
writer . WriteValue ( serializer . ReferenceResolver . GetReference ( serializer , value ) ) ;
2015-05-19 21:39:29 +00:00
var elementType = value . GetType ( ) . GetElementType ( ) ;
if ( elementType . IsPrimitive )
{
writer . WritePropertyName ( "$values" ) ;
bareserializer . Serialize ( writer , value ) ;
}
else
{
var array = ( Array ) value ;
writer . WritePropertyName ( "$length" ) ;
writer . WriteValue ( array . Length ) ;
writer . WritePropertyName ( "$values" ) ;
writer . WriteStartArray ( ) ;
foreach ( object o in array )
{
serializer . Serialize ( writer , o , elementType ) ;
}
writer . WriteEndArray ( ) ;
}
2015-05-18 00:14:00 +00:00
writer . WriteEndObject ( ) ;
}
}
}
2015-05-19 23:27:28 +00:00
public class TypeTypeConverter : JsonConverter
{
// serialize and deserialize types, ignoring assembly entirely and only using namespace+typename
// all types, including generic type arguments to supplied types, must be in one of the declared assemblies (only checked on read!)
// the main goal here is to have something with a slight chance of working across versions
public TypeTypeConverter ( IEnumerable < Assembly > ass )
{
assemblies = ass . ToList ( ) ;
}
private List < Assembly > assemblies ;
private Dictionary < string , Type > readlookup = new Dictionary < string , Type > ( ) ;
public override bool CanConvert ( Type objectType )
{
return typeof ( Type ) . IsAssignableFrom ( objectType ) ;
}
public override bool CanRead { get { return true ; } }
public override bool CanWrite { get { return true ; } }
private Type GetType ( string name )
{
Type ret ;
if ( ! readlookup . TryGetValue ( name , out ret ) )
{
ret = assemblies . Select ( ass = > ass . GetType ( name , false ) ) . Where ( t = > t ! = null ) . Single ( ) ;
readlookup . Add ( name , ret ) ;
}
return ret ;
}
private static string GetName ( Type type )
{
return string . Format ( "{0}.{1}" , type . Namespace , type . Name ) ;
}
public override object ReadJson ( JsonReader reader , Type objectType , object existingValue , JsonSerializer serializer )
{
if ( reader . TokenType = = JsonToken . Null )
{
return null ;
}
else if ( reader . TokenType = = JsonToken . String )
{
return GetType ( reader . Value . ToString ( ) ) ;
}
else if ( reader . TokenType = = JsonToken . StartArray ) // full generic
{
List < string > vals = serializer . Deserialize < List < string > > ( reader ) ;
return GetType ( vals [ 0 ] ) . MakeGenericType ( vals . Skip ( 1 ) . Select ( GetType ) . ToArray ( ) ) ;
}
else
{
throw new InvalidOperationException ( ) ;
}
}
public override void WriteJson ( JsonWriter writer , object value , JsonSerializer serializer )
{
var type = ( Type ) value ;
if ( type . IsGenericType & & ! type . IsGenericTypeDefinition )
{
writer . WriteStartArray ( ) ;
writer . WriteValue ( GetName ( type ) ) ;
foreach ( var t in type . GetGenericArguments ( ) )
{
writer . WriteValue ( GetName ( t ) ) ;
}
writer . WriteEndArray ( ) ;
}
else
{
writer . WriteValue ( GetName ( type ) ) ;
}
}
}
2015-05-18 00:14:00 +00:00
public class DelegateConverter : JsonConverter
{
// caveats: if used on anonymous delegates and/or closures, brittle to name changes in the generated classes and methods
// brittle to type name changes in general
// must be serialized in tree with any real classes referred to by closures
2015-05-19 20:42:41 +00:00
// CAN NOT preserve reference equality of the delegates themselves, because the delegate must be created with
// target in one shot, with no possibility to change the target later. We preserve references to targets,
// and lose the ability to preserve references to delegates.
2015-05-18 00:14:00 +00:00
// TODO: much of this could be made somewhat smarter and more resilient
public override bool CanConvert ( Type objectType )
{
return typeof ( Delegate ) . IsAssignableFrom ( objectType ) ;
}
public override bool CanRead { get { return true ; } }
public override bool CanWrite { get { return true ; } }
public override object ReadJson ( JsonReader reader , Type objectType , object existingValue , JsonSerializer serializer )
{
var slug = serializer . Deserialize < Slug > ( reader ) ;
if ( slug = = null )
return null ;
return slug . GetDelegate ( ) ;
}
public override void WriteJson ( JsonWriter writer , object value , JsonSerializer serializer )
{
var slug = new Slug ( ( Delegate ) value ) ;
serializer . Serialize ( writer , slug ) ;
}
private class Slug
{
public Type DelegateType ;
public Type MethodDeclaringType ;
public string MethodName ;
public List < Type > MethodParameters ;
public object Target ;
public Delegate GetDelegate ( )
{
var mi = MethodDeclaringType . GetMethod (
MethodName ,
BindingFlags . NonPublic | BindingFlags . Public | BindingFlags . Instance | BindingFlags . Static ,
null ,
MethodParameters . ToArray ( ) ,
null ) ;
return Delegate . CreateDelegate ( DelegateType , Target , mi ) ;
}
public Slug ( ) { }
public Slug ( Delegate d )
{
DelegateType = d . GetType ( ) ;
MethodDeclaringType = d . Method . DeclaringType ;
MethodName = d . Method . Name ;
MethodParameters = d . Method . GetParameters ( ) . Select ( p = > p . ParameterType ) . ToList ( ) ;
Target = d . Target ;
}
}
}
}