static Action <IJsonWriter, object> ResolveFormatter(Type type) { // Try `void FormatJson(IJsonWriter)` var formatJson = ReflectionInfo.FindFormatJson(type); if (formatJson != null) { if (formatJson.ReturnType == typeof(void)) { return((w, obj) => formatJson.Invoke(obj, new Object[] { w })); } if (formatJson.ReturnType == typeof(string)) { return((w, obj) => w.WriteStringLiteral((string)formatJson.Invoke(obj, new Object[] { }))); } } var ri = ReflectionInfo.GetReflectionInfo(type); if (ri != null) { return(ri.Write); } else { return(null); } }
// Make a parser for value types public static Func <IJsonReader, Type, object> MakeParser(Type type) { System.Diagnostics.Debug.Assert(type.IsValueType); // ParseJson method? var parseJson = ReflectionInfo.FindParseJson(type); if (parseJson != null) { if (parseJson.GetParameters()[0].ParameterType == typeof(IJsonReader)) { var method = new DynamicMethod("invoke_ParseJson", typeof(Object), new Type[] { typeof(IJsonReader), typeof(Type) }, true); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, parseJson); il.Emit(OpCodes.Box, type); il.Emit(OpCodes.Ret); return((Func <IJsonReader, Type, object>)method.CreateDelegate(typeof(Func <IJsonReader, Type, object>))); } else { var method = new DynamicMethod("invoke_ParseJson", typeof(Object), new Type[] { typeof(string) }, true); var il = method.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, parseJson); il.Emit(OpCodes.Box, type); il.Emit(OpCodes.Ret); var invoke = (Func <string, object>)method.CreateDelegate(typeof(Func <string, object>)); return((r, t) => { if (r.GetLiteralKind() == LiteralKind.String) { var o = invoke(r.GetLiteralString()); r.NextToken(); return o; } throw new InvalidDataException(string.Format("Expected string literal for type {0}", type.FullName)); }); } } else { // Get the reflection info for this type var ri = ReflectionInfo.GetReflectionInfo(type); if (ri == null) { return(null); } // We'll create setters for each property/field var setters = new Dictionary <string, Action <IJsonReader, object> >(); // Store the value in a pseudo box until it's fully initialized var boxType = typeof(PseudoBox <>).MakeGenericType(type); // Process all members foreach (var m in ri.Members) { // Ignore write only properties var pi = m.Member as PropertyInfo; var fi = m.Member as FieldInfo; if (pi != null && pi.GetSetMethod(true) == 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(); // Load the target il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Castclass, boxType); il.Emit(OpCodes.Ldflda, boxType.GetField("value")); // Get the value GenerateGetJsonValue(m, il); // Assign it if (pi != null) { il.Emit(OpCodes.Call, pi.GetSetMethod(true)); } if (fi != null) { il.Emit(OpCodes.Stfld, fi); } // Done il.Emit(OpCodes.Ret); // Store in the map of setters setters.Add(m.JsonKey, (Action <IJsonReader, object>)method.CreateDelegate(typeof(Action <IJsonReader, object>))); } // Create helpers to invoke the interfaces (this is painful but avoids having to really box // the value in order to call the interface). Action <object, IJsonReader> invokeLoading = MakeInterfaceCall(type, typeof(IJsonLoading)); Action <object, IJsonReader> invokeLoaded = MakeInterfaceCall(type, typeof(IJsonLoaded)); Func <object, IJsonReader, string, bool> invokeField = MakeLoadFieldCall(type); // Create the parser Func <IJsonReader, Type, object> parser = (reader, Type) => { // Create pseudobox (ie: new PseudoBox<Type>) var box = DecoratingActivator.CreateInstance(boxType); // Call IJsonLoading if (invokeLoading != null) { invokeLoading(box, reader); } // Read the dictionary reader.ParseDictionary(key => { // Call IJsonLoadField if (invokeField != null && invokeField(box, reader, key)) { return; } // Get a setter and invoke it if found Action <IJsonReader, object> setter; if (setters.TryGetValue(key, out setter)) { setter(reader, box); } }); // IJsonLoaded if (invokeLoaded != null) { invokeLoaded(box, reader); } // Return the value return(((IPseudoBox)box).GetValue()); }; // Done return(parser); } }
// Create an "into parser" that can parse from IJsonReader into a reference type (ie: a class) public static Action <IJsonReader, object> MakeIntoParser(Type type) { System.Diagnostics.Debug.Assert(!type.IsValueType); // Get the reflection info for this type var ri = ReflectionInfo.GetReflectionInfo(type); if (ri == null) { return(null); } // We'll create setters for each property/field var setters = new Dictionary <string, Action <IJsonReader, object> >(); // Process all members foreach (var m in ri.Members) { // Ignore write only properties var pi = m.Member as PropertyInfo; var fi = m.Member as FieldInfo; if (pi != null && pi.GetSetMethod(true) == null) { continue; } // Ignore read only properties that has KeepInstance attribute if (pi != null && pi.GetGetMethod(true) == null && m.KeepInstance) { 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(); // Load the target il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Castclass, type); // Try to keep existing instance? if (m.KeepInstance) { // Get existing existing instance il.Emit(OpCodes.Dup); if (pi != null) { il.Emit(OpCodes.Callvirt, pi.GetGetMethod(true)); } else { il.Emit(OpCodes.Ldfld, fi); } var existingInstance = il.DeclareLocal(m.MemberType); var lblExistingInstanceNull = il.DefineLabel(); // Keep a copy of the existing instance in a locale il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, existingInstance); // Compare to null il.Emit(OpCodes.Ldnull); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brtrue_S, lblExistingInstanceNull); il.Emit(OpCodes.Ldarg_0); // reader il.Emit(OpCodes.Ldloc, existingInstance); // into il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("ParseInto", new Type[] { typeof(Object) })); il.Emit(OpCodes.Pop); // Clean up target left on stack (1) il.Emit(OpCodes.Ret); il.MarkLabel(lblExistingInstanceNull); } // Get the value from IJsonReader GenerateGetJsonValue(m, il); // Assign it if (pi != null) { il.Emit(OpCodes.Callvirt, pi.GetSetMethod(true)); } if (fi != null) { il.Emit(OpCodes.Stfld, fi); } // Done il.Emit(OpCodes.Ret); // Store the handler in map setters.Add(m.JsonKey, (Action <IJsonReader, object>)method.CreateDelegate(typeof(Action <IJsonReader, object>))); } // Now create the parseInto delegate Action <IJsonReader, object> parseInto = (reader, obj) => { // Call IJsonLoading var loading = obj as IJsonLoading; if (loading != null) { loading.OnJsonLoading(reader); } // Cache IJsonLoadField var lf = obj as IJsonLoadField; // Read dictionary keys reader.ParseDictionary(key => { // Call IJsonLoadField if (lf != null && lf.OnJsonField(reader, key)) { return; } // Call setters Action <IJsonReader, object> setter; if (setters.TryGetValue(key, out setter)) { setter(reader, obj); } }); // Call IJsonLoaded var loaded = obj as IJsonLoaded; if (loaded != null) { loaded.OnJsonLoaded(reader); } }; // Since we've created the ParseInto handler, we might as well register // as a Parse handler too. RegisterIntoParser(type, parseInto); // Done return(parseInto); }
// Generates a function that when passed an object of specified type, renders it to an IJsonWriter public static Action <IJsonWriter, object> MakeFormatter(Type type) { var formatJson = ReflectionInfo.FindFormatJson(type); if (formatJson != null) { var method = new DynamicMethod("invoke_formatJson", null, new Type[] { typeof(IJsonWriter), typeof(Object) }, true); var il = method.GetILGenerator(); if (formatJson.ReturnType == typeof(string)) { // w.WriteStringLiteral(o.FormatJson()) il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Unbox, type); il.Emit(OpCodes.Call, formatJson); il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral")); } else { // o.FormatJson(w); il.Emit(OpCodes.Ldarg_1); il.Emit(type.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, type); il.Emit(OpCodes.Ldarg_0); il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, formatJson); } il.Emit(OpCodes.Ret); return((Action <IJsonWriter, object>)method.CreateDelegate(typeof(Action <IJsonWriter, object>))); } else { // 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 (since we'll probably be needing this) 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) }; // Call IJsonWriting if implemented if (typeof(IJsonWriting).IsAssignableFrom(type)) { if (type.IsValueType) { il.Emit(OpCodes.Ldloca, locTypedObj); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, type.GetInterfaceMap(typeof(IJsonWriting)).TargetMethods[0]); } else { il.Emit(OpCodes.Ldloc, locTypedObj); 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) { // Dont save deprecated properties if (m.Deprecated) { continue; } // Ignore write only properties var pi = m.Member as PropertyInfo; if (pi != null && pi.GetGetMethod(true) == null) { continue; } // Get the member type var memberType = m.MemberType; // Get the field/property value and store it in a local LocalBuilder locValue = il.DeclareLocal(memberType); il.Emit(type.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc, locTypedObj); if (pi != null) { il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, pi.GetGetMethod(true)); } if (m.Member is FieldInfo fi) { il.Emit(OpCodes.Ldfld, fi); } il.Emit(OpCodes.Stloc, locValue); // A label for early exit if not writing this member Label lblFinishedMember = il.DefineLabel(); // Helper to generate IL to write the key void EmitWriteKey() { // 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) })); } // Is it a nullable type? var typeUnderlying = Nullable.GetUnderlyingType(memberType); if (typeUnderlying != null) { // Define some labels var lblHasValue = il.DefineLabel(); // Call HasValue il.Emit(OpCodes.Ldloca, locValue); il.Emit(OpCodes.Call, memberType.GetProperty("HasValue").GetGetMethod()); il.Emit(OpCodes.Brtrue, lblHasValue); // HasValue returned false, so either omit the key entirely, or write it as "null" if (!m.ExcludeIfNull) { // Write the key EmitWriteKey(); // No value, write "null" il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, "null"); il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteRaw", new Type[] { typeof(string) })); } il.Emit(OpCodes.Br_S, lblFinishedMember); // Get it's value il.MarkLabel(lblHasValue); il.Emit(OpCodes.Ldloca, locValue); il.Emit(OpCodes.Call, memberType.GetProperty("Value").GetGetMethod()); // Switch to the underlying type from here on locValue = il.DeclareLocal(typeUnderlying); il.Emit(OpCodes.Stloc, locValue); memberType = typeUnderlying; } else { if (m.ExcludeIfNull && !type.IsValueType) { il.Emit(OpCodes.Ldloc, locValue); il.Emit(OpCodes.Brfalse_S, lblFinishedMember); } } if (m.ExcludeIfEquals != null) { il.Emit(OpCodes.Ldloc, locValue); var targetValue = Convert.ChangeType(m.ExcludeIfEquals, m.MemberType); LoadContantFromObject(il, targetValue); il.Emit(OpCodes.Ceq); il.Emit(OpCodes.Brtrue_S, lblFinishedMember); } // ToString() if (toStringTypes.Contains(memberType)) { EmitWriteKey(); il.Emit(OpCodes.Ldarg_0); il.Emit(memberType.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc, locValue); 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)) { EmitWriteKey(); il.Emit(OpCodes.Ldarg_0); il.Emit(memberType.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc, locValue); 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)) { EmitWriteKey(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldloc, locValue); il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteStringLiteral", new Type[] { typeof(string) })); } // Char? else if (memberType == typeof(char)) { EmitWriteKey(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldloca, locValue); 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)) { EmitWriteKey(); il.Emit(OpCodes.Ldarg_0); var lblTrue = il.DefineLabel(); var lblCont = il.DefineLabel(); il.Emit(OpCodes.Ldloc, locValue); 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 { // Load writer il.Emit(OpCodes.Ldarg_0); // Load value il.Emit(OpCodes.Ldloc, locValue); if (memberType.IsValueType) { il.Emit(OpCodes.Box, memberType); } // Write the key and value if (m.ExcludeIfEmpty) { il.Emit(OpCodes.Ldstr, m.JsonKey); il.Emit(OpCodes.Call, typeof(Emit).GetMethod("WriteKeyAndValueCheckIfEmpty")); } else { EmitWriteKey(); il.Emit(OpCodes.Callvirt, typeof(IJsonWriter).GetMethod("WriteValue", new Type[] { typeof(object) })); } } il.MarkLabel(lblFinishedMember); } // Call IJsonWritten if (typeof(IJsonWritten).IsAssignableFrom(type)) { if (type.IsValueType) { il.Emit(OpCodes.Ldloca, locTypedObj); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Call, type.GetInterfaceMap(typeof(IJsonWritten)).TargetMethods[0]); } else { il.Emit(OpCodes.Ldloc, locTypedObj); il.Emit(OpCodes.Castclass, typeof(IJsonWriting)); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Callvirt, typeof(IJsonWriting).GetMethod("OnJsonWritten", new Type[] { typeof(IJsonWriter) })); } } // Done! il.Emit(OpCodes.Ret); 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); }); }); } }