123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Reflection.Emit;
- using System.Reflection;
- using System.Globalization;
- using System.IO;
- namespace PetaJson
- {
- public static class JsonEmit
- {
- public static void Init()
- {
- Json.SetTypeFormatterResolver(Internal.Emit.MakeFormatter);
- Json.SetTypeIntoParserResolver(Internal.Emit.MakeParser);
- }
- }
- namespace Internal
- {
- static class Emit
- {
- public static Action<IJsonWriter, object> MakeFormatter(Type type)
- {
- // Get the reflection info for this type
- var ri = ReflectionInfo.GetReflectionInfo(type);
- if (ri == null)
- return null;
- // Create a dynamic method that can do the work
- var method = new DynamicMethod("dynamic_formatter", null, new Type[] { typeof(IJsonWriter), typeof(object) }, true);
- var il = method.GetILGenerator();
- // Cast/unbox the target object and store in local variable
- var locTypedObj = il.DeclareLocal(type);
- il.Emit(OpCodes.Ldarg_1);
- il.Emit(type.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type);
- il.Emit(OpCodes.Stloc, locTypedObj);
- // Get Invariant CultureInfo
- var locInvariant = il.DeclareLocal(typeof(IFormatProvider));
- il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
- il.Emit(OpCodes.Stloc, locInvariant);
- // These are the types we'll call .ToString(Culture.InvariantCulture) on
- var toStringTypes = new Type[] {
- typeof(int), typeof(uint), typeof(long), typeof(ulong),
- typeof(short), typeof(ushort), typeof(decimal),
- typeof(byte), typeof(sbyte)
- };
- // Theses types we also generate for
- var otherSupportedTypes = new Type[] {
- typeof(double), typeof(float), typeof(string), typeof(char), typeof(bool)
- };
- // Call IJsonWriting if implemented
- if (typeof(IJsonWriting).IsAssignableFrom(type))
- {
- il.Emit(OpCodes.Ldloc, locTypedObj);
- if (type.IsValueType)
- il.Emit(OpCodes.Box, type);
- il.Emit(OpCodes.Castclass, typeof(IJsonWriting));
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriting).GetMethod("OnJsonWriting", new Type[] { typeof(IJsonWriter) }));
- }
- // Process all members
- foreach (var m in ri.Members)
- {
- // Ignore write only properties
- var pi = m.Member as PropertyInfo;
- if (pi!=null && pi.GetGetMethod() == null)
- {
- continue;
- }
- // Write the Json key
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldstr, m.JsonKey);
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteKeyNoEscaping", new Type[] { typeof(string) }));
- // Load the writer
- il.Emit(OpCodes.Ldarg_0);
- // Get the member type
- var memberType = m.MemberType;
- // Load the target object
- if (type.IsValueType)
- {
- il.Emit(OpCodes.Ldloca, locTypedObj);
- }
- else
- {
- il.Emit(OpCodes.Ldloc, locTypedObj);
- }
- // Work out if we need to the value or it's address on the stack
- bool NeedValueAddress = (memberType.IsValueType && (toStringTypes.Contains(memberType) || otherSupportedTypes.Contains(memberType)));
- if (Nullable.GetUnderlyingType(memberType) != null)
- {
- NeedValueAddress = true;
- }
- // Property?
- if (pi != null)
- {
- // Call property's get method
- if (type.IsValueType)
- il.Emit(OpCodes.Call, pi.GetGetMethod());
- else
- il.Emit(OpCodes.Callvirt, pi.GetGetMethod());
- // If we need the address then store in a local and take it's address
- if (NeedValueAddress)
- {
- var locTemp = il.DeclareLocal(memberType);
- il.Emit(OpCodes.Stloc, locTemp);
- il.Emit(OpCodes.Ldloca, locTemp);
- }
- }
- // Field?
- var fi = m.Member as FieldInfo;
- if (fi != null)
- {
- if (NeedValueAddress)
- {
- il.Emit(OpCodes.Ldflda, fi);
- }
- else
- {
- il.Emit(OpCodes.Ldfld, fi);
- }
- }
- Label? lblFinished = null;
- // Is it a nullable type?
- var typeUnderlying = Nullable.GetUnderlyingType(memberType);
- if (typeUnderlying != null)
- {
- // Duplicate the address so we can call get_HasValue() and then get_Value()
- il.Emit(OpCodes.Dup);
- // Define some labels
- var lblHasValue = il.DefineLabel();
- lblFinished = il.DefineLabel();
- // Call has_Value
- il.Emit(OpCodes.Call, memberType.GetProperty("HasValue").GetGetMethod());
- il.Emit(OpCodes.Brtrue, lblHasValue);
- // No value, write "null:
- il.Emit(OpCodes.Pop);
- il.Emit(OpCodes.Ldstr, "null");
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
- il.Emit(OpCodes.Br_S, lblFinished.Value);
- // Get it's value
- il.MarkLabel(lblHasValue);
- il.Emit(OpCodes.Call, memberType.GetProperty("Value").GetGetMethod());
- // Switch to the underlying type from here on
- memberType = typeUnderlying;
- NeedValueAddress = (memberType.IsValueType && (toStringTypes.Contains(memberType) || otherSupportedTypes.Contains(memberType)));
- // Work out again if we need the address of the value
- if (NeedValueAddress)
- {
- var locTemp = il.DeclareLocal(memberType);
- il.Emit(OpCodes.Stloc, locTemp);
- il.Emit(OpCodes.Ldloca, locTemp);
- }
- }
- // ToString()
- if (toStringTypes.Contains(memberType))
- {
- // Convert to string
- il.Emit(OpCodes.Ldloc, locInvariant);
- il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { typeof(IFormatProvider) }));
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
- }
- // ToString("R")
- else if (memberType == typeof(float) || memberType == typeof(double))
- {
- il.Emit(OpCodes.Ldstr, "R");
- il.Emit(OpCodes.Ldloc, locInvariant);
- il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { typeof(string), typeof(IFormatProvider) }));
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
- }
- // String?
- else if (memberType == typeof(string))
- {
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) }));
- }
- // Char?
- else if (memberType == typeof(char))
- {
- il.Emit(OpCodes.Call, memberType.GetMethod("ToString", new Type[] { }));
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) }));
- }
- // Bool?
- else if (memberType == typeof(bool))
- {
- var lblTrue = il.DefineLabel();
- var lblCont = il.DefineLabel();
- il.Emit(OpCodes.Brtrue_S, lblTrue);
- il.Emit(OpCodes.Ldstr, "false");
- il.Emit(OpCodes.Br_S, lblCont);
- il.MarkLabel(lblTrue);
- il.Emit(OpCodes.Ldstr, "true");
- il.MarkLabel(lblCont);
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) }));
- }
- // NB: We don't support DateTime as it's format can be changed
- else
- {
- // Unsupported type, pass through
- if (memberType.IsValueType)
- {
- il.Emit(OpCodes.Box, memberType);
- }
- il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteValue", new Type[] { typeof(object) }));
- }
- if (lblFinished.HasValue)
- il.MarkLabel(lblFinished.Value);
- }
- // Call IJsonWritten
- if (typeof(IJsonWritten).IsAssignableFrom(type))
- {
- il.Emit(OpCodes.Ldloc, locTypedObj);
- if (type.IsValueType)
- il.Emit(OpCodes.Box, type);
- il.Emit(OpCodes.Castclass, typeof(IJsonWritten));
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Callvirt, typeof(IJsonWritten).GetMethod("OnJsonWritten", new Type[] { typeof(IJsonWriter) }));
- }
- // Done!
- il.Emit(OpCodes.Ret);
- // Create delegate to our IL code
- var impl = (Action<IJsonWriter, object>)method.CreateDelegate(typeof(Action<IJsonWriter, object>));
- // Wrap it in a call to WriteDictionary
- return (w, obj) =>
- {
- w.WriteDictionary(() =>
- {
- impl(w, obj);
- });
- };
- }
- public static Action<IJsonReader, object> MakeParser(Type type)
- {
- if (type.IsValueType)
- throw new NotImplementedException("Value types aren't supported through reflection");
- // Get the reflection info for this type
- var ri = ReflectionInfo.GetReflectionInfo(type);
- if (ri == null)
- return null;
- var setters = new Dictionary<string, Action<IJsonReader, object>>();
- // These types we'll call <type>.Parse(reader.String) on
- var numericTypes = new Type[] {
- typeof(int), typeof(uint), typeof(long), typeof(ulong),
- typeof(short), typeof(ushort), typeof(decimal),
- typeof(byte), typeof(sbyte),
- typeof(double), typeof(float)
- };
- // Process all members
- foreach (var m in ri.Members)
- {
- // Ignore write only properties
- var pi = m.Member as PropertyInfo;
- if (pi != null && pi.GetSetMethod() == null)
- {
- continue;
- }
- // Create a dynamic method that can do the work
- var method = new DynamicMethod("dynamic_parser", null, new Type[] { typeof(IJsonReader), typeof(object) }, true);
- var il = method.GetILGenerator();
- if (m.KeepInstance)
- {
- throw new NotImplementedException("Emit KeepInstance not implemented");
- }
- // Load the target
- il.Emit(OpCodes.Ldarg_1);
- il.Emit(OpCodes.Castclass, type);
- Action<string> callHelper = helperName =>
- {
- // check we have a string
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Call, typeof(Emit).GetMethod(helperName, new Type[] { typeof(IJsonReader) }));
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
- };
- if (m.MemberType == typeof(string))
- {
- callHelper("GetLiteralString");
- }
- else if (m.MemberType == typeof(bool))
- {
- callHelper("GetLiteralBool");
- }
- else if (m.MemberType == typeof(char))
- {
- callHelper("GetLiteralChar");
- }
- else if (numericTypes.Contains(m.MemberType))
- {
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Call, typeof(Emit).GetMethod("GetLiteralNumber", new Type[] { typeof(IJsonReader) }));
- // Convert to a string
- il.Emit(OpCodes.Call, typeof(CultureInfo).GetProperty("InvariantCulture").GetGetMethod());
- il.Emit(OpCodes.Call, m.MemberType.GetMethod("Parse", new Type[] { typeof(string), typeof(IFormatProvider) }));
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
- }
- else
- {
- il.Emit(OpCodes.Ldarg_0);
- il.Emit(OpCodes.Ldtoken, m.MemberType);
- il.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
- il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("Parse", new Type[] { typeof(Type) }));
- il.Emit(m.MemberType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, m.MemberType);
- }
- if (pi != null)
- {
- il.Emit(OpCodes.Callvirt, pi.GetSetMethod());
- }
- var fi = m.Member as FieldInfo;
- if (fi != null)
- {
- il.Emit(OpCodes.Stfld, fi);
- }
- il.Emit(OpCodes.Ret);
- // Store the handler in map
- setters.Add(m.JsonKey, (Action<IJsonReader, object>)method.CreateDelegate(typeof(Action<IJsonReader, object>)));
- }
- return (reader, obj) =>
- {
- reader.ReadDictionary(key =>
- {
- var lf = obj as IJsonLoadField;
- if (lf != null)
- {
- if (lf.OnJsonField(reader, key))
- return;
- }
- Action<IJsonReader, object> setter;
- if (setters.TryGetValue(key, out setter))
- {
- setter(reader, obj);
- }
- });
- };
- }
- public static bool GetLiteralBool(IJsonReader r)
- {
- switch (r.GetLiteralKind())
- {
- case LiteralKind.True:
- return true;
- case LiteralKind.False:
- return false;
- default:
- throw new InvalidDataException("expected a boolean value");
- }
- }
- public static char GetLiteralChar(IJsonReader r)
- {
- if (r.GetLiteralKind() != LiteralKind.String)
- throw new InvalidDataException("expected a single character string literal");
- var str = r.GetLiteralString();
- if (str==null || str.Length!=1)
- throw new InvalidDataException("expected a single character string literal");
- return str[0];
- }
- public static string GetLiteralString(IJsonReader r)
- {
- if (r.GetLiteralKind() != LiteralKind.String)
- throw new InvalidDataException("expected a string literal");
- return r.GetLiteralString();
- }
- public static string GetLiteralNumber(IJsonReader r)
- {
- switch (r.GetLiteralKind())
- {
- case LiteralKind.SignedInteger:
- case LiteralKind.UnsignedInteger:
- case LiteralKind.FloatingPoint:
- return r.GetLiteralString();
- }
- throw new InvalidDataException("expected a numeric literal");
- }
- }
- }
- }
|