소스 검색

Added support for ExcludeIfEmpty (for collections) and ExcudeIfEquals (for simple value types)

Brad Robinson 3 년 전
부모
커밋
6903ac1041

+ 82 - 0
Topten.JsonKit.Test/TestExcludeIfEmpty.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Topten.JsonKit;
+using System.IO;
+using System.Reflection;
+using Xunit;
+
+namespace TestCases
+{
+    [Obfuscation(Exclude = true, ApplyToMembers = true)]
+    public class TestExcludeIfEmpty
+    {
+        class Thing
+        {
+            [Json("array", ExcludeIfEmpty = true)]
+            public string[] Array;
+
+            [Json("dictionary", ExcludeIfEmpty = true)]
+            public Dictionary<string, object> Dictionary;
+
+            [Json("list", ExcludeIfEmpty = true)]
+            public List<string> List;
+        }
+
+        [Fact]
+        public void TestDoesntWriteNull()
+        {
+            var thing = new Thing();
+
+            // Save it
+            var json = Json.Format(thing);
+
+            // Check the object kinds were written out
+            Assert.DoesNotContain("\"array\":", json);
+            Assert.DoesNotContain("\"dictionary\":", json);
+            Assert.DoesNotContain("\"list\":", json);
+        }
+
+        [Fact]
+        public void TestDoesntWriteEmpty()
+        {
+            var thing = new Thing()
+            {
+                Array = new string[0],
+                Dictionary = new Dictionary<string, object>(),
+                List = new List<string>(),
+            };
+
+            // Save it
+            var json = Json.Format(thing);
+
+            // Check the object kinds were written out
+            Assert.DoesNotContain("\"array\":", json);
+            Assert.DoesNotContain("\"dictionary\":", json);
+            Assert.DoesNotContain("\"list\":", json);
+        }
+
+        [Fact]
+        public void TestDoesWriteNonEmpty()
+        {
+            var thing = new Thing()
+            {
+                Array = new string[] { "apples" },
+                Dictionary = new Dictionary<string, object>() { { "pears", true } },
+                List = new List<string> { "bananas" },
+            };
+
+            // Save it
+            var json = Json.Format(thing);
+
+            // Check the object kinds were written out
+            Assert.Contains("\"array\":", json);
+            Assert.Contains("\"dictionary\":", json);
+            Assert.Contains("\"list\":", json);
+            Assert.Contains("\"apples\"", json);
+            Assert.Contains("\"pears\"", json);
+            Assert.Contains("\"bananas\"", json);
+        }
+    }
+}

+ 106 - 0
Topten.JsonKit.Test/TestExcludeIfEquals.cs

@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Topten.JsonKit;
+using System.IO;
+using System.Reflection;
+using Xunit;
+
+namespace TestCases
+{
+    [Obfuscation(Exclude = true, ApplyToMembers = true)]
+    public class TestExcludeIfEquals
+    {
+        enum Fruit
+        {
+            Apples,
+            Pears,
+            Bananas,
+        }
+        class Thing
+        {
+            [Json("boolField", ExcludeIfEquals = false)]
+            public bool boolField;
+
+            [Json("intField", ExcludeIfEquals = 0)]
+            public int intField;
+
+            [Json("boolProperty", ExcludeIfEquals = false)]
+            public bool boolProperty { get; set; }
+
+            [Json("intProperty", ExcludeIfEquals = 0)]
+            public int intProperty { get; set; }
+
+            [Json("enumField", ExcludeIfEquals = Fruit.Apples)]
+            public Fruit enumField;
+
+            [Json("enumProperty", ExcludeIfEquals = Fruit.Apples)]
+            public Fruit enumProperty { get; set; }
+        }
+
+        public static object GetDefault(Type type)
+        {
+            if (type.IsValueType)
+            {
+                return Activator.CreateInstance(type);
+            }
+            return null;
+        }
+
+        [Fact]
+        public void TestDoesntWrite()
+        {
+            var thing = new Thing()
+            {
+                boolField = false,
+                intField = 0,
+                boolProperty = false,
+                intProperty = 0,
+                enumField = Fruit.Apples,
+                enumProperty = Fruit.Apples,
+            };
+
+            // Save it
+            var json = Json.Format(thing);
+
+            // Check the object kinds were written out
+            Assert.DoesNotContain("\"boolField\":", json);
+            Assert.DoesNotContain("\"intField\":", json);
+            Assert.DoesNotContain("\"boolProperty\":", json);
+            Assert.DoesNotContain("\"intProperty\":", json);
+            Assert.DoesNotContain("\"enumField\":", json);
+            Assert.DoesNotContain("\"enumProperty\":", json);
+        }
+
+        [Fact]
+        public void TestDoesWriteNonNull()
+        {
+            var thing = new Thing()
+            {
+                boolField = true,
+                intField = 23,
+                boolProperty = true,
+                intProperty = 24,
+                enumField = Fruit.Pears,
+                enumProperty = Fruit.Bananas,
+            };
+
+            // Save it
+            var json = Json.Format(thing);
+
+            // Check the object kinds were written out
+            Assert.Contains("\"boolField\":", json);
+            Assert.Contains("\"intField\":", json);
+            Assert.Contains("\"boolProperty\":", json);
+            Assert.Contains("\"intProperty\":", json);
+            Assert.Contains("\"enumField\":", json);
+            Assert.Contains("\"enumProperty\":", json);
+            Assert.Contains("true", json);
+            Assert.Contains("23", json);
+            Assert.Contains("24", json);
+            Assert.Contains("Pears", json);
+            Assert.Contains("Bananas", json);
+        }
+    }
+}

+ 863 - 836
Topten.JsonKit/Emit.cs

@@ -1,837 +1,864 @@
-// JsonKit v0.5 - A simple but flexible Json library in a single .cs file.
-// 
-// Copyright (C) 2014 Topten Software (contact@toptensoftware.com) All rights reserved.
-// 
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product 
-// except in compliance with the License. You may obtain a copy of the License at
-// 
-// http://www.apache.org/licenses/LICENSE-2.0
-// 
-// Unless required by applicable law or agreed to in writing, software distributed under the 
-// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
-// either express or implied. See the License for the specific language governing permissions 
-// and limitations under the License.
-
-#if !JSONKIT_NO_EMIT
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.IO;
-using System.Reflection;
-using System.Globalization;
-using System.Reflection.Emit;
-
-namespace Topten.JsonKit
-{
-    static class Emit
-    {
-
-        // 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;
-                    }
-
-                    // 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 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(true));
-                        else
-                            il.Emit(OpCodes.Callvirt, pi.GetGetMethod(true));
-
-                        // 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 = il.DefineLabel();
-
-                    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.Dup);
-                        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)
-                        {
-                            il.Emit(OpCodes.Pop);       // Pop value
-                            il.Emit(OpCodes.Pop);       // Pop writer
-                        }
-                        else
-                        {
-                            // Write the key
-                            EmitWriteKey();
-
-                            // 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);
-
-                        // Get it's value
-                        il.MarkLabel(lblHasValue);
-                        EmitWriteKey();
-                        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);
-                        }
-                    }
-                    else
-                    {
-                        if (m.ExcludeIfNull && !type.IsValueType)
-                        {
-                            var lblContinue = il.DefineLabel();
-                            System.Diagnostics.Debug.Assert(!NeedValueAddress);
-                            il.Emit(OpCodes.Dup);
-                            il.Emit(OpCodes.Brtrue_S, lblContinue);
-
-                            il.Emit(OpCodes.Pop);           // Pop value
-                            il.Emit(OpCodes.Pop);           // Pop writer
-
-                            // Define some labels
-                            il.Emit(OpCodes.Br_S, lblFinished);
-
-                            il.MarkLabel(lblContinue);
-                        }
-
-                        EmitWriteKey();
-                    }
-
-                    // 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) }));
-                    }
-
-                    il.MarkLabel(lblFinished);
-                }
-
-                // 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);
-                    });
-                };
-            }
-        }
-
-        // Pseudo box lets us pass a value type by reference.  Used during 
-        // deserialization of value types.
-        interface IPseudoBox
-        {
-            object GetValue();
-        }
-        [Obfuscation(Exclude = true, ApplyToMembers = true)]
-        class PseudoBox<T> : IPseudoBox where T : struct
-        {
-            public T value = default(T);
-            object IPseudoBox.GetValue() { return value; }
-        }
-
-
-        // 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;
-            }
-        }
-
-        // Helper to make the call to a PsuedoBox value's IJsonLoading or IJsonLoaded
-        static Action<object, IJsonReader> MakeInterfaceCall(Type type, Type tItf)
-        {
-            // Interface supported?
-            if (!tItf.IsAssignableFrom(type))
-                return null;
-
-            // Resolve the box type
-            var boxType = typeof(PseudoBox<>).MakeGenericType(type);
-
-            // Create method
-            var method = new DynamicMethod("dynamic_invoke_" + tItf.Name, null, new Type[] { typeof(object), typeof(IJsonReader) }, true);
-            var il = method.GetILGenerator();
-
-            // Call interface method
-            il.Emit(OpCodes.Ldarg_0);
-            il.Emit(OpCodes.Castclass, boxType);
-            il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
-            il.Emit(OpCodes.Ldarg_1);
-            il.Emit(OpCodes.Call, type.GetInterfaceMap(tItf).TargetMethods[0]);
-            il.Emit(OpCodes.Ret);
-
-            // Done
-            return (Action<object, IJsonReader>)method.CreateDelegate(typeof(Action<object, IJsonReader>));
-        }
-
-        // Similar to above but for IJsonLoadField
-        static Func<object, IJsonReader, string, bool> MakeLoadFieldCall(Type type)
-        {
-            // Interface supported?
-            var tItf = typeof(IJsonLoadField);
-            if (!tItf.IsAssignableFrom(type))
-                return null;
-
-            // Resolve the box type
-            var boxType = typeof(PseudoBox<>).MakeGenericType(type);
-
-            // Create method
-            var method = new DynamicMethod("dynamic_invoke_" + tItf.Name, typeof(bool), new Type[] { typeof(object), typeof(IJsonReader), typeof(string) }, true);
-            var il = method.GetILGenerator();
-
-            // Call interface method
-            il.Emit(OpCodes.Ldarg_0);
-            il.Emit(OpCodes.Castclass, boxType);
-            il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
-            il.Emit(OpCodes.Ldarg_1);
-            il.Emit(OpCodes.Ldarg_2);
-            il.Emit(OpCodes.Call, type.GetInterfaceMap(tItf).TargetMethods[0]);
-            il.Emit(OpCodes.Ret);
-
-            // Done
-            return (Func<object, IJsonReader, string, bool>)method.CreateDelegate(typeof(Func<object, IJsonReader, string, bool>));
-        }
-
-        // 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;
-        }
-
-        // Registers a ParseInto handler as Parse handler that instantiates the object
-        // and then parses into it.
-        static void RegisterIntoParser(Type type, Action<IJsonReader, object> parseInto)
-        {
-            // Check type has a parameterless constructor
-            var con = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
-            if (con == null)
-                return;
-
-            // Create a dynamic method that can do the work
-            var method = new DynamicMethod("dynamic_factory", typeof(object), new Type[] { typeof(IJsonReader), typeof(Action<IJsonReader, object>) }, true);
-            var il = method.GetILGenerator();
-
-            // Create the new object
-            var locObj = il.DeclareLocal(typeof(object));
-            il.Emit(OpCodes.Newobj, con);
-
-            il.Emit(OpCodes.Dup);               // For return value
-
-            il.Emit(OpCodes.Stloc, locObj);
-
-            il.Emit(OpCodes.Ldarg_1);           // parseinto delegate
-            il.Emit(OpCodes.Ldarg_0);           // IJsonReader
-            il.Emit(OpCodes.Ldloc, locObj);     // new object instance
-            il.Emit(OpCodes.Callvirt, typeof(Action<IJsonReader, object>).GetMethod("Invoke"));
-            il.Emit(OpCodes.Ret);
-
-            var factory = (Func<IJsonReader, Action<IJsonReader, object>, object>)method.CreateDelegate(typeof(Func<IJsonReader, Action<IJsonReader, object>, object>));
-
-            Json.RegisterParser(type, (reader, type2) =>
-            {
-                return factory(reader, parseInto);
-            });
-        }
-
-        // Generate the MSIL to retrieve a value for a particular field or property from a IJsonReader
-        private static void GenerateGetJsonValue(JsonMemberInfo m, ILGenerator il)
-        {
-            Action<string> generateCallToHelper = helperName =>
-            {
-                // Call the helper
-                il.Emit(OpCodes.Ldarg_0);
-                il.Emit(OpCodes.Call, typeof(Emit).GetMethod(helperName, new Type[] { typeof(IJsonReader) }));
-
-                // Move to next token
-                il.Emit(OpCodes.Ldarg_0);
-                il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
-            };
-
-            Type[] 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)
-            };
-
-            if (m.MemberType == typeof(string))
-            {
-                generateCallToHelper("GetLiteralString");
-            }
-
-            else if (m.MemberType == typeof(bool))
-            {
-                generateCallToHelper("GetLiteralBool");
-            }
-
-            else if (m.MemberType == typeof(char))
-            {
-                generateCallToHelper("GetLiteralChar");
-            }
-
-            else if (numericTypes.Contains(m.MemberType))
-            {
-                // Get raw number string
-                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);
-            }
-        }
-
-        // Helper to fetch a literal bool from an IJsonReader
-        [Obfuscation(Exclude = true)]
-        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");
-            }
-        }
-
-        // Helper to fetch a literal character from an IJsonReader
-        [Obfuscation(Exclude = true)]
-        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];
-        }
-
-        // Helper to fetch a literal string from an IJsonReader
-        [Obfuscation(Exclude = true)]
-        public static string GetLiteralString(IJsonReader r)
-        {
-            switch (r.GetLiteralKind())
-            {
-                case LiteralKind.Null: return null;
-                case LiteralKind.String: return r.GetLiteralString();
-            }
-            throw new InvalidDataException("expected a string literal");
-        }
-
-        // Helper to fetch a literal number from an IJsonReader (returns the raw string)
-        [Obfuscation(Exclude = true)]
-        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");
-        }
-    }
-}
-
+// JsonKit v0.5 - A simple but flexible Json library in a single .cs file.
+// 
+// Copyright (C) 2014 Topten Software (contact@toptensoftware.com) All rights reserved.
+// 
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product 
+// except in compliance with the License. You may obtain a copy of the License at
+// 
+// http://www.apache.org/licenses/LICENSE-2.0
+// 
+// Unless required by applicable law or agreed to in writing, software distributed under the 
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 
+// either express or implied. See the License for the specific language governing permissions 
+// and limitations under the License.
+
+#if !JSONKIT_NO_EMIT
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Reflection;
+using System.Globalization;
+using System.Reflection.Emit;
+
+namespace Topten.JsonKit
+{
+    static class Emit
+    {
+
+        // 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);
+                    });
+                };
+            }
+        }
+
+        static void LoadContantFromObject(ILGenerator il, object value)
+        {
+            switch (Type.GetTypeCode(value.GetType()))
+            {
+                case TypeCode.Boolean:
+                    il.Emit(OpCodes.Ldc_I4, ((bool)value) ? 1 : 0);
+                    break;
+
+                case TypeCode.Char:
+                case TypeCode.SByte:
+                case TypeCode.Byte:
+                case TypeCode.Int16:
+                case TypeCode.UInt16:
+                case TypeCode.Int32:
+                case TypeCode.UInt32:
+                    il.Emit(OpCodes.Ldc_I4, (int)value);
+                    break;
+
+                case TypeCode.Int64:
+                case TypeCode.UInt64:
+                    il.Emit(OpCodes.Ldc_I8, (long)value);
+                    break;
+
+                case TypeCode.Single:
+                    il.Emit(OpCodes.Ldc_R4, (float)value);
+                    break;
+
+                case TypeCode.Double:
+                    il.Emit(OpCodes.Ldc_R8, (double)value);
+                    break;
+
+                default:
+                    throw new InvalidOperationException($"JsonKit doesn't support the type `{value.GetType().ToString()}` for ExcludeIfEquals");
+            }
+        }
+
+        public static void WriteKeyAndValueCheckIfEmpty(IJsonWriter w, object o, string key)
+        {
+            if (o == null)
+                return;
+
+            // Check if empty
+            if (o is System.Collections.IEnumerable e)
+            {
+                if (!e.GetEnumerator().MoveNext())
+                    return;
+            }
+
+            w.WriteKeyNoEscaping(key);
+            w.WriteValue(o);
+        }
+
+        // Pseudo box lets us pass a value type by reference.  Used during 
+        // deserialization of value types.
+        interface IPseudoBox
+        {
+            object GetValue();
+        }
+        [Obfuscation(Exclude = true, ApplyToMembers = true)]
+        class PseudoBox<T> : IPseudoBox where T : struct
+        {
+            public T value = default(T);
+            object IPseudoBox.GetValue() { return value; }
+        }
+
+
+        // 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;
+            }
+        }
+
+        // Helper to make the call to a PsuedoBox value's IJsonLoading or IJsonLoaded
+        static Action<object, IJsonReader> MakeInterfaceCall(Type type, Type tItf)
+        {
+            // Interface supported?
+            if (!tItf.IsAssignableFrom(type))
+                return null;
+
+            // Resolve the box type
+            var boxType = typeof(PseudoBox<>).MakeGenericType(type);
+
+            // Create method
+            var method = new DynamicMethod("dynamic_invoke_" + tItf.Name, null, new Type[] { typeof(object), typeof(IJsonReader) }, true);
+            var il = method.GetILGenerator();
+
+            // Call interface method
+            il.Emit(OpCodes.Ldarg_0);
+            il.Emit(OpCodes.Castclass, boxType);
+            il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
+            il.Emit(OpCodes.Ldarg_1);
+            il.Emit(OpCodes.Call, type.GetInterfaceMap(tItf).TargetMethods[0]);
+            il.Emit(OpCodes.Ret);
+
+            // Done
+            return (Action<object, IJsonReader>)method.CreateDelegate(typeof(Action<object, IJsonReader>));
+        }
+
+        // Similar to above but for IJsonLoadField
+        static Func<object, IJsonReader, string, bool> MakeLoadFieldCall(Type type)
+        {
+            // Interface supported?
+            var tItf = typeof(IJsonLoadField);
+            if (!tItf.IsAssignableFrom(type))
+                return null;
+
+            // Resolve the box type
+            var boxType = typeof(PseudoBox<>).MakeGenericType(type);
+
+            // Create method
+            var method = new DynamicMethod("dynamic_invoke_" + tItf.Name, typeof(bool), new Type[] { typeof(object), typeof(IJsonReader), typeof(string) }, true);
+            var il = method.GetILGenerator();
+
+            // Call interface method
+            il.Emit(OpCodes.Ldarg_0);
+            il.Emit(OpCodes.Castclass, boxType);
+            il.Emit(OpCodes.Ldflda, boxType.GetField("value"));
+            il.Emit(OpCodes.Ldarg_1);
+            il.Emit(OpCodes.Ldarg_2);
+            il.Emit(OpCodes.Call, type.GetInterfaceMap(tItf).TargetMethods[0]);
+            il.Emit(OpCodes.Ret);
+
+            // Done
+            return (Func<object, IJsonReader, string, bool>)method.CreateDelegate(typeof(Func<object, IJsonReader, string, bool>));
+        }
+
+        // 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;
+        }
+
+        // Registers a ParseInto handler as Parse handler that instantiates the object
+        // and then parses into it.
+        static void RegisterIntoParser(Type type, Action<IJsonReader, object> parseInto)
+        {
+            // Check type has a parameterless constructor
+            var con = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null);
+            if (con == null)
+                return;
+
+            // Create a dynamic method that can do the work
+            var method = new DynamicMethod("dynamic_factory", typeof(object), new Type[] { typeof(IJsonReader), typeof(Action<IJsonReader, object>) }, true);
+            var il = method.GetILGenerator();
+
+            // Create the new object
+            var locObj = il.DeclareLocal(typeof(object));
+            il.Emit(OpCodes.Newobj, con);
+
+            il.Emit(OpCodes.Dup);               // For return value
+
+            il.Emit(OpCodes.Stloc, locObj);
+
+            il.Emit(OpCodes.Ldarg_1);           // parseinto delegate
+            il.Emit(OpCodes.Ldarg_0);           // IJsonReader
+            il.Emit(OpCodes.Ldloc, locObj);     // new object instance
+            il.Emit(OpCodes.Callvirt, typeof(Action<IJsonReader, object>).GetMethod("Invoke"));
+            il.Emit(OpCodes.Ret);
+
+            var factory = (Func<IJsonReader, Action<IJsonReader, object>, object>)method.CreateDelegate(typeof(Func<IJsonReader, Action<IJsonReader, object>, object>));
+
+            Json.RegisterParser(type, (reader, type2) =>
+            {
+                return factory(reader, parseInto);
+            });
+        }
+
+        // Generate the MSIL to retrieve a value for a particular field or property from a IJsonReader
+        private static void GenerateGetJsonValue(JsonMemberInfo m, ILGenerator il)
+        {
+            Action<string> generateCallToHelper = helperName =>
+            {
+                // Call the helper
+                il.Emit(OpCodes.Ldarg_0);
+                il.Emit(OpCodes.Call, typeof(Emit).GetMethod(helperName, new Type[] { typeof(IJsonReader) }));
+
+                // Move to next token
+                il.Emit(OpCodes.Ldarg_0);
+                il.Emit(OpCodes.Callvirt, typeof(IJsonReader).GetMethod("NextToken", new Type[] { }));
+            };
+
+            Type[] 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)
+            };
+
+            if (m.MemberType == typeof(string))
+            {
+                generateCallToHelper("GetLiteralString");
+            }
+
+            else if (m.MemberType == typeof(bool))
+            {
+                generateCallToHelper("GetLiteralBool");
+            }
+
+            else if (m.MemberType == typeof(char))
+            {
+                generateCallToHelper("GetLiteralChar");
+            }
+
+            else if (numericTypes.Contains(m.MemberType))
+            {
+                // Get raw number string
+                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);
+            }
+        }
+
+        // Helper to fetch a literal bool from an IJsonReader
+        [Obfuscation(Exclude = true)]
+        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");
+            }
+        }
+
+        // Helper to fetch a literal character from an IJsonReader
+        [Obfuscation(Exclude = true)]
+        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];
+        }
+
+        // Helper to fetch a literal string from an IJsonReader
+        [Obfuscation(Exclude = true)]
+        public static string GetLiteralString(IJsonReader r)
+        {
+            switch (r.GetLiteralKind())
+            {
+                case LiteralKind.Null: return null;
+                case LiteralKind.String: return r.GetLiteralString();
+            }
+            throw new InvalidDataException("expected a string literal");
+        }
+
+        // Helper to fetch a literal number from an IJsonReader (returns the raw string)
+        [Obfuscation(Exclude = true)]
+        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");
+        }
+    }
+}
+
 #endif

+ 8 - 1
Topten.JsonKit/JsonAttribute.cs

@@ -107,12 +107,19 @@ namespace Topten.JsonKit
 
         /// <summary>
         /// For collection types controls whether they should be serialized
-        /// if the collection is empty.
+        /// if the collection is empty or null
         /// </summary>
         public bool ExcludeIfEmpty
         {
             get;
             set;
         }
+        
+        public object ExcludeIfEquals
+        {
+            get;
+            set;
+        }
+
     }
 }

+ 6 - 0
Topten.JsonKit/JsonMemberInfo.cs

@@ -36,6 +36,12 @@ namespace Topten.JsonKit
         // True if should be excluded when null
         public bool ExcludeIfNull => Attribute?.ExcludeIfNull ?? false;
 
+        // True if should be excluded when collection is empty
+        public bool ExcludeIfEmpty => Attribute?.ExcludeIfEmpty ?? false;
+
+        // True if should be excluded when value is equal to a specified value
+        public object ExcludeIfEquals => Attribute?.ExcludeIfEquals;
+
         // The JSON attribute for the member info
         public JsonAttribute Attribute
         {

+ 15 - 2
Topten.JsonKit/ReflectionInfo.cs

@@ -76,9 +76,22 @@ namespace Topten.JsonKit
                 {
                     // Exclude null?
                     var mval = jmi.GetValue(val);
-                    if (mval == null && jmi.ExcludeIfNull)
+                    if (jmi.ExcludeIfNull && mval == null)
                         continue;
-
+                    if (jmi.ExcludeIfEmpty)
+                    {
+                        if (mval == null)
+                            continue;
+
+                        if (mval is System.Collections.IEnumerable e && !e.GetEnumerator().MoveNext())
+                            continue;
+                    }
+                    if (jmi.ExcludeIfEquals != null)
+                    {
+                        if (jmi.ExcludeIfEquals.Equals(mval))
+                            continue;
+                    }
+
                     w.WriteKeyNoEscaping(jmi.JsonKey);
                     w.WriteValue(mval);
                 }

+ 33 - 32
Topten.JsonKit/Topten.JsonKit.csproj

@@ -1,32 +1,33 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <Import Project="../buildtools/Topten.props" />
-
-  <PropertyGroup>
-    <TargetFrameworks>net46;netcoreapp2.1;net5.0</TargetFrameworks>
-    <PublishRepositoryUrl>true</PublishRepositoryUrl>
-    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)'=='Release'">
-    <TtsCodeSign>True</TtsCodeSign>
-    <GenerateDocumentationFile>True</GenerateDocumentationFile>
-    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
-    <Copyright>Copyright © 2014-2021 Topten Software.  All Rights Reserved</Copyright>
-    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
-    <PackageIcon>nuget-icon.png</PackageIcon>
-    <PackageProjectUrl>https://github.com/toptensoftware/jsonkit</PackageProjectUrl>
-    <PackageTags>JSON, Serialization</PackageTags>
-    <Description>Lightweight JSON Serialization Library</Description>
-    <RepositoryUrl>https://github.com/toptensoftware/jsonkit</RepositoryUrl>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">
-      <PrivateAssets>all</PrivateAssets>
-      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-    </PackageReference>
-  </ItemGroup>
-
-
-</Project>
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <Import Project="../buildtools/Topten.props" />
+
+  <PropertyGroup>
+    <TargetFrameworks>net46;netcoreapp2.1;net5.0</TargetFrameworks>
+    <PublishRepositoryUrl>true</PublishRepositoryUrl>
+    <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
+    <XXDefineConstants>JSONKIT_NO_EMIT</XXDefineConstants>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <TtsCodeSign>True</TtsCodeSign>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+    <Copyright>Copyright © 2014-2021 Topten Software.  All Rights Reserved</Copyright>
+    <PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
+    <PackageIcon>nuget-icon.png</PackageIcon>
+    <PackageProjectUrl>https://github.com/toptensoftware/jsonkit</PackageProjectUrl>
+    <PackageTags>JSON, Serialization</PackageTags>
+    <Description>Lightweight JSON Serialization Library</Description>
+    <RepositoryUrl>https://github.com/toptensoftware/jsonkit</RepositoryUrl>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
+  </ItemGroup>
+
+
+</Project>