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); } }
// 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); }); }); } }