Просмотр исходного кода

More tests, keep instance support for emitted code

Brad Robinson 11 лет назад
Родитель
Сommit
60f4b32a9a
5 измененных файлов с 242 добавлено и 25 удалено
  1. 17 2
      EmitDev/Program.cs
  2. 34 1
      PetaJson.cs
  3. 56 15
      PetaJsonEmit.cs
  4. 42 7
      TestCases/Program.cs
  5. 93 0
      TestCases/TestsReflection.cs

+ 17 - 2
EmitDev/Program.cs

@@ -15,7 +15,7 @@ namespace EmitDev
     }
 
     [Json]
-    class Person : IJsonWriting, IJsonWritten
+    struct Person : IJsonWriting, IJsonWritten
     {
         public string StringField;
         public int IntField;
@@ -51,13 +51,27 @@ namespace EmitDev
         }
     }
 
+    [Json]
+    struct SimpleStruct
+    {
+        public int field;
+    }
+
     class Program
     {
 
         static void Main(string[] args)
         {
-            JsonEmit.Init();   
+            JsonEmit.Init();
+
+            var json = "{\"field\":23}";
+            var ss = Json.Parse<SimpleStruct>(json);
+
+
+
+            return;
 
+            /*
             var p = new Person()
             {
                 StringField = "Hello World",
@@ -94,6 +108,7 @@ namespace EmitDev
 
             var json2 = Json.Format(p2);
             Console.WriteLine(json2);
+             */
         }
     }
 }

+ 34 - 1
PetaJson.cs

@@ -106,6 +106,11 @@ namespace PetaJson
         // Parse from text reader into an already instantied object
         public static void ParseInto(TextReader r, Object into, JsonOptions options = JsonOptions.None)
         {
+            if (into == null)
+                throw new NullReferenceException();
+            if (into.GetType().IsValueType)
+                throw new InvalidOperationException("Can't ParseInto a value type");
+
             Internal.Reader reader = null;
             try
             {
@@ -226,6 +231,11 @@ namespace PetaJson
             Internal.Writer._typeWriterResolver = resolver;
         }
 
+        public static void SetTypeParserResolver(Func<Type, Func<IJsonReader, Type, object>> resolver)
+        {
+            Internal.Reader._typeParserResolver = resolver;
+        }
+
         public static void SetTypeIntoParserResolver(Func<Type, Action<IJsonReader, object>> resolver)
         {
             Internal.Reader._typeIntoParserResolver = resolver;
@@ -424,6 +434,7 @@ namespace PetaJson
             static Reader()
             {
                 _typeIntoParserResolver = ResolveParseInto;
+                _typeParserResolver = ResolveParse;
 
                 Func<IJsonReader, Type, object> simpleConverter = (reader, type) =>
                 {
@@ -471,6 +482,16 @@ namespace PetaJson
                     return null;
             }
 
+            static Func<IJsonReader, Type, object> ResolveParse(Type type)
+            {
+                return (r, t) =>
+                {
+                    var into = Activator.CreateInstance(type);
+                    r.ParseInto(into);
+                    return into;
+                };
+            }
+
             
 
             Tokenizer _tokenizer;
@@ -624,8 +645,19 @@ namespace PetaJson
                     return lit;
                 }
 
+                // Call type parser resolver
+                if (type.IsValueType)
+                {
+                    var tp = _typeParserResolver(type);
+                    if (tp != null)
+                    {
+                        _typeParsers[type] = tp;
+                        return tp(this, type);
+                    }
+                }
+
                 // Is it a type we can parse into?
-                if ((type.IsClass || (type.IsValueType && !type.IsPrimitive)) && type != typeof(object))
+                if (type.IsClass && type != typeof(object))
                 {
                     var into = Activator.CreateInstance(type);
                     ParseInto(into);
@@ -834,6 +866,7 @@ namespace PetaJson
             public static Dictionary<Type, Func<IJsonReader, Type, object>> _typeParsers = new Dictionary<Type, Func<IJsonReader, Type, object>>();
             public static Dictionary<Type, Action<IJsonReader, object>> _typeIntoParsers = new Dictionary<Type, Action<IJsonReader, object>>();
             public static Func<Type, Action<IJsonReader, object>> _typeIntoParserResolver;
+            public static Func<Type, Func<IJsonReader, Type, object>> _typeParserResolver;
             public static Dictionary<Type, Func<IJsonReader, string, object>> _typeFactories = new Dictionary<Type, Func<IJsonReader, string, object>>();
         }
 

+ 56 - 15
PetaJsonEmit.cs

@@ -14,7 +14,7 @@ namespace PetaJson
         public static void Init()
         {
             Json.SetTypeFormatterResolver(Internal.Emit.MakeFormatter);
-            Json.SetTypeIntoParserResolver(Internal.Emit.MakeParser);
+            Json.SetTypeIntoParserResolver(Internal.Emit.MakeIntoParser);
         }
     }
 
@@ -266,11 +266,8 @@ namespace PetaJson
 
             }
 
-            public static Action<IJsonReader, object> MakeParser(Type type)
+            public static Action<IJsonReader, object> MakeIntoParser(Type type)
             {
-                if (type.IsValueType)
-                    throw new NotImplementedException("Value types aren't supported through reflection");
-
                 // Get the reflection info for this type
                 var ri = ReflectionInfo.GetReflectionInfo(type);
                 if (ri == null)
@@ -292,23 +289,54 @@ namespace PetaJson
                 {
                     // Ignore write only properties
                     var pi = m.Member as PropertyInfo;
+                    var fi = m.Member as FieldInfo;
                     if (pi != null && pi.GetSetMethod() == null)
                     {
                         continue;
                     }
+                    if (pi != null && pi.GetGetMethod() == 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(type.IsValueType ? OpCodes.Unbox : OpCodes.Castclass, type);
+
                     if (m.KeepInstance)
                     {
-                        throw new NotImplementedException("Emit KeepInstance not implemented");
-                    }
+                        // Get existing existing instance
+                        il.Emit(OpCodes.Dup);
+                        if (pi != null)
+                            il.Emit(OpCodes.Callvirt, pi.GetGetMethod());
+                        else
+                            il.Emit(OpCodes.Ldfld, fi);
 
-                    // Load the target
-                    il.Emit(OpCodes.Ldarg_1);
-                    il.Emit(OpCodes.Castclass, type);
+                        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);
+                    }
 
                     Action<string> callHelper = helperName =>
                     {
@@ -359,10 +387,9 @@ namespace PetaJson
 
                     if (pi != null)
                     {
-                        il.Emit(OpCodes.Callvirt, pi.GetSetMethod());
+                        il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, pi.GetSetMethod());
                     }
 
-                    var fi = m.Member as FieldInfo;
                     if (fi != null)
                     {
                         il.Emit(OpCodes.Stfld, fi);
@@ -421,9 +448,23 @@ namespace PetaJson
                 var il = method.GetILGenerator();
 
                 // Create the new object
-                var locObj = il.DeclareLocal(type);
-                il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
-                il.Emit(OpCodes.Dup);
+                var locObj = il.DeclareLocal(typeof(object));
+                if (type.IsValueType)
+                {
+                    // Create boxed type
+                    var locTempStruct = il.DeclareLocal(type);
+                    il.Emit(OpCodes.Ldloca, locTempStruct);
+                    il.Emit(OpCodes.Initobj);
+                    il.Emit(OpCodes.Ldloc, locTempStruct);
+                    il.Emit(OpCodes.Box, type);
+                }
+                else
+                {
+                    il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
+                }
+
+                il.Emit(OpCodes.Dup);               // For return value
+
                 il.Emit(OpCodes.Stloc, locObj);
 
                 il.Emit(OpCodes.Ldarg_1);           // parseinto delegate

+ 42 - 7
TestCases/Program.cs

@@ -2,21 +2,56 @@
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
+using System.Reflection.Emit;
 
 namespace TestCases
 {
-    class Program
+    struct MyStruct
     {
-        int field { get; set; }
+        public int x;
+        public int y;
+    }
 
-        static void Main(string[] args)
+    class Program
+    {
+        static void StructTest()
         {
-            var p = new Program();
-            p.field = 23;
+            /*
+            var inst = Activator.CreateInstance(typeof(MyStruct));
+
+            var fi = typeof(MyStruct).GetField("x");
+            fi.SetValue(inst, 23);
+
+            var final = (MyStruct)inst;
+
+            int x = 3;
+             */
+
+            var method = new DynamicMethod("set_struct_field", null, new Type[] { typeof(object) }, true);
+            var il = method.GetILGenerator();
+
+            il.Emit(OpCodes.Ldarg_0);
+            il.Emit(OpCodes.Unbox, typeof(MyStruct));
+            il.Emit(OpCodes.Ldc_I4, 23);
+            il.Emit(OpCodes.Stfld, typeof(MyStruct).GetField("x"));
 
-            var str = p.field.ToString();
+            il.Emit(OpCodes.Ret);
 
-            //PetaJson.JsonEmit.Init();
+            var fn = (Action<object>)method.CreateDelegate(typeof(Action<object>));
+
+            object inst = new MyStruct();
+
+            fn(inst);
+
+
+            int x = 3;
+        }
+
+
+        static void Main(string[] args)
+        {
+            StructTest();
+            PetaJson.JsonEmit.Init();
             PetaTest.Runner.RunMain(args);
         }
     }

+ 93 - 0
TestCases/TestsReflection.cs

@@ -45,6 +45,29 @@ namespace TestCases
         public string Prop2 { get; set; }
     }
 
+    [Json]
+    class InstanceObject
+    {
+        public int IntVal1;
+
+        [JsonExclude] public int IntVal2;
+
+    }
+
+    [Json]
+    class ModelKeepInstance
+    {
+        [Json(KeepInstance=true)]
+        public InstanceObject InstObj;
+    }
+
+    [Json]
+    struct ModelStruct
+    {
+        public int IntField;
+        public int IntProp { get; set; }
+    }
+
     [TestFixture]
     public class TestsReflection
     {
@@ -127,5 +150,75 @@ namespace TestCases
             Assert.Contains(json, "Prop1");
             Assert.DoesNotContain(json, "prop2");
         }
+
+        [Test]
+        public void KeepInstanceTest1()
+        {
+            // Create model and save it
+            var ki = new ModelKeepInstance();
+            ki.InstObj = new InstanceObject();
+            ki.InstObj.IntVal1 = 1;
+            ki.InstObj.IntVal2 = 2;
+            var json = Json.Format(ki);
+
+            // Update the kept instance object
+            ki.InstObj.IntVal1 = 11;
+            ki.InstObj.IntVal2 = 12;
+
+            // Reload
+            var oldInst = ki.InstObj;
+            Json.ParseInto(json, ki);
+
+            // Check object instance kept
+            Assert.AreSame(oldInst, ki.InstObj);
+
+            // Check json properties updated, others not
+            Assert.AreEqual(ki.InstObj.IntVal1, 1);
+            Assert.AreEqual(ki.InstObj.IntVal2, 12);
+        }
+
+        [Test]
+        public void KeepInstanceTest2()
+        {
+            // Create model and save it
+            var ki = new ModelKeepInstance();
+            ki.InstObj = new InstanceObject();
+            ki.InstObj.IntVal1 = 1;
+            ki.InstObj.IntVal2 = 2;
+            var json = Json.Format(ki);
+
+            // Update the kept instance object
+            ki.InstObj = null;
+
+            // Reload
+            Json.ParseInto(json, ki);
+
+            // Check object instance kept
+            Assert.IsNotNull(ki.InstObj);
+
+            // Check json properties updated, others not
+            Assert.AreEqual(ki.InstObj.IntVal1, 1);
+            Assert.AreEqual(ki.InstObj.IntVal2, 0);
+        }
+
+        [Test]
+        public void StructTest()
+        {
+            var o = new ModelStruct();
+            o.IntField = 23;
+            o.IntProp = 24;
+
+            var json = Json.Format(o);
+            Assert.Contains(json, "23");
+            Assert.Contains(json, "24");
+
+            var o2 = Json.Parse<ModelStruct>(json);
+            Assert.AreEqual(o2.IntField, 23);
+            Assert.AreEqual(o2.IntProp, 24);
+
+            // Test parseInto on a value type not supported
+            var o3 = new ModelStruct();
+            Assert.Throws<InvalidOperationException>(() => Json.ParseInto(json, o3));
+        }
     }
 }