diff --git a/CHANGELOG.md b/CHANGELOG.md index c150ebf..41510d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Better VC-4 support for IcdConsole + - JSON refactoring for simpler deserialization ## [8.3.0] - 2019-01-25 ### Added diff --git a/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs b/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs index c6e77dc..d174160 100644 --- a/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs +++ b/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Text; using ICD.Common.Utils.Extensions; +using ICD.Common.Utils.IO; using Newtonsoft.Json; using NUnit.Framework; @@ -17,6 +18,36 @@ namespace ICD.Common.Utils.Tests.Extensions Assert.Inconclusive(); } + [Test] + public void ReadObjectTest() + { + const string json = + "{\"name\":\"Test\",\"help\":\"Test test.\",\"type\":\"System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e\",\"value\":\"Test\"}"; + + Dictionary expected = new Dictionary + { + {"name", "Test"}, + {"help", "Test test."}, + {"type", "System.String, System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"}, + {"value", "Test"} + }; + + Dictionary deserialized = new Dictionary(); + + using (IcdStringReader textReader = new IcdStringReader(json)) + { + using (JsonReader reader = new JsonTextReader(textReader.WrappedTextReader)) + { + JsonSerializer serializer = new JsonSerializer(); + + reader.Read(); + reader.ReadObject(serializer, (p, r, s) => deserialized.Add(p, (string)r.Value)); + } + } + + Assert.IsTrue(deserialized.DictionaryEqual(expected)); + } + [Test] public void GetValueAsIntTest() { @@ -78,53 +109,5 @@ namespace ICD.Common.Utils.Tests.Extensions Assert.IsTrue(deserialized.SequenceEqual(new[] {1, 2, 3, 4})); } - - [Test] - public void SerializeDictionaryTest() - { - Dictionary dict = new Dictionary - { - {1, "Item 1"}, - {10, "Item 2"}, - {15, "Item 3"} - }; - - JsonSerializer serializer = new JsonSerializer(); - StringBuilder stringBuilder = new StringBuilder(); - - using (StringWriter stringWriter = new StringWriter(stringBuilder)) - { - using (JsonWriter writer = new JsonTextWriter(stringWriter)) - { - serializer.SerializeDictionary(writer, dict); - } - } - - string json = stringBuilder.ToString(); - Assert.AreEqual("{\"1\":\"Item 1\",\"10\":\"Item 2\",\"15\":\"Item 3\"}", json); - } - - [Test] - public void DeserializeDictionaryTest() - { - const string json = "{\"1\":\"Item 1\",\"10\":\"Item 2\",\"15\":\"Item 3\"}"; - - JsonSerializer serializer = new JsonSerializer(); - Dictionary deserialized; - - using (StringReader stringReader = new StringReader(json)) - { - using (JsonReader reader = new JsonTextReader(stringReader)) - { - reader.Read(); - deserialized = serializer.DeserializeDictionary(reader).ToDictionary(); - } - } - - Assert.AreEqual(3, deserialized.Count); - Assert.AreEqual("Item 1", deserialized[1]); - Assert.AreEqual("Item 2", deserialized[10]); - Assert.AreEqual("Item 3", deserialized[15]); - } } } diff --git a/ICD.Common.Utils.Tests/Json/JsonUtilsTest.cs b/ICD.Common.Utils.Tests/Json/JsonUtilsTest.cs index 49c5b3d..fe77594 100644 --- a/ICD.Common.Utils.Tests/Json/JsonUtilsTest.cs +++ b/ICD.Common.Utils.Tests/Json/JsonUtilsTest.cs @@ -2,7 +2,6 @@ using ICD.Common.Utils.IO; using ICD.Common.Utils.Json; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NUnit.Framework; namespace ICD.Common.Utils.Tests.Json @@ -141,32 +140,6 @@ namespace ICD.Common.Utils.Tests.Json Assert.AreEqual("test message", messageName); } - [Test] - public void DeserializeTypeTest() - { - const string json = "{\"test\":10}"; - - JObject root = JObject.Parse(json); - JToken token = root["test"]; - - int value = (int)JsonUtils.Deserialize(typeof(int), token); - - Assert.AreEqual(10, value); - } - - [Test] - public void DeserializeTestSerializerTest() - { - const string json = "{\"test\":10}"; - - JObject root = JObject.Parse(json); - JToken token = root["test"]; - - int value = (int)JsonUtils.Deserialize(typeof(int), token, new JsonSerializer()); - - Assert.AreEqual(10, value); - } - public sealed class TestSerializable { private const string PROPERTY_NAME = "Test"; diff --git a/ICD.Common.Utils/Extensions/JsonExtensions.cs b/ICD.Common.Utils/Extensions/JsonReaderExtensions.cs similarity index 54% rename from ICD.Common.Utils/Extensions/JsonExtensions.cs rename to ICD.Common.Utils/Extensions/JsonReaderExtensions.cs index 5bffe77..0ef7078 100644 --- a/ICD.Common.Utils/Extensions/JsonExtensions.cs +++ b/ICD.Common.Utils/Extensions/JsonReaderExtensions.cs @@ -3,25 +3,37 @@ using System.Collections.Generic; using System.Linq; using ICD.Common.Properties; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; namespace ICD.Common.Utils.Extensions { /// /// Extension methods for working with JSON. /// - public static class JsonExtensions + public static class JsonReaderExtensions { /// - /// Writes the object value. + /// Reads the current token in the reader and deserializes to the given type. /// + /// + /// + /// + public static T ReadAsObject(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + JsonSerializer serializer = new JsonSerializer(); + return extends.ReadAsObject(serializer); + } + + /// + /// Reads the current token in the reader and deserializes to the given type. + /// + /// /// - /// /// - /// - [PublicAPI] - public static void WriteObject(this JsonWriter extends, object value, JsonSerializer serializer, - JsonConverter converter) + /// + public static T ReadAsObject(this JsonReader extends, JsonSerializer serializer) { if (extends == null) throw new ArgumentNullException("extends"); @@ -29,36 +41,48 @@ namespace ICD.Common.Utils.Extensions if (serializer == null) throw new ArgumentNullException("serializer"); - if (converter == null) - throw new ArgumentNullException("converter"); - - JObject jObject = JObject.FromObject(value, serializer); - jObject.WriteTo(extends, converter); + return serializer.Deserialize(extends); } /// - /// Writes the type value. + /// Reads through the current object token and calls the callback for each property value. /// /// - /// - [PublicAPI] - public static void WriteType(this JsonWriter extends, Type type) + /// + /// + public static void ReadObject(this JsonReader extends, JsonSerializer serializer, + Action readPropertyValue) { if (extends == null) throw new ArgumentNullException("extends"); - if (type == null) - { - extends.WriteNull(); + if (serializer == null) + throw new ArgumentNullException("serializer"); + + if (readPropertyValue == null) + throw new ArgumentNullException("readPropertyValue"); + + if (extends.TokenType == JsonToken.Null) return; + + if (extends.TokenType != JsonToken.StartObject) + throw new FormatException(string.Format("Expected {0} got {1}", JsonToken.StartObject, extends.TokenType)); + + while (extends.Read()) + { + if (extends.TokenType == JsonToken.EndObject) + break; + + // Get the property + if (extends.TokenType != JsonToken.PropertyName) + continue; + string property = (string)extends.Value; + + // Read into the value + extends.Read(); + + readPropertyValue(property, extends, serializer); } - - // Find the smallest possible name representation for the type that will still resolve - string name = Type.GetType(type.FullName) == null - ? type.AssemblyQualifiedName - : type.FullName; - - extends.WriteValue(name); } /// @@ -166,58 +190,6 @@ namespace ICD.Common.Utils.Extensions return (T)(object)extends.GetValueAsInt(); } - /// - /// Serializes the given sequence of items to the writer. - /// - /// - /// - /// - /// - public static void SerializeArray(this JsonSerializer extends, JsonWriter writer, IEnumerable items) - { - if (extends == null) - throw new ArgumentNullException("extends"); - - if (writer == null) - throw new ArgumentNullException("writer"); - - if (items == null) - throw new ArgumentNullException("items"); - - extends.SerializeArray(writer, items, (s, w, item) => s.Serialize(w, item)); - } - - /// - /// Serializes the given sequence of items to the writer. - /// - /// - /// - /// - /// - /// - public static void SerializeArray(this JsonSerializer extends, JsonWriter writer, IEnumerable items, - Action write) - { - if (extends == null) - throw new ArgumentNullException("extends"); - - if (writer == null) - throw new ArgumentNullException("writer"); - - if (items == null) - throw new ArgumentNullException("items"); - - if (write == null) - throw new ArgumentNullException("write"); - - writer.WriteStartArray(); - { - foreach (TItem item in items) - write(extends, writer, item); - } - writer.WriteEndArray(); - } - /// /// Deserializes an array of items from the reader's current value. /// @@ -285,90 +257,5 @@ namespace ICD.Common.Utils.Extensions reader.Read(); } } - - /// - /// Serializes the given sequence of key-value-pairs to the writer. - /// - /// - /// - /// - /// - /// - public static void SerializeDictionary(this JsonSerializer extends, JsonWriter writer, - IEnumerable> items) - { - if (extends == null) - throw new ArgumentNullException("extends"); - - if (writer == null) - throw new ArgumentNullException("writer"); - - if (items == null) - throw new ArgumentNullException("items"); - - writer.WriteStartObject(); - { - foreach (KeyValuePair kvp in items) - { - writer.WritePropertyName(kvp.Key.ToString()); - extends.Serialize(writer, kvp.Value); - } - } - writer.WriteEndObject(); - } - - /// - /// Deserializes a dictionary of items from the reader's current value. - /// - /// - /// - /// - /// - public static IEnumerable> DeserializeDictionary(this JsonSerializer extends, - JsonReader reader) - { - if (extends == null) - throw new ArgumentNullException("extends"); - - if (reader == null) - throw new ArgumentNullException("reader"); - - if (reader.TokenType == JsonToken.Null) - return Enumerable.Empty>(); - - if (reader.TokenType != JsonToken.StartObject) - throw new FormatException(string.Format("Expected token {0} got {1}", JsonToken.StartObject, reader.TokenType)); - - return DeserializeDictionaryIterator(extends, reader); - } - - /// - /// Deserializes a dictionary of items from the reader's current value. - /// - /// - /// - /// - /// - private static IEnumerable> DeserializeDictionaryIterator( - JsonSerializer serializer, JsonReader reader) - { - // Step into the first key - reader.Read(); - - while (reader.TokenType != JsonToken.EndObject) - { - TKey key = (TKey)Convert.ChangeType(reader.Value, typeof(TKey), null); - - // Step into the value - reader.Read(); - - TValue value = serializer.Deserialize(reader); - - yield return new KeyValuePair(key, value); - - // Read out of the last value - reader.Read(); - } - } } } diff --git a/ICD.Common.Utils/Extensions/JsonWriterExtensions.cs b/ICD.Common.Utils/Extensions/JsonWriterExtensions.cs new file mode 100644 index 0000000..98b4fe8 --- /dev/null +++ b/ICD.Common.Utils/Extensions/JsonWriterExtensions.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using ICD.Common.Properties; +using Newtonsoft.Json; + +namespace ICD.Common.Utils.Extensions +{ + public static class JsonWriterExtensions + { + /// + /// Writes the type value. + /// + /// + /// + [PublicAPI] + public static void WriteType(this JsonWriter extends, Type type) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (type == null) + { + extends.WriteNull(); + return; + } + + // Find the smallest possible name representation for the type that will still resolve + string name = Type.GetType(type.FullName) == null + ? type.AssemblyQualifiedName + : type.FullName; + + extends.WriteValue(name); + } + + /// + /// Serializes the given sequence of items to the writer. + /// + /// + /// + /// + /// + public static void SerializeArray(this JsonSerializer extends, JsonWriter writer, IEnumerable items) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (writer == null) + throw new ArgumentNullException("writer"); + + if (items == null) + throw new ArgumentNullException("items"); + + extends.SerializeArray(writer, items, (s, w, item) => s.Serialize(w, item)); + } + + /// + /// Serializes the given sequence of items to the writer. + /// + /// + /// + /// + /// + /// + public static void SerializeArray(this JsonSerializer extends, JsonWriter writer, IEnumerable items, + Action write) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (writer == null) + throw new ArgumentNullException("writer"); + + if (items == null) + throw new ArgumentNullException("items"); + + if (write == null) + throw new ArgumentNullException("write"); + + writer.WriteStartArray(); + { + foreach (TItem item in items) + write(extends, writer, item); + } + writer.WriteEndArray(); + } + } +} diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index 103d153..64c2c65 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -106,6 +106,7 @@ + @@ -154,7 +155,7 @@ - + diff --git a/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs b/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs index 0b95a0c..e1bc3c1 100644 --- a/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs +++ b/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs @@ -1,5 +1,6 @@ using System; using ICD.Common.Properties; +using ICD.Common.Utils.Extensions; using Newtonsoft.Json; namespace ICD.Common.Utils.Json @@ -10,7 +11,10 @@ namespace ICD.Common.Utils.Json /// Creates a new instance of T. /// /// - protected abstract T Instantiate(); + protected virtual T Instantiate() + { + return ReflectionUtils.CreateInstance(); + } /// /// Writes the JSON representation of the object. @@ -26,12 +30,6 @@ namespace ICD.Common.Utils.Json if (serializer == null) throw new ArgumentNullException("serializer"); - if (value == null) - { - writer.WriteNull(); - return; - } - WriteJson(writer, (T)value, serializer); } @@ -113,30 +111,15 @@ namespace ICD.Common.Utils.Json if (serializer == null) throw new ArgumentNullException("serializer"); - T output = default(T); - bool instantiated = false; + if (reader.TokenType == JsonToken.Null) + return default(T); - while (reader.Read()) - { - if (reader.TokenType == JsonToken.Null || reader.TokenType == JsonToken.EndObject) - break; + if (reader.TokenType != JsonToken.StartObject) + throw new FormatException(string.Format("Expected {0} got {1}", JsonToken.StartObject, reader.TokenType)); - if (!instantiated) - { - instantiated = true; - output = Instantiate(); - } + T output = Instantiate(); - // Get the property - if (reader.TokenType != JsonToken.PropertyName) - continue; - string property = (string)reader.Value; - - // Read into the value - reader.Read(); - - ReadProperty(property, reader, output, serializer); - } + reader.ReadObject(serializer, (p, r, s) => ReadProperty(p, r, output, s)); return output; } diff --git a/ICD.Common.Utils/Json/JsonUtils.cs b/ICD.Common.Utils/Json/JsonUtils.cs index 56270bb..870bc56 100644 --- a/ICD.Common.Utils/Json/JsonUtils.cs +++ b/ICD.Common.Utils/Json/JsonUtils.cs @@ -22,28 +22,6 @@ namespace ICD.Common.Utils.Json private const string MESSAGE_NAME_PROPERTY = "m"; private const string MESSAGE_DATA_PROPERTY = "d"; - /// - /// Forces Newtonsoft to cache the given type for faster subsequent usage. - /// - /// - public static void CacheType() - where T : new() - { - CacheType(typeof(T)); - } - - /// - /// Forces Newtonsoft to cache the given type for faster subsequent usage. - /// - public static void CacheType(Type type) - { - if (type == null) - throw new ArgumentNullException("type"); - - string serialized = JsonConvert.SerializeObject(ReflectionUtils.CreateInstance(type)); - JsonConvert.DeserializeObject(serialized, type); - } - /// /// Gets the token as a DateTime value. /// @@ -87,31 +65,6 @@ namespace ICD.Common.Utils.Json } } - /// - /// Pretty-prints the JSON document. - /// - /// - [PublicAPI] - public static void Print(string json) - { - if (json == null) - throw new ArgumentNullException("json"); - - string formatted = Format(json); - IcdConsole.PrintLine(formatted); - } - - /// - /// Serializes the given item and pretty-prints to JSON. - /// - /// - [PublicAPI] - public static void Print(object value) - { - string formatted = Format(value); - IcdConsole.PrintLine(formatted); - } - /// /// Serializes the given item and formats the JSON into a human-readable form. /// @@ -234,6 +187,22 @@ namespace ICD.Common.Utils.Json } } + /// + /// Serializes to json, wrapping the object with a message property to differentiate between messages. + /// E.g. + /// { a = 1 } + /// Becomes + /// { m = "Test", d = { a = 1 } } + /// + /// + /// + /// + [PublicAPI] + public static string SerializeMessage(object value, string messageName) + { + return SerializeMessage(w => new JsonSerializer().Serialize(w, value), messageName); + } + /// /// Serializes to json, wrapping the object with a message property to differentiate between messages. /// E.g. @@ -317,44 +286,5 @@ namespace ICD.Common.Utils.Json }, json); } - - /// - /// Deserializes the given token based on the known type. - /// - /// - /// - /// - public static object Deserialize(Type type, JToken token) - { - if (type == null) - throw new ArgumentNullException("type"); - - if (token == null) - throw new ArgumentNullException("token"); - - return Deserialize(type, token, new JsonSerializer()); - } - - /// - /// Deserializes the given token based on the known type. - /// - /// - /// - /// - /// - public static object Deserialize(Type type, JToken token, JsonSerializer serializer) - { - if (type == null) - throw new ArgumentNullException("type"); - - if (token == null) - throw new ArgumentNullException("token"); - - if (serializer == null) - throw new ArgumentNullException("serializer"); - - using (JTokenReader jsonReader = new JTokenReader(token)) - return serializer.Deserialize(jsonReader, type); - } } } diff --git a/ICD.Common.Utils/RecursionUtils.cs b/ICD.Common.Utils/RecursionUtils.cs index 4d06381..755d705 100644 --- a/ICD.Common.Utils/RecursionUtils.cs +++ b/ICD.Common.Utils/RecursionUtils.cs @@ -109,7 +109,7 @@ namespace ICD.Common.Utils if (getChildren == null) throw new ArgumentNullException("getChildren"); - return BreadthFirstSearchPath(root, child, getChildren) != null; + return BreadthFirstSearch(root, child, getChildren, EqualityComparer.Default); } ///