diff --git a/ICD.Common.Utils.Tests/Extensions/EnumerableExtensionsTest.cs b/ICD.Common.Utils.Tests/Extensions/EnumerableExtensionsTest.cs index 70139af..13cdc40 100644 --- a/ICD.Common.Utils.Tests/Extensions/EnumerableExtensionsTest.cs +++ b/ICD.Common.Utils.Tests/Extensions/EnumerableExtensionsTest.cs @@ -374,6 +374,29 @@ namespace ICD.Common.Utils.Tests.Extensions Assert.AreEqual("C", values[2]); } + [Test] + public void DistinctTest() + { + string[] items = + { + "one", + "two", + "three" + }; + + items = items.Distinct(i => i.Length).ToArray(); + + Assert.AreEqual(2, items.Length); + Assert.AreEqual("one", items[0]); + Assert.AreEqual("three", items[1]); + } + + [Test] + public void DistinctComparerTest() + { + Assert.Inconclusive(); + } + [Test] public void UnanimousTest() { diff --git a/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs b/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs index 7327c7f..b759053 100644 --- a/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs +++ b/ICD.Common.Utils.Tests/Extensions/JsonExtensionsTest.cs @@ -1,4 +1,10 @@ -using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using ICD.Common.Utils.Extensions; +using Newtonsoft.Json; +using NUnit.Framework; namespace ICD.Common.Utils.Tests.Extensions { @@ -34,5 +40,91 @@ namespace ICD.Common.Utils.Tests.Extensions { Assert.Inconclusive(); } + + [Test] + public void SerializeArrayTest() + { + JsonSerializer serializer = new JsonSerializer(); + StringBuilder stringBuilder = new StringBuilder(); + + using (StringWriter stringWriter = new StringWriter(stringBuilder)) + { + using (JsonWriter writer = new JsonTextWriter(stringWriter)) + { + serializer.SerializeArray(writer, new int[] {1, 2, 3, 4}); + } + } + + string json = stringBuilder.ToString(); + Assert.AreEqual("[1,2,3,4]", json); + } + + [Test] + public void DeserializeArrayTest() + { + const string json = "[1,2,3,4]"; + + JsonSerializer serializer = new JsonSerializer(); + int[] deserialized; + + using (StringReader stringReader = new StringReader(json)) + { + using (JsonReader reader = new JsonTextReader(stringReader)) + { + reader.Read(); + deserialized = serializer.DeserializeArray(reader).ToArray(); + } + } + + Assert.IsTrue(deserialized.SequenceEqual(new int[] {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/AbstractGenericJsonConverterTest.cs b/ICD.Common.Utils.Tests/Json/AbstractGenericJsonConverterTest.cs new file mode 100644 index 0000000..c43fd6d --- /dev/null +++ b/ICD.Common.Utils.Tests/Json/AbstractGenericJsonConverterTest.cs @@ -0,0 +1,13 @@ +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests.Json +{ + public abstract class AbstractGenericJsonConverterTest + { + [Test] + public abstract void WriteJsonTest(); + + [Test] + public abstract void ReadJsonTest(); + } +} diff --git a/ICD.Common.Utils/Extensions/EnumerableExtensions.cs b/ICD.Common.Utils/Extensions/EnumerableExtensions.cs index f40fa3f..719d53c 100644 --- a/ICD.Common.Utils/Extensions/EnumerableExtensions.cs +++ b/ICD.Common.Utils/Extensions/EnumerableExtensions.cs @@ -653,23 +653,51 @@ namespace ICD.Common.Utils.Extensions } /// - /// Gets distinct elements from the sequence based on given callbacks. + /// Gets distinct elements from the sequence based on given property. /// - /// + /// + /// /// - /// - /// + /// /// [PublicAPI] - public static IEnumerable Distinct(this IEnumerable extends, Func comparer, Func getHashCode) + public static IEnumerable Distinct(this IEnumerable extends, + Func getProperty) { if (extends == null) throw new ArgumentNullException("extends"); - if (comparer == null) - throw new ArgumentNullException("comparer"); + if (getProperty == null) + throw new ArgumentNullException("getProperty"); - return extends.Distinct(new FuncComparer(comparer, getHashCode)); + IEqualityComparer comparer = EqualityComparer.Default; + return extends.Distinct(getProperty, comparer); + } + + /// + /// Gets distinct elements from the sequence based on given property. + /// + /// + /// + /// + /// + /// + /// + [PublicAPI] + public static IEnumerable Distinct(this IEnumerable extends, + Func getProperty, + IEqualityComparer propertyComparer) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (getProperty == null) + throw new ArgumentNullException("getProperty"); + + if (propertyComparer == null) + throw new ArgumentNullException("propertyComparer"); + + return extends.Distinct(new PropertyEqualityComparer(propertyComparer, getProperty)); } /// diff --git a/ICD.Common.Utils/Extensions/JsonExtensions.cs b/ICD.Common.Utils/Extensions/JsonExtensions.cs index 22ca920..be15396 100644 --- a/ICD.Common.Utils/Extensions/JsonExtensions.cs +++ b/ICD.Common.Utils/Extensions/JsonExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using ICD.Common.Properties; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -34,6 +35,44 @@ namespace ICD.Common.Utils.Extensions jObject.WriteTo(extends, converter); } + /// + /// Writes the type value. + /// + /// + /// + [PublicAPI] + public static void WriteType(this JsonWriter extends, Type type) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string name; + + if (type == null) + name = null; + else if (type.IsPrimitive) + name = type.Name; + else + name = type.AssemblyQualifiedName; + + extends.WriteValue(name); + } + + /// + /// Gets the current value as a Type. + /// + /// + /// + [PublicAPI] + public static Type GetValueAsType(this JsonReader extends) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + string value = extends.GetValueAsString(); + return Type.GetType(value, false, true); + } + /// /// Gets the current value as an integer. /// @@ -103,5 +142,183 @@ namespace ICD.Common.Utils.Extensions return EnumUtils.Parse(extends.GetValueAsString(), true); 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. + /// + /// + /// + /// + public static IEnumerable DeserializeArray(this JsonSerializer extends, JsonReader reader) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (reader == null) + throw new ArgumentNullException("reader"); + + return extends.DeserializeArray(reader, (s, r) => extends.Deserialize(reader)); + } + + /// + /// Deserializes an array of items from the reader's current value. + /// + /// + /// + /// + /// + public static IEnumerable DeserializeArray(this JsonSerializer extends, JsonReader reader, + Func read) + { + if (extends == null) + throw new ArgumentNullException("extends"); + + if (reader == null) + throw new ArgumentNullException("reader"); + + if (read == null) + throw new ArgumentNullException("read"); + + if (reader.TokenType == JsonToken.Null) + yield break; + + if (reader.TokenType != JsonToken.StartArray) + throw new FormatException(string.Format("Expected token {0} got {1}", JsonToken.StartArray, reader.TokenType)); + + // Step into the first value + reader.Read(); + + while (reader.TokenType != JsonToken.EndArray) + { + TItem output = read(extends, reader); + yield return output; + + // Read out of the last value + 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) + yield break; + + if (reader.TokenType != JsonToken.StartObject) + throw new FormatException(string.Format("Expected token {0} got {1}", JsonToken.StartObject, reader.TokenType)); + + // 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 = extends.Deserialize(reader); + + yield return new KeyValuePair(key, value); + + // Read out of the last value + reader.Read(); + } + } } } diff --git a/ICD.Common.Utils/Extensions/PropertyEqualityComparer.cs b/ICD.Common.Utils/Extensions/PropertyEqualityComparer.cs new file mode 100644 index 0000000..57ef41a --- /dev/null +++ b/ICD.Common.Utils/Extensions/PropertyEqualityComparer.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace ICD.Common.Utils.Extensions +{ + /// + /// Allows for comparing items based on some property. + /// + /// + /// + public sealed class PropertyEqualityComparer : IEqualityComparer + { + private readonly IEqualityComparer m_Comparer; + private readonly Func m_GetProperty; + + /// + /// Constructor. + /// + /// + /// + public PropertyEqualityComparer(IEqualityComparer comparer, Func getProperty) + { + m_Comparer = comparer; + m_GetProperty = getProperty; + } + + public bool Equals(TParent x, TParent y) + { + TProperty a = m_GetProperty(x); + TProperty b = m_GetProperty(y); + + return m_Comparer.Equals(a, b); + } + + public int GetHashCode(TParent parent) + { + TProperty property = m_GetProperty(parent); + return m_Comparer.GetHashCode(property); + } + } +} diff --git a/ICD.Common.Utils/Extensions/ReflectionExtensions.cs b/ICD.Common.Utils/Extensions/ReflectionExtensions.cs index 79f344b..7986791 100644 --- a/ICD.Common.Utils/Extensions/ReflectionExtensions.cs +++ b/ICD.Common.Utils/Extensions/ReflectionExtensions.cs @@ -42,7 +42,15 @@ namespace ICD.Common.Utils.Extensions if (extends == null) throw new ArgumentNullException("extends"); - return extends.GetCustomAttributes(typeof(T), inherits).Cast(); + try + { + return extends.GetCustomAttributes(typeof(T), inherits).Cast(); + } + // Crestron bug? + catch (ArgumentNullException) + { + return Enumerable.Empty(); + } } /// diff --git a/ICD.Common.Utils/FuncComparer.cs b/ICD.Common.Utils/FuncComparer.cs deleted file mode 100644 index 34b5894..0000000 --- a/ICD.Common.Utils/FuncComparer.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace ICD.Common.Utils -{ - /// - /// Simple comparer for comparing items using a callback. - /// - /// - public sealed class FuncComparer : IEqualityComparer - { - private readonly Func m_Comparer; - private readonly Func m_GetHashCode; - - /// - /// Constructor. - /// - /// - /// - public FuncComparer(Func comparer, Func getHashCode) - { - m_Comparer = comparer; - m_GetHashCode = getHashCode; - } - - public bool Equals(T x, T y) - { - return m_Comparer(x, y); - } - - public int GetHashCode(T obj) - { - return m_GetHashCode(obj); - } - } -} diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index 966b40e..665f60e 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -87,7 +87,7 @@ - + diff --git a/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs b/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs index 900b1ce..31a2a6e 100644 --- a/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs +++ b/ICD.Common.Utils/Json/AbstractGenericJsonConverter.cs @@ -45,9 +45,6 @@ namespace ICD.Common.Utils.Json public sealed override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { - if (reader.TokenType == JsonToken.Null) - return null; - return ReadJson(reader, (T)existingValue, serializer); } diff --git a/ICD.Common.Utils/Json/JsonUtils.cs b/ICD.Common.Utils/Json/JsonUtils.cs index 2e0a46b..4b49dfe 100644 --- a/ICD.Common.Utils/Json/JsonUtils.cs +++ b/ICD.Common.Utils/Json/JsonUtils.cs @@ -29,8 +29,16 @@ namespace ICD.Common.Utils.Json public static void CacheType() where T : new() { - string serialized = JsonConvert.SerializeObject(ReflectionUtils.CreateInstance()); - JsonConvert.DeserializeObject(serialized); + CacheType(typeof(T)); + } + + /// + /// Forces Newtonsoft to cache the given type for faster subsequent usage. + /// + public static void CacheType(Type type) + { + string serialized = JsonConvert.SerializeObject(ReflectionUtils.CreateInstance(type)); + JsonConvert.DeserializeObject(serialized, type); } /// diff --git a/ICD.Common.Utils/ReflectionUtils.cs b/ICD.Common.Utils/ReflectionUtils.cs index 0aaeab0..76b182b 100644 --- a/ICD.Common.Utils/ReflectionUtils.cs +++ b/ICD.Common.Utils/ReflectionUtils.cs @@ -210,6 +210,26 @@ namespace ICD.Common.Utils : null; } + /// + /// Returns true if the given type has a public parameterless constructor. + /// + /// + /// + public static bool HasPublicParameterlessConstructor(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + + const BindingFlags binding = BindingFlags.Instance | BindingFlags.Public; + +#if SIMPLSHARP + return ((CType)type).GetConstructor(binding, null, new CType[0], null) +#else + return type.GetConstructor(binding, null, new Type[0], null) +#endif + != null; + } + /// /// Creates an instance of the given type, calling the default constructor. /// @@ -240,6 +260,21 @@ namespace ICD.Common.Utils } } + /// + /// Creates an instance of the given type, calling the default constructor. + /// + /// + public static T CreateInstance(Type type) + { + if (type == null) + throw new ArgumentNullException("type"); + + if (!type.IsAssignableTo(typeof(T))) + throw new InvalidOperationException("Type is not assignable to T"); + + return (T)CreateInstance(type); + } + /// /// Gets the custom attributes added to the given assembly. ///