diff --git a/VersionHistory.md b/VersionHistory.md index 643af90..d5aceaf 100644 --- a/VersionHistory.md +++ b/VersionHistory.md @@ -9,14 +9,21 @@ Prefix the description of the change with `[major]`, `[minor]` or `[patch]` in a ## Released +### 0.3.0 + +* [major] Remove `ReadOnlyDictionaryJsonConverter` and `DictionaryKeysAreNotPropertyNamesJsonConverter`. (Json.NET 9+ doesn't need them.) +* [minor] Add `OptionalJsonConverter` and use by default with `JsonUtility`. +* [minor] Add `DefaultValueDefaultAttribute` (used on `Optional` properties to distinguish null from missing). +* [major] Remove empty `IsoDateTimeOffsetJsonConverter` constructor. + ### 0.2.0 -* **Breaking:** Change .NET Framework minimum version to 4.6.1. -* **Breaking:** Remove `DefaultValueDefaultAttribute`. -* **Breaking:** Remove `JsonPointer` and `JsonPatch`. -* **Breaking:** Simplify `JsonInputSettings` and `JsonOutputSettings`. -* **Breaking:** Rename `JsonFilter.AlternatePathSepartor` to `AlternatePathSeparator`. -* **Breaking:** Move JToken-specific members from `JsonUtility` to `JTokenUtility`. +* [major] Change .NET Framework minimum version to 4.6.1. +* [major] Remove `DefaultValueDefaultAttribute`. +* [major] Remove `JsonPointer` and `JsonPatch`. +* [major] Simplify `JsonInputSettings` and `JsonOutputSettings`. +* [major] Rename `JsonFilter.AlternatePathSepartor` to `AlternatePathSeparator`. +* [major] Move JToken-specific members from `JsonUtility` to `JTokenUtility`. ### 0.1.1 diff --git a/src/Faithlife.Json/Converters/CamelCaseEnumJsonConverter.cs b/src/Faithlife.Json/Converters/CamelCaseEnumJsonConverter.cs index a297100..02f691f 100644 --- a/src/Faithlife.Json/Converters/CamelCaseEnumJsonConverter.cs +++ b/src/Faithlife.Json/Converters/CamelCaseEnumJsonConverter.cs @@ -1,5 +1,3 @@ -#pragma warning disable 1591 - using System; using Newtonsoft.Json; using Newtonsoft.Json.Converters; diff --git a/src/Faithlife.Json/Converters/DictionaryKeysAreNotPropertyNamesJsonConverter.cs b/src/Faithlife.Json/Converters/DictionaryKeysAreNotPropertyNamesJsonConverter.cs deleted file mode 100644 index 8cb78b5..0000000 --- a/src/Faithlife.Json/Converters/DictionaryKeysAreNotPropertyNamesJsonConverter.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections; -using System.Globalization; -using Faithlife.Utility; -using Newtonsoft.Json; - -#pragma warning disable 1591 - -namespace Faithlife.Json.Converters -{ - /// - /// JSON converter for IDictionary that ignores the contract resolver (e.g. CamelCasePropertyNamesContractResolver) - /// when converting dictionary keys to property names. - /// - public class DictionaryKeysAreNotPropertyNamesJsonConverter : JsonConverter - { - public override bool CanConvert(Type objectType) - { - return typeof(IDictionary).IsAssignableFrom(objectType); - } - - public override bool CanRead - { - get { return false; } - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - IDictionary dictionary = (IDictionary) value; - - writer.WriteStartObject(); - - foreach (DictionaryEntry entry in dictionary) - { - string key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture); - writer.WritePropertyName(key); - serializer.Serialize(writer, entry.Value); - } - - writer.WriteEndObject(); - } - } -} diff --git a/src/Faithlife.Json/Converters/EnumAsIntegerJsonConverter.cs b/src/Faithlife.Json/Converters/EnumAsIntegerJsonConverter.cs index 976cc65..ac7f497 100644 --- a/src/Faithlife.Json/Converters/EnumAsIntegerJsonConverter.cs +++ b/src/Faithlife.Json/Converters/EnumAsIntegerJsonConverter.cs @@ -1,5 +1,3 @@ -#pragma warning disable 1591 - using System; using Faithlife.Utility; using Newtonsoft.Json; diff --git a/src/Faithlife.Json/Converters/GuidLowerNoDashJsonConverter.cs b/src/Faithlife.Json/Converters/GuidLowerNoDashJsonConverter.cs index 0e8121a..7be09c3 100644 --- a/src/Faithlife.Json/Converters/GuidLowerNoDashJsonConverter.cs +++ b/src/Faithlife.Json/Converters/GuidLowerNoDashJsonConverter.cs @@ -1,5 +1,3 @@ -#pragma warning disable 1591 - using System; using Faithlife.Utility; using Newtonsoft.Json; diff --git a/src/Faithlife.Json/Converters/IsoDateOnlyJsonConverter.cs b/src/Faithlife.Json/Converters/IsoDateOnlyJsonConverter.cs index af8af8d..a2ab787 100644 --- a/src/Faithlife.Json/Converters/IsoDateOnlyJsonConverter.cs +++ b/src/Faithlife.Json/Converters/IsoDateOnlyJsonConverter.cs @@ -1,5 +1,3 @@ -#pragma warning disable 1591 - using System; using System.Globalization; using Newtonsoft.Json; diff --git a/src/Faithlife.Json/Converters/IsoDateTimeOffsetJsonConverter.cs b/src/Faithlife.Json/Converters/IsoDateTimeOffsetJsonConverter.cs index 0efaea8..67d9a70 100644 --- a/src/Faithlife.Json/Converters/IsoDateTimeOffsetJsonConverter.cs +++ b/src/Faithlife.Json/Converters/IsoDateTimeOffsetJsonConverter.cs @@ -11,13 +11,6 @@ namespace Faithlife.Json.Converters /// Uses DateTimeOffsetUtility.Iso8601Format public class IsoDateTimeOffsetJsonConverter : JsonConverterBase { - /// - /// Initializes a new instance of the class. - /// - public IsoDateTimeOffsetJsonConverter() - { - } - /// /// Overrides WriteCore. /// diff --git a/src/Faithlife.Json/Converters/OptionalJsonConverter.cs b/src/Faithlife.Json/Converters/OptionalJsonConverter.cs new file mode 100644 index 0000000..e48a3d4 --- /dev/null +++ b/src/Faithlife.Json/Converters/OptionalJsonConverter.cs @@ -0,0 +1,53 @@ +using System; +using System.Linq; +using System.Reflection; +using Faithlife.Utility; +using Newtonsoft.Json; + +namespace Faithlife.Json.Converters +{ + /// + /// Supports JSON conversion of Optional{T}. + /// + public class OptionalJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType.IsGenericType() && objectType.GetGenericTypeDefinition() == typeof(Optional<>); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + // make sure it has a value; optional instances without a value must be ignored + IOptional optional = (IOptional) value; + if (!optional.HasValue) + { + string optionalValueTypeName = optional.GetType().GenericTypeArguments.Single().Name; + throw new InvalidOperationException(("Optional<{0}>.HasValue is false. " + + "Optional properties should include these attributes: " + + "[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Include), DefaultValueDefault(typeof(Optional<{0}>))]") + .FormatInvariant(optionalValueTypeName)); + } + + // serialize value + serializer.Serialize(writer, optional.Value); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + // get T of Optional + Type optionalValueType = objectType.GenericTypeArguments.Single(); + + // deserialize using T + object optionalValue = serializer.Deserialize(reader, optionalValueType); + + // call Optional(T value) constructor + ConstructorInfo constructorInfo = GetConstructor(objectType, new[] { optionalValueType }); + Verify.IsNotNull(constructorInfo); + return constructorInfo.Invoke(new[] { optionalValue }); + } + + private static ConstructorInfo GetConstructor(Type type, Type[] types) + => type.GetTypeInfo().DeclaredConstructors.FirstOrDefault(x => x.IsPublic && EnumerableUtility.AreEqual(x.GetParameters().Select(p => p.ParameterType), types)); + } +} diff --git a/src/Faithlife.Json/Converters/ReadOnlyDictionaryJsonConverter.cs b/src/Faithlife.Json/Converters/ReadOnlyDictionaryJsonConverter.cs deleted file mode 100644 index 086a8fc..0000000 --- a/src/Faithlife.Json/Converters/ReadOnlyDictionaryJsonConverter.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using Faithlife.Utility; -using Newtonsoft.Json; - -#pragma warning disable 1591 - -namespace Faithlife.Json.Converters -{ - /// - /// JSON converter for ReadOnlyDictionary. - /// - /// When deserializing, the ReadOnlyDictionary wraps a Dictionary. - [Obsolete("Json.NET has built-in support for ReadOnlyDictionary")] - public class ReadOnlyDictionaryJsonConverter : DictionaryKeysAreNotPropertyNamesJsonConverter - { - public override bool CanConvert(Type objectType) - { - return objectType.IsGenericType() && objectType.GetGenericTypeDefinition() == typeof(ReadOnlyDictionary<,>); - } - - public override bool CanRead - { - get { return true; } - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(objectType.GetGenericArguments()); - object dictionary = serializer.Deserialize(reader, dictionaryType); - - if (dictionary == null) - return null; - return objectType.GetConstructor(new[] { dictionaryType }).Invoke(new[] { dictionary }); - } - } -} diff --git a/src/Faithlife.Json/DefaultValueDefaultAttribute.cs b/src/Faithlife.Json/DefaultValueDefaultAttribute.cs new file mode 100644 index 0000000..65632d0 --- /dev/null +++ b/src/Faithlife.Json/DefaultValueDefaultAttribute.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel; + +namespace Faithlife.Json +{ + /// + /// Sets the DefaultValue to new T() for the specified type. + /// + public sealed class DefaultValueDefaultAttribute : DefaultValueAttribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type. + public DefaultValueDefaultAttribute(Type type) + : base(Activator.CreateInstance(type)) + { + } + } +} diff --git a/src/Faithlife.Json/Faithlife.Json.csproj b/src/Faithlife.Json/Faithlife.Json.csproj index dbe0670..31f60ea 100644 --- a/src/Faithlife.Json/Faithlife.Json.csproj +++ b/src/Faithlife.Json/Faithlife.Json.csproj @@ -1,6 +1,6 @@ - 0.2.0 + 0.3.0 netstandard1.4;netstandard2.0;net461 $(MONO_ROOT)/lib/mono/4.6.1-api/ True @@ -19,7 +19,7 @@ https://github.com/Faithlife/FaithlifeJson.git - + diff --git a/src/Faithlife.Json/JTokenUtility.cs b/src/Faithlife.Json/JTokenUtility.cs index 4832925..5b961a2 100644 --- a/src/Faithlife.Json/JTokenUtility.cs +++ b/src/Faithlife.Json/JTokenUtility.cs @@ -24,19 +24,13 @@ public static class JTokenUtility /// The left JToken. /// The right JToken. /// True if the two JTokens are equal. - public static bool AreEqual(JToken left, JToken right) - { - return EqualityComparer.Equals(left, right); - } + public static bool AreEqual(JToken left, JToken right) => EqualityComparer.Equals(left, right); /// /// Returns a Boolean corresponding to the JToken if possible. /// /// This method returns null if the JToken is null or if it doesn't contain a Boolean. - public static bool? AsBoolean(this JToken jToken) - { - return jToken != null && jToken.Type == JTokenType.Boolean ? (bool) jToken : default(bool?); - } + public static bool? AsBoolean(this JToken jToken) => jToken != null && jToken.Type == JTokenType.Boolean ? (bool) jToken : default(bool?); /// /// Returns a Decimal corresponding to the JToken if possible. @@ -116,8 +110,7 @@ public static bool AreEqual(JToken left, JToken right) /// on the number itself (since numeric representations of the number could lose precision). public static JValue AsNumber(this JToken jToken) { - JValue jValue = jToken as JValue; - if (jValue == null) + if (!(jToken is JValue jValue)) return null; JTokenType jTokenType = jValue.Type; return jTokenType == JTokenType.Integer || jTokenType == JTokenType.Float ? jValue : null; @@ -127,10 +120,7 @@ public static JValue AsNumber(this JToken jToken) /// Returns a string corresponding to the JToken if possible. /// /// This method returns null if the JToken is null, or if it doesn't contain a string. - public static string AsString(this JToken jToken) - { - return jToken != null && jToken.Type == JTokenType.String ? (string) jToken : null; - } + public static string AsString(this JToken jToken) => jToken != null && jToken.Type == JTokenType.String ? (string) jToken : null; /// /// Returns true if the JToken is null or represents null. @@ -140,8 +130,7 @@ public static bool IsNull(this JToken jToken) if (jToken == null) return true; - JValue jValue = jToken as JValue; - return jValue != null && jValue.Value == null; + return jToken is JValue jValue && jValue.Value == null; } /// @@ -151,11 +140,7 @@ public static bool IsNull(this JToken jToken) /// The index of the array item. /// This method returns null if the JToken is null, or if it doesn't contain an array, /// or if the index is out of bounds. - public static JToken TryGetValue(this JToken jToken, int itemIndex) - { - JArray jArray = jToken as JArray; - return jArray != null && itemIndex >= 0 && itemIndex < jArray.Count ? jArray[itemIndex] : null; - } + public static JToken TryGetValue(this JToken jToken, int itemIndex) => jToken is JArray jArray && itemIndex >= 0 && itemIndex < jArray.Count ? jArray[itemIndex] : null; /// /// Returns the specified property value if possible. @@ -164,11 +149,7 @@ public static JToken TryGetValue(this JToken jToken, int itemIndex) /// The name of the property. /// This method returns null if the JToken is null, or if it doesn't contain an object, /// or if the property name is null, or if the property doesn't exist. - public static JToken TryGetValue(this JToken jToken, string propertyName) - { - JObject jObject = jToken as JObject; - return jObject != null && propertyName != null ? jObject[propertyName] : null; - } + public static JToken TryGetValue(this JToken jToken, string propertyName) => jToken is JObject jObject && propertyName != null ? jObject[propertyName] : null; /// /// Gets a persistent hash code for the token. @@ -273,8 +254,7 @@ public bool Equals(JToken left, JToken right) // allow properties to be in any order, but make sure they have the same names and values foreach (KeyValuePair leftProperty in leftProperties) { - JToken rightValue; - if (!rightProperties.TryGetValue(leftProperty.Key, out rightValue)) + if (!rightProperties.TryGetValue(leftProperty.Key, out var rightValue)) return false; if (!Equals(leftProperty.Value, rightValue)) return false; diff --git a/src/Faithlife.Json/JsonUtility.cs b/src/Faithlife.Json/JsonUtility.cs index aec052a..1138b2b 100644 --- a/src/Faithlife.Json/JsonUtility.cs +++ b/src/Faithlife.Json/JsonUtility.cs @@ -21,10 +21,7 @@ public static class JsonUtility /// /// The value. /// The JSON. - public static string ToJson(object value) - { - return ToJson(value, null); - } + public static string ToJson(object value) => ToJson(value, null); /// /// Converts the object to JSON. @@ -46,10 +43,7 @@ public static string ToJson(object value, JsonSettings settings) /// /// The value. /// The JSON writer to write JSON to. - public static void ToJsonWriter(object value, JsonWriter jsonWriter) - { - ToJsonWriter(value, null, jsonWriter); - } + public static void ToJsonWriter(object value, JsonWriter jsonWriter) => ToJsonWriter(value, null, jsonWriter); /// /// Converts the object to a JSON writer. @@ -68,10 +62,7 @@ public static void ToJsonWriter(object value, JsonSettings settings, JsonWriter /// /// The value. /// The text writer to write JSON to. - public static void ToJsonTextWriter(object value, TextWriter textWriter) - { - ToJsonTextWriter(value, null, textWriter); - } + public static void ToJsonTextWriter(object value, TextWriter textWriter) => ToJsonTextWriter(value, null, textWriter); /// /// Converts the object to a JSON text writer. @@ -91,10 +82,7 @@ public static void ToJsonTextWriter(object value, JsonSettings settings, TextWri /// /// The value. /// The stream to write JSON to, using UTF-8 encoding. - public static void ToJsonStream(object value, Stream outputStream) - { - ToJsonStream(value, null, outputStream); - } + public static void ToJsonStream(object value, Stream outputStream) => ToJsonStream(value, null, outputStream); /// /// Converts the object to a JSON stream. @@ -115,10 +103,7 @@ public static void ToJsonStream(object value, JsonSettings settings, Stream outp /// /// The value. /// The compressed JSON (as compressed by StringUtility). - public static byte[] ToCompressedJson(object value) - { - return ToCompressedJson(value, null); - } + public static byte[] ToCompressedJson(object value) => ToCompressedJson(value, null); /// /// Converts the object to JSON. @@ -141,10 +126,7 @@ public static byte[] ToCompressedJson(object value, JsonSettings settings) /// /// The value. /// The number of bytes used by the JSON of an object. - public static int ToJsonByteCount(object value) - { - return ToJsonByteCount(value, null); - } + public static int ToJsonByteCount(object value) => ToJsonByteCount(value, null); /// /// Returns the number of bytes used by the JSON of an object. @@ -166,10 +148,7 @@ public static int ToJsonByteCount(object value, JsonSettings settings) /// /// The value. /// The JToken. - public static JToken ToJToken(object value) - { - return ToJToken(value, null); - } + public static JToken ToJToken(object value) => ToJToken(value, null); /// /// Converts the object to a JToken. @@ -194,10 +173,7 @@ public static JToken ToJToken(object value, JsonSettings settings) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static T FromJson(string json) - { - return FromJson(json, null); - } + public static T FromJson(string json) => FromJson(json, null); /// /// Creates an object from JSON. @@ -207,10 +183,7 @@ public static T FromJson(string json) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static object FromJson(string json, Type type) - { - return FromJson(json, type, null); - } + public static object FromJson(string json, Type type) => FromJson(json, type, null); /// /// Creates an object from JSON. @@ -221,10 +194,7 @@ public static object FromJson(string json, Type type) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static T FromJson(string json, JsonSettings settings) - { - return (T) FromJson(json, typeof(T), settings); - } + public static T FromJson(string json, JsonSettings settings) => (T) FromJson(json, typeof(T), settings); /// /// Creates an object from JSON. @@ -248,10 +218,7 @@ public static object FromJson(string json, Type type, JsonSettings settings) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static T FromJsonTextReader(TextReader textReader) - { - return FromJsonTextReader(textReader, null); - } + public static T FromJsonTextReader(TextReader textReader) => FromJsonTextReader(textReader, null); /// /// Creates an object from JSON. @@ -261,10 +228,7 @@ public static T FromJsonTextReader(TextReader textReader) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static object FromJsonTextReader(TextReader textReader, Type type) - { - return FromJsonTextReader(textReader, type, null); - } + public static object FromJsonTextReader(TextReader textReader, Type type) => FromJsonTextReader(textReader, type, null); /// /// Creates an object from JSON. @@ -274,10 +238,7 @@ public static object FromJsonTextReader(TextReader textReader, Type type) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static T FromJsonTextReader(TextReader textReader, JsonSettings settings) - { - return (T) FromJsonTextReader(textReader, typeof(T), settings); - } + public static T FromJsonTextReader(TextReader textReader, JsonSettings settings) => (T) FromJsonTextReader(textReader, typeof(T), settings); /// /// Creates an object from JSON. @@ -302,10 +263,7 @@ public static object FromJsonTextReader(TextReader textReader, Type type, JsonSe /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static T FromCompressedJson(byte[] json) - { - return FromCompressedJson(json, null); - } + public static T FromCompressedJson(byte[] json) => FromCompressedJson(json, null); /// /// Creates an object from compressed JSON. @@ -315,10 +273,7 @@ public static T FromCompressedJson(byte[] json) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static object FromCompressedJson(byte[] json, Type type) - { - return FromCompressedJson(json, type, null); - } + public static object FromCompressedJson(byte[] json, Type type) => FromCompressedJson(json, type, null); /// /// Creates an object from compressed JSON. @@ -329,10 +284,7 @@ public static object FromCompressedJson(byte[] json, Type type) /// The object. /// The text is not valid JSON. /// The JSON cannot be deserialized into the specified type. - public static T FromCompressedJson(byte[] json, JsonSettings settings) - { - return (T) FromCompressedJson(json, typeof(T), settings); - } + public static T FromCompressedJson(byte[] json, JsonSettings settings) => (T) FromCompressedJson(json, typeof(T), settings); /// /// Creates an object from compressed JSON. @@ -356,10 +308,7 @@ public static object FromCompressedJson(byte[] json, Type type, JsonSettings set /// The type of object to create. /// The JToken. /// The object. - public static T FromJToken(JToken json) - { - return FromJToken(json, null); - } + public static T FromJToken(JToken json) => FromJToken(json, null); /// /// Creates an object from a JToken. @@ -367,10 +316,7 @@ public static T FromJToken(JToken json) /// The JToken. /// The type. /// The object. - public static object FromJToken(JToken json, Type type) - { - return FromJToken(json, type, null); - } + public static object FromJToken(JToken json, Type type) => FromJToken(json, type, null); /// /// Creates an object from a JToken. @@ -379,10 +325,7 @@ public static object FromJToken(JToken json, Type type) /// The JToken. /// The settings. /// The object. - public static T FromJToken(JToken json, JsonSettings settings) - { - return (T) FromJToken(json, typeof(T), settings); - } + public static T FromJToken(JToken json, JsonSettings settings) => (T) FromJToken(json, typeof(T), settings); /// /// Creates an object from a JToken. @@ -404,10 +347,7 @@ public static object FromJToken(JToken json, Type type, JsonSettings settings) /// Creates default serialization settings. /// /// The serialization settings used by ToJson and FromJson. - public static JsonSerializerSettings CreateDefaultJsonSerializerSettings() - { - return CreateDefaultJsonSerializerSettings(null); - } + public static JsonSerializerSettings CreateDefaultJsonSerializerSettings() => CreateDefaultJsonSerializerSettings(null); /// /// Creates default serialization settings. @@ -415,23 +355,25 @@ public static JsonSerializerSettings CreateDefaultJsonSerializerSettings() /// The serialization settings used by ToJson. public static JsonSerializerSettings CreateDefaultJsonSerializerSettings(JsonSettings settings) { - settings = settings ?? new JsonSettings(); - JsonSerializerSettings serializerSettings = new JsonSerializerSettings { - ContractResolver = new CamelCasePropertyNamesContractResolver(), + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy + { + OverrideSpecifiedNames = false, + ProcessDictionaryKeys = false + } + }, DateParseHandling = DateParseHandling.None, - NullValueHandling = settings.IncludesNullValues ? NullValueHandling.Include : NullValueHandling.Ignore, - MissingMemberHandling = settings.RejectsExtraProperties ? MissingMemberHandling.Error : MissingMemberHandling.Ignore, + NullValueHandling = settings?.IncludesNullValues ?? false ? NullValueHandling.Include : NullValueHandling.Ignore, + MissingMemberHandling = settings?.RejectsExtraProperties ?? false ? MissingMemberHandling.Error : MissingMemberHandling.Ignore, MetadataPropertyHandling = MetadataPropertyHandling.Ignore, CheckAdditionalContent = true, }; - if (settings.Converters != null) - serializerSettings.Converters.AddRange(settings.Converters); - - if (settings.Converters != null) + if (settings?.Converters != null) serializerSettings.Converters.AddRange(settings.Converters); serializerSettings.Converters.AddRange(s_defaultConverters); @@ -444,10 +386,7 @@ public static JsonSerializerSettings CreateDefaultJsonSerializerSettings(JsonSet /// /// The settings. /// The JSON formatting. - public static Formatting GetJsonFormatting(JsonSettings settings) - { - return settings != null && settings.IsIndented ? Formatting.Indented : Formatting.None; - } + public static Formatting GetJsonFormatting(JsonSettings settings) => settings != null && settings.IsIndented ? Formatting.Indented : Formatting.None; private static object Deserialize(JsonSettings settings, JsonReader reader, Type type) { @@ -464,7 +403,7 @@ private static object Deserialize(JsonSettings settings, JsonReader reader, Type new CamelCaseEnumJsonConverter(), new IsoDateTimeUtcJsonConverter(), new IsoDateTimeOffsetJsonConverter(), - new DictionaryKeysAreNotPropertyNamesJsonConverter(), // NOTE: must be after any other dictionary converters + new OptionalJsonConverter(), }; } } diff --git a/src/Faithlife.Json/ThrowOnError.cs b/src/Faithlife.Json/ThrowOnError.cs deleted file mode 100644 index 19a40da..0000000 --- a/src/Faithlife.Json/ThrowOnError.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace Faithlife.Json -{ - /// - /// Indicates whether a method should throw an exception if it fails. - /// - internal enum ThrowOnError - { - /// - /// The method should not throw an exception if it fails. - /// - False, - - /// - /// The method should throw an exception if it fails. - /// - True - } - - /// - /// Utility methods for ThrowOnError. - /// - internal static class ThrowOnErrorUtility - { - /// - /// Throws the specified exception if required. - /// - /// The throw on error. - /// The exception thrown if throwOnError is True. - /// False if throwOnError is False. - /// If performance may be an issue, do not use this method. - public static bool TryThrow(this ThrowOnError throwOnError, Exception exception) - { - if (throwOnError == ThrowOnError.True) - throw exception; - return false; - } - } -} diff --git a/tests/Faithlife.Json.Tests/CamelCaseEnumJsonConverterTests.cs b/tests/Faithlife.Json.Tests/CamelCaseEnumJsonConverterTests.cs new file mode 100644 index 0000000..6fcf234 --- /dev/null +++ b/tests/Faithlife.Json.Tests/CamelCaseEnumJsonConverterTests.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; +using Faithlife.Json.Converters; +using NUnit.Framework; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class CamelCaseEnumJsonConverterTests + { + [Test] + public void UsingStringComparisons() + { + JsonConvert.SerializeObject(StringComparison.Ordinal).ShouldBe("4"); + JsonConvert.SerializeObject(StringComparison.Ordinal, new StringEnumConverter()).ShouldBe("\"Ordinal\""); + JsonConvert.SerializeObject(StringComparison.Ordinal, new StringEnumConverter { CamelCaseText = true }).ShouldBe("\"ordinal\""); + JsonConvert.SerializeObject(StringComparison.Ordinal, new CamelCaseEnumJsonConverter()).ShouldBe("\"ordinal\""); + JsonUtility.ToJson(StringComparison.Ordinal).ShouldBe("\"ordinal\""); + + JsonConvert.SerializeObject(StringComparison.OrdinalIgnoreCase).ShouldBe("5"); + JsonConvert.SerializeObject(StringComparison.OrdinalIgnoreCase, new StringEnumConverter()).ShouldBe("\"OrdinalIgnoreCase\""); + JsonConvert.SerializeObject(StringComparison.OrdinalIgnoreCase, new StringEnumConverter { CamelCaseText = true }).ShouldBe("\"ordinalIgnoreCase\""); + JsonConvert.SerializeObject(StringComparison.OrdinalIgnoreCase, new CamelCaseEnumJsonConverter()).ShouldBe("\"ordinalIgnoreCase\""); + JsonUtility.ToJson(StringComparison.OrdinalIgnoreCase).ShouldBe("\"ordinalIgnoreCase\""); + + JsonConvert.DeserializeObject("\"OrdinalIgnoreCase\"").ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("\"OrdinalIgnoreCase\"", new StringEnumConverter()).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("\"OrdinalIgnoreCase\"", new StringEnumConverter { CamelCaseText = true }).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("\"OrdinalIgnoreCase\"", new CamelCaseEnumJsonConverter()).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonUtility.FromJson("\"OrdinalIgnoreCase\"").ShouldBe(StringComparison.OrdinalIgnoreCase); + + JsonConvert.DeserializeObject("\"ordinalIgnoreCase\"").ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("\"ordinalIgnoreCase\"", new StringEnumConverter()).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("\"ordinalIgnoreCase\"", new StringEnumConverter { CamelCaseText = true }).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("\"ordinalIgnoreCase\"", new CamelCaseEnumJsonConverter()).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonUtility.FromJson("\"ordinalIgnoreCase\"").ShouldBe(StringComparison.OrdinalIgnoreCase); + + JsonConvert.DeserializeObject("5").ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("5", new StringEnumConverter()).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("5", new StringEnumConverter { CamelCaseText = true }).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonConvert.DeserializeObject("5", new CamelCaseEnumJsonConverter()).ShouldBe(StringComparison.OrdinalIgnoreCase); + JsonUtility.FromJson("5").ShouldBe(StringComparison.OrdinalIgnoreCase); + } + + [Test] + public void UsingFileOptions() + { + JsonConvert.SerializeObject(FileOptions.RandomAccess | FileOptions.DeleteOnClose).ShouldBe("335544320"); + JsonConvert.SerializeObject(FileOptions.RandomAccess | FileOptions.DeleteOnClose, new StringEnumConverter()).ShouldBe("\"DeleteOnClose, RandomAccess\""); + JsonConvert.SerializeObject(FileOptions.RandomAccess | FileOptions.DeleteOnClose, new StringEnumConverter { CamelCaseText = true }).ShouldBe("\"deleteOnClose, randomAccess\""); + JsonConvert.SerializeObject(FileOptions.RandomAccess | FileOptions.DeleteOnClose, new CamelCaseEnumJsonConverter()).ShouldBe("\"deleteOnClose, randomAccess\""); + JsonUtility.ToJson(FileOptions.RandomAccess | FileOptions.DeleteOnClose).ShouldBe("\"deleteOnClose, randomAccess\""); + + JsonConvert.DeserializeObject("335544320").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("335544320", new StringEnumConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("335544320", new StringEnumConverter { CamelCaseText = true }).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("335544320", new CamelCaseEnumJsonConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonUtility.FromJson("335544320").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + + JsonConvert.DeserializeObject("\"DeleteOnClose, RandomAccess\"").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\"DeleteOnClose, RandomAccess\"", new StringEnumConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\"DeleteOnClose, RandomAccess\"", new StringEnumConverter { CamelCaseText = true }).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\"DeleteOnClose, RandomAccess\"", new CamelCaseEnumJsonConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonUtility.FromJson("\"DeleteOnClose, RandomAccess\"").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + + JsonConvert.DeserializeObject("\"deleteOnClose, randomAccess\"").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\"deleteOnClose, randomAccess\"", new StringEnumConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\"deleteOnClose, randomAccess\"", new StringEnumConverter { CamelCaseText = true }).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\"deleteOnClose, randomAccess\"", new CamelCaseEnumJsonConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonUtility.FromJson("\"deleteOnClose, randomAccess\"").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + + JsonConvert.DeserializeObject("\" deleteOnClose ,randomAccess\"").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\" deleteOnClose ,randomAccess\"", new StringEnumConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\" deleteOnClose ,randomAccess\"", new StringEnumConverter { CamelCaseText = true }).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonConvert.DeserializeObject("\" deleteOnClose ,randomAccess\"", new CamelCaseEnumJsonConverter()).ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + JsonUtility.FromJson("\" deleteOnClose ,randomAccess\"").ShouldBe(FileOptions.RandomAccess | FileOptions.DeleteOnClose); + } + } +} diff --git a/tests/Faithlife.Json.Tests/EnumAsIntegerJsonConverterTests.cs b/tests/Faithlife.Json.Tests/EnumAsIntegerJsonConverterTests.cs new file mode 100644 index 0000000..e1f6e2f --- /dev/null +++ b/tests/Faithlife.Json.Tests/EnumAsIntegerJsonConverterTests.cs @@ -0,0 +1,65 @@ +using System; +using Faithlife.Json.Converters; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class EnumAsIntegerJsonConverterTests + { + [Test] + public void Serialize() + { + JsonConvert.SerializeObject(default(Enum?), new EnumAsIntegerJsonConverter()).ShouldBe("null"); + JsonConvert.SerializeObject(Enum.Test1, new EnumAsIntegerJsonConverter()).ShouldBe("1"); + JsonConvert.SerializeObject(FlagEnum.None, new EnumAsIntegerJsonConverter()).ShouldBe("0"); + JsonConvert.SerializeObject(FlagEnum.Flag1 | FlagEnum.Flag2 | FlagEnum.Flag3, new EnumAsIntegerJsonConverter()).ShouldBe("7"); + JsonConvert.SerializeObject(LittleEnum.Little0, new EnumAsIntegerJsonConverter()).ShouldBe("0"); + JsonConvert.SerializeObject(BigEnum.Big0, new EnumAsIntegerJsonConverter()).ShouldBe("2147483648"); + JsonUtility.ToJson(Enum.Test1, new JsonSettings { Converters = new[] { new EnumAsIntegerJsonConverter() } }).ShouldBe("1"); + } + + [Test] + public void Deserialize() + { + JsonConvert.DeserializeObject("1", new EnumAsIntegerJsonConverter()).ShouldBe(Enum.Test1); + JsonConvert.DeserializeObject("\"1\"", new EnumAsIntegerJsonConverter()).ShouldBe(Enum.Test1); + JsonConvert.DeserializeObject("0", new EnumAsIntegerJsonConverter()).ShouldBe(FlagEnum.None); + JsonConvert.DeserializeObject("1", new EnumAsIntegerJsonConverter()).ShouldBe(LittleEnum.Little1); + JsonConvert.DeserializeObject("2147483649", new EnumAsIntegerJsonConverter()).ShouldBe(BigEnum.Big1); + JsonConvert.DeserializeObject("7", new EnumAsIntegerJsonConverter()).ShouldBe(FlagEnum.Flag1 | FlagEnum.Flag2 | FlagEnum.Flag3); + JsonConvert.DeserializeObject("null", new EnumAsIntegerJsonConverter()).ShouldBe(null); + JsonConvert.DeserializeObject("\"Flag1, Flag3\"", new EnumAsIntegerJsonConverter()).ShouldBe(FlagEnum.Flag1 | FlagEnum.Flag3); + JsonConvert.DeserializeObject("\"flag2, flag3\"", new EnumAsIntegerJsonConverter()).ShouldBe(FlagEnum.Flag2 | FlagEnum.Flag3); + JsonConvert.DeserializeObject("\"big1\"", new EnumAsIntegerJsonConverter()).ShouldBe(BigEnum.Big1); + JsonUtility.FromJson("1", new JsonSettings { Converters = new[] { new EnumAsIntegerJsonConverter() } }).ShouldBe(Enum.Test1); + } + + private enum Enum + { + Test1 = 1, + } + + [Flags] + private enum FlagEnum + { + None = 0, + Flag1 = 1, + Flag2 = 2, + Flag3 = 4 + } + + private enum LittleEnum : short + { + Little0 = 0, + Little1 = 1, + } + + private enum BigEnum : long + { + Big0 = 0x80000000, + Big1 = 0x80000001, + } + } +} diff --git a/tests/Faithlife.Json.Tests/GuidLowerNoDashJsonConverterTests.cs b/tests/Faithlife.Json.Tests/GuidLowerNoDashJsonConverterTests.cs new file mode 100644 index 0000000..60fe2ff --- /dev/null +++ b/tests/Faithlife.Json.Tests/GuidLowerNoDashJsonConverterTests.cs @@ -0,0 +1,43 @@ +using System; +using Faithlife.Json.Converters; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class GuidLowerNoDashJsonConverterTests + { + [Test] + public void SaveAndLoad() + { + Guid start = new Guid("5f346a0d-a38b-40c2-9289-44c5ffe40752"); + Guid end = new Guid("4ea3a3fd-8892-46b5-8dca-370e64f6c4f9"); + + Guids guids = new Guids { Start = start }; + JsonConvert.SerializeObject(guids, new GuidLowerNoDashJsonConverter()).ShouldBe(@"{""Start"":""5f346a0da38b40c2928944c5ffe40752"",""End"":null}"); + guids.End = end; + JsonConvert.SerializeObject(guids, new GuidLowerNoDashJsonConverter()).ShouldBe(@"{""Start"":""5f346a0da38b40c2928944c5ffe40752"",""End"":""4ea3a3fd889246b58dca370e64f6c4f9""}"); + + guids = JsonConvert.DeserializeObject(@"{""Start"":""5f346a0da38b40c2928944c5ffe40752"",""End"":null}", new GuidLowerNoDashJsonConverter()); + guids.Start.ShouldBe(start); + guids.End.ShouldBe(null); + guids = JsonConvert.DeserializeObject(@"{""Start"":""5f346a0da38b40c2928944c5ffe40752"",""End"":""4ea3a3fd889246b58dca370e64f6c4f9""}", new GuidLowerNoDashJsonConverter()); + guids.Start.ShouldBe(start); + guids.End.ShouldBe(end); + } + + [Test] + public void Fail() + { + Assert.Throws(() => JsonConvert.DeserializeObject(@"{""Start"":""5f346a0d-a38b-40c2-9289-44c5ffe40752"",""End"":null}", new GuidLowerNoDashJsonConverter())); + Assert.Throws(() => JsonConvert.DeserializeObject(@"{""Start"":""5F346a0da38b40c2928944c5ffe40752"",""End"":null}", new GuidLowerNoDashJsonConverter())); + } + + public class Guids + { + public Guid Start { get; set; } + public Guid? End { get; set; } + } + } +} diff --git a/tests/Faithlife.Json.Tests/IsoDateOnlyJsonConverterTests.cs b/tests/Faithlife.Json.Tests/IsoDateOnlyJsonConverterTests.cs new file mode 100644 index 0000000..cb8d6a3 --- /dev/null +++ b/tests/Faithlife.Json.Tests/IsoDateOnlyJsonConverterTests.cs @@ -0,0 +1,40 @@ +using System; +using Faithlife.Json.Converters; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class IsoDateOnlyJsonConverterTests + { + [Test] + public void Elevens() + { + DateTime elevens = new DateTime(2011, 11, 11, 0, 0, 0, DateTimeKind.Utc); + DateTime twelves = new DateTime(2012, 12, 12, 0, 0, 0, DateTimeKind.Utc); + + DateTimes dates = new DateTimes { Start = elevens }; + JsonConvert.SerializeObject(dates, new IsoDateOnlyJsonConverter()).ShouldBe(@"{""Start"":""2011-11-11"",""End"":null}"); + dates.End = twelves; + JsonConvert.SerializeObject(dates, new IsoDateOnlyJsonConverter()).ShouldBe(@"{""Start"":""2011-11-11"",""End"":""2012-12-12""}"); + JsonUtility.ToJson(dates, new JsonSettings { Converters = new[] { new IsoDateOnlyJsonConverter() } }).ShouldBe(@"{""start"":""2011-11-11"",""end"":""2012-12-12""}"); + + dates = JsonConvert.DeserializeObject(@"{""Start"":""2011-11-11"",""End"":null}", new IsoDateOnlyJsonConverter()); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(null); + dates = JsonConvert.DeserializeObject(@"{""Start"":""2011-11-11"",""End"":""2012-12-12""}", new IsoDateOnlyJsonConverter()); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + dates = JsonUtility.FromJson(@"{""Start"":""2011-11-11"",""End"":""2012-12-12""}", new JsonSettings { Converters = new[] { new IsoDateOnlyJsonConverter() } }); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + } + + public class DateTimes + { + public DateTime Start { get; set; } + public DateTime? End { get; set; } + } + } +} diff --git a/tests/Faithlife.Json.Tests/IsoDateTimeOffsetJsonConverterTests.cs b/tests/Faithlife.Json.Tests/IsoDateTimeOffsetJsonConverterTests.cs new file mode 100644 index 0000000..119c146 --- /dev/null +++ b/tests/Faithlife.Json.Tests/IsoDateTimeOffsetJsonConverterTests.cs @@ -0,0 +1,79 @@ +using System; +using Faithlife.Json.Converters; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class IsoDateTimeOffsetJsonConverterTests + { + [Test] + public void TestWithoutOffset() + { + DateTimeOffset elevens = new DateTimeOffset(2011, 11, 11, 11, 11, 11, new TimeSpan(0, 0, 0)); + DateTimeOffset twelves = new DateTimeOffset(2012, 12, 12, 12, 12, 12, new TimeSpan(0, 0, 0)); + + DateTimeOffsets dates = new DateTimeOffsets { Start = elevens }; + JsonConvert.SerializeObject(dates, new IsoDateTimeOffsetJsonConverter()) + .ShouldBe(@"{""Start"":""2011-11-11T11:11:11+00:00"",""End"":null}"); + + dates.End = twelves; + JsonConvert.SerializeObject(dates, new IsoDateTimeOffsetJsonConverter()) + .ShouldBe(@"{""Start"":""2011-11-11T11:11:11+00:00"",""End"":""2012-12-12T12:12:12+00:00""}"); + JsonUtility.ToJson(dates) + .ShouldBe(@"{""start"":""2011-11-11T11:11:11+00:00"",""end"":""2012-12-12T12:12:12+00:00""}"); + + dates = JsonConvert.DeserializeObject( + @"{""Start"":""2011-11-11T11:11:11+00:00"",""End"":null}", new IsoDateTimeOffsetJsonConverter()); + + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(null); + dates = JsonConvert.DeserializeObject( + @"{""Start"":""2011-11-11T11:11:11+00:00"",""End"":""2012-12-12T12:12:12+00:00""}", new IsoDateTimeOffsetJsonConverter()); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + dates = JsonUtility.FromJson( + @"{""Start"":""2011-11-11T11:11:11+00:00"",""End"":""2012-12-12T12:12:12+00:00""}"); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + } + + [Test] + public void TestWithOffset() + { + DateTimeOffset elevens = new DateTimeOffset(2011, 11, 11, 11, 11, 11, new TimeSpan(5, 0, 0)); + DateTimeOffset twelves = new DateTimeOffset(2012, 12, 12, 12, 12, 12, new TimeSpan(5, 0, 0)); + + DateTimeOffsets dates = new DateTimeOffsets { Start = elevens }; + JsonConvert.SerializeObject(dates, new IsoDateTimeOffsetJsonConverter()) + .ShouldBe(@"{""Start"":""2011-11-11T11:11:11+05:00"",""End"":null}"); + + dates.End = twelves; + JsonConvert.SerializeObject(dates, new IsoDateTimeOffsetJsonConverter()) + .ShouldBe(@"{""Start"":""2011-11-11T11:11:11+05:00"",""End"":""2012-12-12T12:12:12+05:00""}"); + + dates = JsonConvert.DeserializeObject( + @"{""Start"":""2011-11-11T11:11:11+05:00"",""End"":null}", new IsoDateTimeOffsetJsonConverter()); + + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(null); + dates = JsonConvert.DeserializeObject( + @"{""Start"":""2011-11-11T11:11:11+05:00"",""End"":""2012-12-12T12:12:12+05:00""}", new IsoDateTimeOffsetJsonConverter()); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + } + + [Test] + public void SecondsRequired() + { + Assert.Throws(() => JsonUtility.FromJson(@"""2011-11-11T11:11+05:00""")); + } + + public class DateTimeOffsets + { + public DateTimeOffset Start { get; set; } + public DateTimeOffset? End { get; set; } + } + } +} diff --git a/tests/Faithlife.Json.Tests/IsoDateTimeUtcJsonConverterTests.cs b/tests/Faithlife.Json.Tests/IsoDateTimeUtcJsonConverterTests.cs new file mode 100644 index 0000000..60c0748 --- /dev/null +++ b/tests/Faithlife.Json.Tests/IsoDateTimeUtcJsonConverterTests.cs @@ -0,0 +1,51 @@ +using System; +using Faithlife.Json.Converters; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class IsoDateTimeUtcJsonConverterTests + { + [Test] + public void Elevens() + { + DateTime elevens = new DateTime(2011, 11, 11, 11, 11, 11, DateTimeKind.Utc); + DateTime twelves = new DateTime(2012, 12, 12, 12, 12, 12, DateTimeKind.Utc); + + DateTimes dates = new DateTimes { Start = elevens }; + JsonConvert.SerializeObject(dates, new IsoDateTimeUtcJsonConverter()).ShouldBe(@"{""Start"":""2011-11-11T11:11:11Z"",""End"":null}"); + dates.End = twelves; + JsonConvert.SerializeObject(dates, new IsoDateTimeUtcJsonConverter()).ShouldBe(@"{""Start"":""2011-11-11T11:11:11Z"",""End"":""2012-12-12T12:12:12Z""}"); + JsonUtility.ToJson(dates).ShouldBe(@"{""start"":""2011-11-11T11:11:11Z"",""end"":""2012-12-12T12:12:12Z""}"); + + JsonSerializerSettings settings = new JsonSerializerSettings + { + DateParseHandling = DateParseHandling.None, + Converters = { new IsoDateTimeUtcJsonConverter() } + }; + dates = JsonConvert.DeserializeObject(@"{""Start"":""2011-11-11T11:11:11Z"",""End"":null}", settings); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(null); + dates = JsonConvert.DeserializeObject(@"{""Start"":""2011-11-11T11:11:11Z"",""End"":""2012-12-12T12:12:12Z""}", settings); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + dates = JsonUtility.FromJson(@"{""Start"":""2011-11-11T11:11:11Z"",""End"":""2012-12-12T12:12:12Z""}"); + dates.Start.ShouldBe(elevens); + dates.End.ShouldBe(twelves); + } + + [Test] + public void SecondsRequired() + { + Assert.Throws(() => JsonUtility.FromJson(@"""2011-11-11T11:11Z""")); + } + + public class DateTimes + { + public DateTime Start { get; set; } + public DateTime? End { get; set; } + } + } +} diff --git a/tests/Faithlife.Json.Tests/OptionalJsonConverterTests.cs b/tests/Faithlife.Json.Tests/OptionalJsonConverterTests.cs new file mode 100644 index 0000000..f6d7f86 --- /dev/null +++ b/tests/Faithlife.Json.Tests/OptionalJsonConverterTests.cs @@ -0,0 +1,80 @@ +using System; +using Faithlife.Json.Converters; +using Faithlife.Utility; +using NUnit.Framework; +using Newtonsoft.Json; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class OptionalJsonConverterTests + { + [Test] + public void SaveAndLoad() + { + GoodOptionals optionals = new GoodOptionals(); + SaveAndLoad(optionals, @"{}"); + optionals.MyBoolean = false; + SaveAndLoad(optionals, @"{""MyBoolean"":false}"); + optionals.MyBoolean = true; + SaveAndLoad(optionals, @"{""MyBoolean"":true}"); + optionals.MyNullableBoolean = false; + SaveAndLoad(optionals, @"{""MyBoolean"":true,""MyNullableBoolean"":false}"); + optionals.MyBoolean = default(Optional); + optionals.MyNullableBoolean = true; + SaveAndLoad(optionals, @"{""MyNullableBoolean"":true}"); + optionals.MyNullableBoolean = null; + SaveAndLoad(optionals, @"{""MyNullableBoolean"":null}"); + } + + [Test] + public void SaveBadOptionals() + { + BadOptionals optionals = new BadOptionals(); + SaveBad(optionals); + optionals.MyBoolean = false; + SaveBad(optionals); + optionals.MyBoolean = true; + SaveBad(optionals); + optionals.MyNullableBoolean = false; + SaveAndLoad(optionals, @"{""MyBoolean"":true,""MyNullableBoolean"":false}"); // don't fail because HasValue isn't false + optionals.MyBoolean = default(Optional); + optionals.MyNullableBoolean = true; + SaveBad(optionals); + optionals.MyNullableBoolean = null; + SaveBad(optionals); + } + + public struct GoodOptionals + { + [JsonProperty("MyBoolean", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Include), DefaultValueDefault(typeof(Optional))] + public Optional MyBoolean { get; set; } + + [JsonProperty("MyNullableBoolean", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Include), DefaultValueDefault(typeof(Optional))] + public Optional MyNullableBoolean { get; set; } + } + + public struct BadOptionals + { + [JsonProperty("MyBoolean")] + public Optional MyBoolean { get; set; } + + [JsonProperty("MyNullableBoolean")] + public Optional MyNullableBoolean { get; set; } + } + + private void SaveAndLoad(T optionals, string json) + { + Assert.AreEqual(json, JsonConvert.SerializeObject(optionals, new OptionalJsonConverter()), $"Failed: {json}"); + Assert.AreEqual(optionals, JsonConvert.DeserializeObject(json, new OptionalJsonConverter()), $"Failed: {json}"); + + Assert.AreEqual(json, JsonUtility.ToJson(optionals), $"Failed: {json}"); + Assert.AreEqual(optionals, JsonUtility.FromJson(json), $"Failed: {json}"); + } + + private void SaveBad(T optionals) + { + Assert.Throws(() => JsonConvert.SerializeObject(optionals, new OptionalJsonConverter())); + } + } +} diff --git a/tests/Faithlife.Json.Tests/TestUtility.cs b/tests/Faithlife.Json.Tests/TestUtility.cs new file mode 100644 index 0000000..640939d --- /dev/null +++ b/tests/Faithlife.Json.Tests/TestUtility.cs @@ -0,0 +1,12 @@ +using NUnit.Framework; + +namespace Faithlife.Json.Tests +{ + internal static class TestUtility + { + public static void ShouldBe(this T actual, T expected) + { + Assert.AreEqual(expected, actual); + } + } +} diff --git a/tests/Faithlife.Json.Tests/UInt64JsonConverterTests.cs b/tests/Faithlife.Json.Tests/UInt64JsonConverterTests.cs new file mode 100644 index 0000000..e6b3c6d --- /dev/null +++ b/tests/Faithlife.Json.Tests/UInt64JsonConverterTests.cs @@ -0,0 +1,45 @@ +using System.IO; +using Faithlife.Json.Converters; +using Newtonsoft.Json; +using NUnit.Framework; + +namespace Faithlife.Json.Tests +{ + [TestFixture] + public class UInt64JsonConverterTests + { + [Test] + public void Serialize() + { + const ulong longMaxValue = long.MaxValue; + JsonConvert.SerializeObject(null, new UInt64JsonConverter()).ShouldBe("null"); + JsonConvert.SerializeObject(ulong.MinValue, new UInt64JsonConverter()).ShouldBe("\"0\""); + JsonConvert.SerializeObject(longMaxValue, new UInt64JsonConverter()).ShouldBe("\"9223372036854775807\""); + JsonConvert.SerializeObject(longMaxValue + 1, new UInt64JsonConverter()).ShouldBe("\"9223372036854775808\""); + JsonConvert.SerializeObject(ulong.MaxValue, new UInt64JsonConverter()).ShouldBe("\"18446744073709551615\""); + } + + [Test] + public void Deserialize() + { + const ulong longMaxValue = long.MaxValue; + JsonConvert.DeserializeObject("", new UInt64JsonConverter()).ShouldBe(null); + JsonConvert.DeserializeObject("null", new UInt64JsonConverter()).ShouldBe(null); + JsonConvert.DeserializeObject("\"0\"", new UInt64JsonConverter()).ShouldBe((ulong) 0); + JsonConvert.DeserializeObject("0", new UInt64JsonConverter()).ShouldBe((ulong) 0); + JsonConvert.DeserializeObject("\"9223372036854775807\"", new UInt64JsonConverter()).ShouldBe(longMaxValue); + JsonConvert.DeserializeObject("9223372036854775807", new UInt64JsonConverter()).ShouldBe(longMaxValue); + JsonConvert.DeserializeObject("\"9223372036854775808\"", new UInt64JsonConverter()).ShouldBe(longMaxValue + 1); + JsonConvert.DeserializeObject("\"18446744073709551615\"", new UInt64JsonConverter()).ShouldBe(ulong.MaxValue); + } + + [Test] + public void WriteNullValue() + { + UInt64JsonConverter converter = new UInt64JsonConverter(); + using (StringWriter stringWriter = new StringWriter()) + using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter)) + converter.WriteJson(jsonTextWriter, null, new JsonSerializer()); + } + } +}