From 23d0315fb9f26f871451dc33b2957b0d85ac09ba Mon Sep 17 00:00:00 2001 From: ebragge Date: Fri, 11 Mar 2016 16:40:28 +0200 Subject: [PATCH] feat(AsyncStorageModule): add AsyncStorageModule AsyncStorageModule provides permanent storage for React Native JavaScript applications. - implement PR comments #2 - add Android tests Fixes #136 --- .../Storage/AsyncStorageModuleTests.cs | 593 ++++++++++++++++-- .../Modules/Storage/AsyncStorageModule.cs | 252 ++++++-- 2 files changed, 742 insertions(+), 103 deletions(-) diff --git a/ReactNative.Tests/Modules/Storage/AsyncStorageModuleTests.cs b/ReactNative.Tests/Modules/Storage/AsyncStorageModuleTests.cs index a70111ef672..85e58ff1d44 100644 --- a/ReactNative.Tests/Modules/Storage/AsyncStorageModuleTests.cs +++ b/ReactNative.Tests/Modules/Storage/AsyncStorageModuleTests.cs @@ -1,6 +1,7 @@ using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; using Newtonsoft.Json.Linq; using ReactNative.Modules.Storage; +using System.Collections.Generic; using System.Threading; namespace ReactNative.Tests.Modules.Storage @@ -62,37 +63,133 @@ public void AsyncStorageModule_InvalidKeyValue_Method() } [TestMethod] - public void AsyncStorageModule_multiGet_Method() + public void AsyncStorageModule_Datatypes_Method() { var module = new AsyncStorageModule(); var waitHandle = new AutoResetEvent(false); var result = new JArray(); - var emptyCallback = new MockCallback(_ => waitHandle.Set()); - var callback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + var setCallback = new MockCallback( _ => waitHandle.Set() ); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + var obj = new JObject(); + obj.Add("ABC", "AQE"); + obj.Add("DEF", "DFG"); + + var constr = new JConstructor("ABC", "GHJ"); var array = new JArray { new JArray { - "test1", - 5, - } + "1", + null, + }, + new JArray + { + "2", + true, + }, + new JArray + { + "3", + 555, + }, + new JArray + { + "4", + 999.999, + }, + new JArray + { + "5", + "Test string", + }, + new JArray + { + "6", + JToken.FromObject(new System.DateTime(2016, 1, 1, 12, 13, 14)), + }, + new JArray + { + "7", + JToken.FromObject(new System.Uri("http://dev.windows.com")), + }, + new JArray + { + "8", + JToken.FromObject(new System.TimeSpan(1, 1, 1)), + }, + new JArray + { + "9", + JToken.FromObject(new System.Guid("936DA01F-9ABD-4d9d-80C7-02AF85C822A8")), + }, + new JArray + { + "10", + obj, + }, + new JArray + { + "11", + new JArray + { + 1, + 2, + obj, + }, + }, + new JArray + { + "12", + new JArray + { + "abc", + "def", + 5, + 6, + }, + }, + new JArray + { + "13", + new JRaw(560), + }, + new JArray + { + "14", + new JRaw("ABC"), + }, + new JArray + { + "15", + new JRaw(5.5), + }, + new JArray + { + "16", + constr, + }, }; - module.multiSet(array, callback); + module.clear(setCallback); waitHandle.WaitOne(); - module.multiGet(new string[] { "test1", }, callback); + module.multiSet(array, setCallback); waitHandle.WaitOne(); - Assert.AreEqual(result.Count, 1); - Assert.AreEqual((result[0]).Last.Value(), 5); + var keys = new string[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16" }; + + module.multiGet(keys, getCallback); + waitHandle.WaitOne(); + + AssertJArraysAreEqual(array, result); } [TestMethod] - public void AsyncStorageModule_multiMerge_Method() + public void AsyncStorageModule_multiGet_Method() { var module = new AsyncStorageModule(); var waitHandle = new AutoResetEvent(false); @@ -102,47 +199,23 @@ public void AsyncStorageModule_multiMerge_Method() var emptyCallback = new MockCallback(_ => waitHandle.Set()); var callback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); - var array1 = new JArray + var array = new JArray { new JArray { "test1", 5, - }, - new JArray - { - "test2", - 10, } }; - var array2 = new JArray - { - new JArray - { - "test2", - 15, - }, - new JArray - { - "test3", - 20, - } - }; - - module.clear(emptyCallback); - waitHandle.WaitOne(); - - module.multiSet(array1, callback); - waitHandle.WaitOne(); - - module.multiMerge(array2, callback); + module.multiSet(array, callback); waitHandle.WaitOne(); - module.getAllKeys(callback); + module.multiGet(new string[] { "test1", }, callback); waitHandle.WaitOne(); - Assert.AreEqual(result.Count, 3); + Assert.AreEqual(result.Count, 1); + Assert.AreEqual((result[0]).Last.Value(), 5); } [TestMethod] @@ -237,6 +310,432 @@ public void AsyncStorageModule_multiRemove_Method() Assert.AreEqual(result.Count, 4); } + /// ************** + /// Android tests + /// ************** + + [TestMethod] + public void AsyncStorageModule_Android_testMultiSetMultiGet() + { + var mStorage = new AsyncStorageModule(); + var waitHandle = new AutoResetEvent(false); + + var result = new JArray(); + var setCallback = new MockCallback(_ => waitHandle.Set()); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + + string key1 = "foo1"; + string key2 = "foo2"; + string fakeKey = "fakeKey"; + string value1 = "bar1"; + string value2 = "bar2"; + + var keyValues = new JArray(); + keyValues.Add(new JArray { key1, value1 }); + keyValues.Add(new JArray { key2, value2 }); + + mStorage.multiSet(keyValues, setCallback); + waitHandle.WaitOne(); + + var keys = new List(); + keys.Add(key1); + keys.Add(key2); + + mStorage.multiGet(keys.ToArray(), getCallback); + waitHandle.WaitOne(); + + Assert.IsTrue(JToken.DeepEquals(result, keyValues)); + + keys.Add(fakeKey); + keyValues.Add(new JArray { fakeKey, null }); + + mStorage.multiGet(keys.ToArray(), getCallback); + waitHandle.WaitOne(); + + Assert.IsTrue(JToken.DeepEquals(result, keyValues)); + } + + [TestMethod] + public void AsyncStorageModule_Android_testMultiRemove() + { + var mStorage = new AsyncStorageModule(); + var waitHandle = new AutoResetEvent(false); + + var result = new JArray(); + var setCallback = new MockCallback(_ => waitHandle.Set()); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + string key1 = "foo1"; + string key2 = "foo2"; + string value1 = "bar1"; + string value2 = "bar2"; + + var keyValues = new JArray(); + keyValues.Add(new JArray { key1, value1 }); + keyValues.Add(new JArray { key2, value2 }); + + mStorage.multiSet(keyValues, setCallback); + waitHandle.WaitOne(); + + var keys = new List(); + keys.Add(key1); + keys.Add(key2); + + mStorage.multiRemove(keys.ToArray(), setCallback); + waitHandle.WaitOne(); + + mStorage.getAllKeys(getCallback); + waitHandle.WaitOne(); + + Assert.AreEqual(result.Count, 0); + + mStorage.multiSet(keyValues, setCallback); + waitHandle.WaitOne(); + + keys.Add("fakeKey"); + mStorage.multiRemove(keys.ToArray(), setCallback); + waitHandle.WaitOne(); + + mStorage.getAllKeys(getCallback); + waitHandle.WaitOne(); + + Assert.AreEqual(result.Count, 0); + } + + [TestMethod] + public void AsyncStorageModule_Android_testMultiMerge() + { + var mStorage = new AsyncStorageModule(); + var waitHandle = new AutoResetEvent(false); + + var result = new JArray(); + var setCallback = new MockCallback(_ => waitHandle.Set()); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + string mergeKey = "mergeTest"; + + var value = new JObject(); + value.Add("foo1", "bar1"); + + value.Add("foo2", new JArray + { + "val1", + "val2", + 3, + }); + + value.Add("foo3", 1001); + + var val = new JObject(); + val.Add("key1", "randomValueThatWillNeverBeUsed"); + value.Add("foo4", val); + + var array = new JArray + { + new JArray + { + mergeKey, + + value, + }, + }; + + mStorage.multiSet(array, setCallback); + waitHandle.WaitOne(); + + var str = new string[] { mergeKey }; + + mStorage.multiGet(str, getCallback); + waitHandle.WaitOne(); + + Assert.IsTrue(JToken.DeepEquals(result, array)); + + value.Remove("foo1"); + value.Remove("foo2"); + value.Remove("foo3"); + value.Remove("foo4"); + + value.Add("foo1", 1001); + + var val2 = new JObject(); + val2.Add("key1", "val1"); + value.Add("foo2", val2); + + value.Add("foo3", "bar1"); + + value.Add("foo4", new JArray + { + "val1", + "val2", + 3 + }); + + var newValue = new JObject(); + var val3 = new JObject(); + val3.Add("key2", "val2"); + newValue.Add("foo2", val3); + + var newValue2 = new JObject(); + var val4 = new JObject(); + val4.Add("key1", "val3"); + newValue2.Add("foo2", val4); + + var array2 = new JArray + { + new JArray + { + mergeKey, + value, + }, + }; + + mStorage.multiMerge(array2, setCallback); + waitHandle.WaitOne(); + + var array3 = new JArray + { + new JArray + { + mergeKey, + newValue, + }, + }; + + mStorage.multiMerge(array3, setCallback); + waitHandle.WaitOne(); + + var array4 = new JArray + { + new JArray + { + mergeKey, + newValue2, + }, + }; + + mStorage.multiMerge(array4, setCallback); + waitHandle.WaitOne(); + + value.Remove("foo2"); + var val5 = new JObject(); + val5.Add("key1", "val3"); + val5.Add("key2", "val2"); + value.Add("foo2", val5); + + mStorage.multiGet(str, getCallback); + waitHandle.WaitOne(); + + Assert.IsTrue(JToken.DeepEquals(value, result.Last.Value().Last.Value())); + } + + [TestMethod] + public void AsyncStorageModule_Android_testGetAllKeys() + { + var mStorage = new AsyncStorageModule(); + var waitHandle = new AutoResetEvent(false); + + var result = new JArray(); + var setCallback = new MockCallback(_ => waitHandle.Set()); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + string[] keys = { "foo", "foo2" }; + string[] values = { "bar", "bar2" }; + + var keyValues = new JArray + { + new JArray + { + keys[0], + values[0], + }, + new JArray + { + keys[1], + values[1], + }, + }; + + mStorage.multiSet(keyValues, setCallback); + waitHandle.WaitOne(); + + mStorage.getAllKeys(getCallback); + waitHandle.WaitOne(); + + var storedKeys = new JArray + { + keys[0], + keys[1], + }; + + var set = new SortedSet(); + IEnumerable enumerator = storedKeys.Values(); + + foreach (var value in enumerator) + { + set.Add(value); + } + + set.SymmetricExceptWith(result.Values()); + Assert.AreEqual(set.Count, 0); + + mStorage.multiRemove(keys, getCallback); + waitHandle.WaitOne(); + + mStorage.getAllKeys(getCallback); + waitHandle.WaitOne(); + + Assert.AreEqual(result.Count, 0); + } + + [TestMethod] + public void AsyncStorageModule_Android_testClear() + { + var mStorage = new AsyncStorageModule(); + var waitHandle = new AutoResetEvent(false); + + var result = new JArray(); + var setCallback = new MockCallback(_ => waitHandle.Set()); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + string[] keys = { "foo", "foo2" }; + string[] values = { "bar", "bar2" }; + + var keyValues = new JArray + { + new JArray + { + keys[0], + values[0], + }, + new JArray + { + keys[1], + values[1], + }, + }; + + mStorage.multiSet(keyValues, setCallback); + waitHandle.WaitOne(); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + mStorage.getAllKeys(getCallback); + waitHandle.WaitOne(); + + Assert.AreEqual(result.Count, 0); + } + + [TestMethod] + public void AsyncStorageModule_Android_testHugeMultiGetMultiGet() + { + var mStorage = new AsyncStorageModule(); + var waitHandle = new AutoResetEvent(false); + + var result = new JArray(); + var setCallback = new MockCallback(_ => waitHandle.Set()); + var getCallback = new MockCallback(res => { result = (JArray)res[0]; waitHandle.Set(); }); + + mStorage.clear(setCallback); + waitHandle.WaitOne(); + + // Limitation on Android - not a limitation on Windows + // Test with many keys, so that it's above the 999 limit per batch imposed by SQLite. + int keyCount = 1001; + // don't set keys that divide by this magical number, so that we can check that multiGet works, + // and returns null for missing keys + int magicalNumber = 343; + + var keyValues = new JArray(); + for (int i = 0; i < keyCount; i++) + { + if (i % magicalNumber > 0) + { + var key = "key" + i; + var value = "value" + i; + keyValues.Add(new JArray + { + key, + value, + }); + } + } + mStorage.multiSet(keyValues, setCallback); + waitHandle.WaitOne(); + + var keys = new List(); + for (int i = 0; i < keyCount; i++) + { + keys.Add("key" + i); + } + + mStorage.multiGet(keys.ToArray(), getCallback); + waitHandle.WaitOne(); + + Assert.AreEqual(result.Count, keys.Count); + + var keyReceived = new bool[keyCount]; + + for (int i = 0; i < keyCount; i++) + { + var keyValue = result[i]; + var key = keyValue.Value().First.Value().Substring(3); + + int idx = System.Int32.Parse(key); + Assert.IsFalse(keyReceived[idx]); + keyReceived[idx] = true; + + if (idx % magicalNumber > 0) + { + var value = keyValue.Value().Last.Value().Substring(5); + Assert.AreEqual(key, value); + } + else + { + Assert.IsTrue(keyValue.Value().Last.Type == JTokenType.Null); + } + } + + var keyRemoves = new List(); + for (int i = 0; i < keyCount; i++) + { + if (i % 2 > 0) + { + keyRemoves.Add("key" + i); + } + } + + mStorage.multiRemove(keyRemoves.ToArray(), setCallback); + waitHandle.WaitOne(); + + mStorage.getAllKeys(getCallback); + waitHandle.WaitOne(); + + Assert.AreEqual(result.Count, 499); + for (int i = 0; i < result.Count; i++) + { + var key = result[i].Value().Substring(3); ; + int idx = System.Int32.Parse(key); + Assert.AreEqual(idx % 2,0); + } + } + private void AssertJArraysAreEqual(JArray a, JArray b) { foreach (var item in a) @@ -256,19 +755,23 @@ private void AssertJArraysAreEqual(JArray a, JArray b) else if (o.GetType() != value.GetType()) Assert.Fail(); else { - var t = value.GetType(); - if (t == typeof(bool) || t == typeof(long) || t == typeof(double)) + if (value is bool || value is long || value is double || value is System.Guid || + value is System.TimeSpan || value is System.Uri || value is System.DateTime) { Assert.AreEqual(value, o); } - else if (t == typeof(string)) + else if (value is string) { - Assert.IsTrue(((string)value).CompareTo((string)o) == 0); + Assert.IsTrue((value as string).CompareTo(o as string) == 0); } - else if (t == typeof(JArray)) + else if (value is JArray || value is JRaw || value is JConstructor) { Assert.IsTrue(value.ToString().CompareTo(o.ToString()) == 0); } + else if (value is JObject) + { + Assert.IsTrue(JToken.DeepEquals(value as JObject, o as JObject)); + } } found = true; } diff --git a/ReactNative/Modules/Storage/AsyncStorageModule.cs b/ReactNative/Modules/Storage/AsyncStorageModule.cs index 90aeab5b8c0..ebb3512a117 100644 --- a/ReactNative/Modules/Storage/AsyncStorageModule.cs +++ b/ReactNative/Modules/Storage/AsyncStorageModule.cs @@ -1,7 +1,9 @@ using Newtonsoft.Json.Linq; using ReactNative.Bridge; +using System; using System.Diagnostics; using Windows.Storage; +using System.Collections.Generic; namespace ReactNative.Modules.Storage { @@ -13,8 +15,13 @@ public class AsyncStorageModule : NativeModuleBase private enum _DataType { Null, - String, Array, + Constructor, + Date, + Object, + Uri, + String, + Raw, } private const string _invalidKey = "Invalid key"; @@ -47,18 +54,29 @@ public override string Name } /// - /// Given an array of keys, this returns a map of (key, value) pairs for the keys found, and - /// (key, null) for the keys that haven't been found. + /// Given an array of keys, this returns through an a + /// of (key, value) pairs for the keys found, and (key, null) for the keys that haven't been found. /// /// Array of key values /// Callback. [ReactMethod] public void multiGet(string[] keys, ICallback callback) { - Debug.Assert(keys != null); Debug.Assert(callback != null); var result = new JArray(); + + if (keys == null) + { + result.Add(new JArray + { + _invalidKey, + JValue.CreateNull(), + }); + callback.Invoke(result); + return; + } + foreach (var key in keys) { result.Add(new JArray @@ -67,21 +85,35 @@ public void multiGet(string[] keys, ICallback callback) getTokenFromContainer(key), }); } + callback.Invoke(result); } /// - /// Inserts multiple (key, value) pairs. The insertion will replace conflicting (key, value) pairs. + /// Inserts multiple (key, value) pairs from a . + /// The insertion will replace conflicting (key, value) pairs. + /// When done invokes with possible errors. /// /// Array of key values /// Callback. [ReactMethod] public void multiSet(JArray keyValueArray, ICallback callback) { - Debug.Assert(keyValueArray != null); Debug.Assert(callback != null); var result = new JArray(); + + if (keyValueArray == null) + { + result.Add(new JArray + { + _invalidKey, + JValue.CreateNull(), + }); + callback.Invoke(result); + return; + } + foreach (var keyValue in keyValueArray) { if (keyValue.Type == JTokenType.Array && keyValue.Value().Count == 2) @@ -109,21 +141,34 @@ public void multiSet(JArray keyValueArray, ICallback callback) }); } } + callback.Invoke(result); } /// /// Removes all rows of the keys given. + /// When done invokes . /// /// Array of key values /// Callback. [ReactMethod] public void multiRemove(string[] keys, ICallback callback) { - Debug.Assert(keys != null); Debug.Assert(callback != null); var result = new JArray(); + + if (keys == null) + { + result.Add(new JArray + { + _invalidKey, + JValue.CreateNull(), + }); + callback.Invoke(result); + return; + } + foreach (var key in keys) { _dataContainer.Values.Remove(key); @@ -133,18 +178,30 @@ public void multiRemove(string[] keys, ICallback callback) } /// - /// Given an array of (key, value) pairs, this will merge the given values with the stored values + /// Given a of (key, value) pairs, this will merge the given values with the stored values /// of the given keys, if they exist. + /// When done invokes with possible errors. /// /// Array of key values /// Callback. [ReactMethod] public void multiMerge(JArray keyValueArray, ICallback callback) { - Debug.Assert(keyValueArray != null); Debug.Assert(callback != null); var result = new JArray(); + + if (keyValueArray == null) + { + result.Add(new JArray + { + _invalidKey, + JValue.CreateNull(), + }); + callback.Invoke(result); + return; + } + foreach (var keyValue in keyValueArray) { if (keyValue.Type == JTokenType.Array && keyValue.Value().Count == 2) @@ -156,23 +213,25 @@ public void multiMerge(JArray keyValueArray, ICallback callback) var tokenOld = getTokenFromContainer(key); var tokenNew = pair.Last; - if (tokenOld.Type != JTokenType.Null) + if (tokenOld.Type == JTokenType.Object && tokenNew.Type == JTokenType.Object) + { + deepMergeInto((JObject)tokenOld, (JObject)tokenNew); + addTokenToContainer(key, tokenOld); + } + else if (tokenOld.Type == JTokenType.Array) + { + (tokenOld as JArray).Merge(tokenNew); + addTokenToContainer(key, tokenOld); + } + else if (tokenNew.Type == JTokenType.Array) { - if (tokenOld.Type == JTokenType.Array) - { - ((JArray)tokenOld).Merge(tokenNew); - tokenNew = tokenOld; - } - else if (tokenNew.Type == JTokenType.Array) - { - ((JArray)tokenNew).Merge(tokenOld); - } - else if (tokenNew.Type == JTokenType.Null) - { - tokenNew = tokenOld; - } + (tokenNew as JArray).Merge(tokenOld); + addTokenToContainer(key, tokenNew); + } + else if (tokenOld.Type == JTokenType.Null) + { + addTokenToContainer(key, tokenNew); } - addTokenToContainer(key, tokenNew); } else { @@ -192,11 +251,13 @@ public void multiMerge(JArray keyValueArray, ICallback callback) }); } } + callback.Invoke(result); } /// - /// Clears the database. + /// Clears the . + /// When done invokes . /// /// Callback. [ReactMethod] @@ -211,7 +272,8 @@ public void clear(ICallback callback) } /// - /// Returns an array with all keys from the database. + /// Returns a with all keys from the + /// through . /// /// Callback. [ReactMethod] @@ -219,11 +281,11 @@ public void getAllKeys(ICallback callback) { Debug.Assert(callback != null); - callback.Invoke(JToken.FromObject(_dataContainer.Values.Keys)); + callback.Invoke(new JArray(_dataContainer.Values.Keys)); } /// - /// Adds JToken to ApplicationDataContainer with the specified key. + /// Adds to with the specified key. /// /// The key. /// The token. @@ -244,6 +306,20 @@ private void addTokenToContainer(string key, JToken token) case JTokenType.Float: _dataContainer.Values[key] = token.Value(); break; + case JTokenType.TimeSpan: + _dataContainer.Values[key] = token.Value(); + break; + case JTokenType.Guid: + _dataContainer.Values[key] = token.Value(); + break; + case JTokenType.Date: + _dataContainer.Values[key] = token.ToString(); + addTokenTypeToContainer(key, _DataType.Date); + break; + case JTokenType.Uri: + _dataContainer.Values[key] = token.ToString(); + addTokenTypeToContainer(key, _DataType.Uri); + break; case JTokenType.String: _dataContainer.Values[key] = token.Value(); addTokenTypeToContainer(key, _DataType.String); @@ -252,54 +328,67 @@ private void addTokenToContainer(string key, JToken token) _dataContainer.Values[key] = token.ToString(); addTokenTypeToContainer(key, _DataType.Array); break; + case JTokenType.Constructor: + _dataContainer.Values[key] = token.ToString(); + addTokenTypeToContainer(key, _DataType.Constructor); + break; + case JTokenType.Object: + _dataContainer.Values[key] = token.ToString(); + addTokenTypeToContainer(key, _DataType.Object); + break; + case JTokenType.Raw: + _dataContainer.Values[key] = token.ToString(); + addTokenTypeToContainer(key, _DataType.Raw); + break; default: + Debug.Assert(false); // Not supported JToken type break; } } /// - /// Gets related value for a specific key from ApplicationDataContainer as JToken. + /// Gets related value for a specific key from as . /// /// The key. private JToken getTokenFromContainer(string key) { - var value = _dataContainer.Values[key]; - if (value == null) + object value; + if (_dataContainer.Values.TryGetValue(key, out value)) { - return JValue.CreateNull(); - } - else - { - var t = value.GetType(); - if (t == typeof(bool) || t == typeof(long) || t == typeof(double)) + if (value is bool || value is long || value is double || value is Guid || value is TimeSpan) { return JToken.FromObject(value); } - else if (t == typeof(string)) + else if (value is string) { var type = getTokenTypeFromContainer(key); - if (type == _DataType.Array) - { - return JToken.Parse((string)value); - } - else if (type == _DataType.String) - { - return JToken.FromObject((string)value); - } - else + switch (type) { - return JValue.CreateNull(); + case _DataType.Array: + return JArray.Parse(value as string); + case _DataType.Constructor: + return JToken.Parse(value as string); + case _DataType.Object: + return JObject.Parse(value as string); + case _DataType.Date: + return JToken.FromObject(DateTime.Parse(value as string)); + case _DataType.Uri: + return JToken.FromObject(new Uri(value as string)); + case _DataType.Raw: + return new JRaw(value); + case _DataType.String: + return JValue.CreateString(value as string); + default: + break; } } - else - { - return JValue.CreateNull(); - } } + + return JValue.CreateNull(); } - // - /// Adds token's type to ApplicationDataContainer with the specified key. + /// + /// Adds token's type to with the specified key. /// /// The key. /// The type. @@ -309,17 +398,64 @@ private void addTokenTypeToContainer(string key, _DataType type) } /// - /// Gets token's type for a specific key from ApplicationDataContainer. + /// Gets token's type for a specific key from . /// /// The key. private _DataType getTokenTypeFromContainer(string key) { - var value = _typeContainer.Values[key]; - if (value == null) + object value; + if (_typeContainer.Values.TryGetValue(key, out value)) + { + return (_DataType)value; + } + else { return _DataType.Null; } - return (_DataType)value; + } + + /// + /// Merge two . + /// + /// The old value. + /// The old value. + private static void deepMergeInto(JObject oldObj, JObject newObj) + { + IDictionary dictionary = newObj; + var keys = dictionary.Keys; + + foreach (var key in keys) + { + JToken tokenNew, tokenOld; + newObj.TryGetValue(key, out tokenNew); + oldObj.TryGetValue(key, out tokenOld); + if (tokenNew?.Type == JTokenType.Object && tokenOld?.Type == JTokenType.Object) + { + deepMergeInto(tokenOld as JObject, tokenNew as JObject); + putToJObject(key, tokenOld, oldObj); + } + else + { + var property = newObj.Property(key); + foreach (var token in property) + { + putToJObject(key, token, oldObj); + } + } + } + } + + /// + /// Puts an { , } item to . + /// Replaces existing value. + /// + /// The key. + /// The token. + /// The object. + private static void putToJObject(string key, JToken token, JObject obj) + { + obj.Remove(key); + obj.Add(key, token); } } }