Procházet zdrojové kódy

Reparse and IDictionary<> extensions

Brad Robinson před 11 roky
rodič
revize
1db8c04490
5 změnil soubory, kde provedl 351 přidání a 14 odebrání
  1. 121 5
      PetaJson.cs
  2. 2 0
      TestCases/TestCases.csproj
  3. 87 0
      TestCases/TestDictionaryUtils.cs
  4. 47 0
      TestCases/TestReparse.cs
  5. 94 9
      readme.md

+ 121 - 5
PetaJson.cs

@@ -200,22 +200,61 @@ namespace PetaJson
         // Create a clone of an object
         public static T Clone<T>(T source)
         {
-            return (T)Clone((object)source);
+            return Reparse<T>(source);
         }
 
         // Create a clone of an object (untyped)
         public static object Clone(object source)
+        {
+            return Reparse(typeof(object), source);
+        }
+
+        // Clone an object into another instance
+        public static void CloneInto(object dest, object source)
+        {
+            ReparseInto(dest, source);
+        }
+
+        // Reparse an object by writing to a stream and re-reading (possibly
+        // as a different type).
+        public static object Reparse(Type type, object source)
         {
             if (source == null)
                 return null;
+            var ms = new MemoryStream();
+            try
+            {
+                // Write
+                var w = new StreamWriter(ms);
+                Json.Write(w, source);
+                w.Flush();
 
-            return Parse(Format(source), source.GetType());
+                // Read
+                ms.Seek(0, SeekOrigin.Begin);
+                var r = new StreamReader(ms);
+                return Json.Parse(r, type);
+            }
+            finally
+            {
+                ms.Dispose();
+            }
         }
 
-        // Clone an object into another instance
-        public static void CloneInto<T>(T dest, T source)
+        // Typed version of above
+        public static T Reparse<T>(object source)
         {
-            ParseInto(Format(source), dest);
+            return (T)Reparse(typeof(T), source);
+        }
+
+        // Reparse one object into another object 
+        public static void ReparseInto(object dest, object source)
+        {
+            using (var ms = new MemoryStream())
+            {
+                using (var w = new StreamWriter(ms)) Json.Write(w, source);
+                ms.Seek(0, SeekOrigin.Begin);
+                using (var r = new StreamReader(ms)) Json.ParseInto(r, dest);
+            }
         }
 
         // Register a callback that can format a value of a particular type into json
@@ -292,6 +331,83 @@ namespace PetaJson
             Internal.Reader._intoParserResolver = resolver;
         }
 
+        public static bool WalkPath(this IDictionary<string, object> This, string Path, bool create, Func<IDictionary<string,object>,string, bool> leafCallback)
+        {
+            // Walk the path
+            var parts = Path.Split('.');
+            for (int i = 0; i < parts.Length-1; i++)
+            {
+                object val;
+                if (!This.TryGetValue(parts[i], out val))
+                {
+                    if (!create)
+                        return false;
+
+                    val = new Dictionary<string, object>();
+                    This[parts[i]] = val;
+                }
+                This = (IDictionary<string,object>)val;
+            }
+
+            // Process the leaf
+            return leafCallback(This, parts[parts.Length-1]);
+        }
+
+        public static bool PathExists(this IDictionary<string, object> This, string Path)
+        {
+            return This.WalkPath(Path, false, (dict, key) => dict.ContainsKey(key));
+        }
+
+        public static object GetPath(this IDictionary<string, object> This, Type type, string Path, object def)
+        {
+            This.WalkPath(Path, false, (dict, key) =>
+            {
+                object val;
+                if (dict.TryGetValue(key, out val))
+                {
+                    if (val == null)
+                        def = val;
+                    else if (type.IsAssignableFrom(val.GetType()))
+                        def = val;
+                    else
+                        def = Reparse(type, val);
+                }
+                return true;
+            });
+
+            return def;
+        }
+
+        // Ensure there's an object of type T at specified path
+        public static T GetObjectAtPath<T>(this IDictionary<string, object> This, string Path) where T:class,new()
+        {
+            T retVal = null;
+            This.WalkPath(Path, true, (dict, key) =>
+            {
+                object val;
+                dict.TryGetValue(key, out val);
+                retVal = val as T;
+                if (retVal == null)
+                {
+                    retVal = val == null ? new T() : Reparse<T>(val);
+                    dict[key] = retVal;
+                }
+                return true;
+            });
+
+            return retVal;
+        }
+
+        public static T GetPath<T>(this IDictionary<string, object> This, string Path, T def = default(T))
+        {
+            return (T)This.GetPath(typeof(T), Path, def);
+        }
+
+        public static void SetPath(this IDictionary<string, object> This, string Path, object value)
+        {
+            This.WalkPath(Path, true, (dict, key) => { dict[key] = value; return true; });
+        }
+
         // Resolve passed options        
         static JsonOptions ResolveOptions(JsonOptions options)
         {

+ 2 - 0
TestCases/TestCases.csproj

@@ -53,9 +53,11 @@
     <Compile Include="TestAbstractTypes.cs" />
     <Compile Include="TestConcreteFromInterface.cs" />
     <Compile Include="TestCustomFormat.cs" />
+    <Compile Include="TestDictionaryUtils.cs" />
     <Compile Include="TestNullableTypes.cs" />
     <Compile Include="TestOptions.cs" />
     <Compile Include="TestPrimitiveTypes.cs" />
+    <Compile Include="TestReparse.cs" />
     <Compile Include="TestsEvents.cs" />
     <Compile Include="TestsGeneral.cs" />
     <Compile Include="TestsReflection.cs" />

+ 87 - 0
TestCases/TestDictionaryUtils.cs

@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using PetaTest;
+using PetaJson;
+
+namespace TestCases
+{
+    [TestFixture]
+    public class TestDictionaryUtils
+    {
+        [Test]
+        public void DictionaryPaths()
+        {
+            var dict = new Dictionary<string, object>();
+            dict.SetPath("settings.subSettings.settingA", 23);
+            dict.SetPath("settings.subSettings.settingB", 24);
+
+            Assert.IsTrue(dict.ContainsKey("settings"));
+            Assert.IsTrue(((IDictionary<string, object>)dict["settings"]).ContainsKey("subSettings"));
+            Assert.AreEqual(dict.GetPath<int>("settings.subSettings.settingA"), 23);
+            Assert.AreEqual(dict.GetPath<int>("settings.subSettings.settingB"), 24);
+            Assert.IsTrue(dict.PathExists("settings.subSettings"));
+            Assert.IsTrue(dict.PathExists("settings.subSettings.settingA"));
+            Assert.IsFalse(dict.PathExists("missing_in_action"));
+        }
+
+        [Test]
+        public void DictionaryReparseType()
+        {
+            // Create and initialize and object then convert it to a dictionary
+            var o = new DaObject() { id = 101, Name = "#101" };
+            var oDict = Json.Reparse<IDictionary<string, object>>(o);
+
+            // Store that dictionary at a path inside another dictionary
+            var dict = new Dictionary<string, object>();
+            dict.SetPath("settings.daObject", oDict);
+
+            // Get it back out, but reparse it back into a strongly typed object
+            var o2 = dict.GetPath<DaObject>("settings.daObject");
+            Assert.AreEqual(o2.id, o.id);
+            Assert.AreEqual(o2.Name, o.Name);
+        }
+
+        [Test]
+        public void ObjectAtPath()
+        {
+            // Create and initialize and object then convert it to a dictionary
+            var o = new DaObject() { id = 101, Name = "#101" };
+            var oDict = Json.Reparse<IDictionary<string, object>>(o);
+
+            // Store that dictionary at a path inside another dictionary
+            var dict = new Dictionary<string, object>();
+            dict.SetPath("settings.daObject", oDict);
+
+            // Get it back as an object (and update dict to hold an actual DaObject
+            var o2 = dict.GetObjectAtPath<DaObject>("settings.daObject");
+
+            // Modify it
+            o2.id = 102;
+            o2.Name = "modified";
+
+            // Save the dictionary and make sure we got the change
+            var json = Json.Format(dict);
+            Assert.Contains(json, "102");
+            Assert.Contains(json, "modified");
+        }
+
+        [Test]
+        public void NewObjectAtPath()
+        {
+            // Create a new object at a path
+            var dict = new Dictionary<string, object>();
+            var o2 = dict.GetObjectAtPath<DaObject>("settings.daObject");
+
+            // Modify it
+            o2.id = 103;
+            o2.Name = "new guy";
+
+            // Save the dictionary and make sure we got the change
+            var json = Json.Format(dict);
+            Assert.Contains(json, "103");
+            Assert.Contains(json, "new guy");
+        }
+    }
+}

+ 47 - 0
TestCases/TestReparse.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using PetaTest;
+using PetaJson;
+
+namespace TestCases
+{
+    class DaObject
+    {
+        [Json] public long id;
+        [Json] public string Name;
+    }
+
+    [TestFixture]
+    public class TestReparse
+    {
+        void Compare(DaObject a, DaObject b)
+        {
+            Assert.AreEqual(a.id, b.id);
+            Assert.AreEqual(a.Name, b.Name);
+        }
+
+        [Test]
+        public void Clone()
+        {
+            var a = new DaObject() { id = 101, Name = "#101" };
+            var b = Json.Clone(a);
+            Compare(a, b);
+        }
+
+        [Test]
+        public void Reparse()
+        {
+            var a = new DaObject() { id = 101, Name = "#101" };
+            var dict = Json.Reparse<IDictionary<string, object>>(a);
+
+            Assert.AreEqual(dict["id"], 101UL);
+            Assert.AreEqual(dict["name"], "#101");
+
+            var b = Json.Reparse<DaObject>(dict);
+
+            Compare(a, b);
+        }
+    }
+}

+ 94 - 9
readme.md

@@ -410,11 +410,8 @@ For example, it's often necessary to wire up ownership references on loaded sub-
 
 		void IJsonLoaded.OnJsonLoaded()
 		{
-			// Shapes have been loaded, set back reference to self
-			foreach (var s in Shapes)
-			{
-				s.Owner = this;
-			}
+			// Shapes have been loaded, set owner references
+			Shapes.ForEach(x => x.Owner = this);
 		}
 	}
 
@@ -437,10 +434,10 @@ imagine a situation where a numeric ID field was incorrectly provided by a serve
 				// Parse the string
 				id = long.Parse(r.GetLiteralString());
 
-				// Skip the string literal not that we've handled it
+				// Skip the string literal now that we've handled it
 				r.NextToken();		
 
-				// Return true to suppress default processing of this key
+				// Return true to suppress default processing 
 				return true;
 			}
 
@@ -452,9 +449,9 @@ imagine a situation where a numeric ID field was incorrectly provided by a serve
 Note: although these event methods could have been implemented using reflection rather than interfaces,
 the use of interfaces is more discoverable through Intellisense/Autocomplete.
 
-## Cloning Objects
+## Cloning and Re-parsing Objects
 
-PetaJson includes a couple of helper functions for cloning object:
+PetaJson includes a couple of helper functions for cloning object by saving to Json and then reloading:
 
 	var person1 = new Person() { Name = "Mr Json Bourne"; }
 	var person2 = Json.Clone(person1);
@@ -464,6 +461,94 @@ You can also clone into an existing instance
 	var person3 = new Person();
 	Json.CloneInto(person3, person1);		// Copies from person1 to person3
 
+Similar to cloning is re-parsing. While cloning copies from one object to another of the same type,
+reparsing allows converting from one object type to another.  For example you can convert a dictionary
+of values into a person:
+
+	IDictionary<string,object> dictionary = getDictionaryFromSomewhere();
+	var person = Json.Reparse<Person>(dictionary);
+
+You can also go the other way:
+
+	var dictionary = Json.Reparse<IDictionary<string,object>>(person);
+
+## Bonus Dictionary Helpers
+
+PetaJson includes some super handy extensions to IDictionary<string,object> that make working
+with weakly typed JSON data easier.  Some of these methods are particularly handy when an app
+is using JSON to store configuration options or settings.
+
+Suppose we have the following JSON:
+
+	{
+		"settings":
+		{
+			"userSettings":
+			{
+				"username":"jsonbourne23",
+				"password":"123",
+				"email":"json@bourne.com",
+			},
+			"appSettings":
+			{
+				"firstRun":false,
+				"serverUrl":"http://www.toptensoftware.com",
+			}
+		}
+	}
+
+and we parse all this into a weakly typed dictionary:
+
+	var data = Json.ParseFile<IDictionary<string,object>>("settings.json");
+
+We can get a setting like this:
+
+	bool firstRun = data.GetPath<bool>("settings.appSettings.firstRun", true);
+
+Or set it like this:
+
+	data.SetPath("settings.appSettings.firstRun", false);
+
+SetPath creates the path using a set of Dictionary<string,object> if necessary:
+
+	var data = new Dictionary<String, object>();
+	data.SetPath("settings.appSettings.serverUrl", "http://whatever.com");
+
+GetPath can reparse if necessary to satify the requested type:
+
+	var userSettings = data.GetPath<UserSettings>("settings.userSettings", null);
+
+You can check if a path exists like this:
+
+	if (data.PathExists("settings.appSettings"))
+	{
+		// yep
+	}
+
+And finally, there's `T GetObjectAtPath<T>(string path)` which does a few things:
+
+1. Makes sure the path exists, and creates it if it doesn't
+2. If the path does exist, reparses whatever it finds there into type T.
+3. If the path doesn't exists, creates a new T
+4. Stores the T instance back into the dictionary at that path.
+
+So now we can work with parts of a weakly typed JSON dictionary with strong types.
+
+eg: 
+
+	var userSettings = data.GetObjectAtPath<UserSettings>("settings.userSettings");
+
+and saving data, will get the changes:
+
+	// Make a change
+	userSettings.email = "newemail@bourne.com";
+
+	// It sticks...
+	var json = Json.Format(data);
+	System.Diagnostic.Assert(json.IndexOf("newemail")>=0);
+
+Note: GetObjectAtPath only works with reference types, not structs.
+
 ## Options
 
 PetaJson has a couple of formatting/parsing options. These can be set as global defaults: