diff --git a/Directory.Build.props b/Directory.Build.props index 996cc43..0e3677a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 0.4.0-preview-01 + 0.4.0-preview-02 Stef Heyenrath 11 enable diff --git a/src/AnyOf.Newtonsoft.Json/AnyOf.Newtonsoft.Json.csproj b/src/AnyOf.Newtonsoft.Json/AnyOf.Newtonsoft.Json.csproj index eb2aba5..5390154 100644 --- a/src/AnyOf.Newtonsoft.Json/AnyOf.Newtonsoft.Json.csproj +++ b/src/AnyOf.Newtonsoft.Json/AnyOf.Newtonsoft.Json.csproj @@ -2,17 +2,15 @@ AnyOf.Newtonsoft.Json - net45;netstandard1.3;netstandard2.0;netstandard2.1 + net45;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net7.0 {31D0104F-3E63-4223-A4AD-372E68A6B0CB} Contains an AnyOfJsonConverter which can be used to serialize and deserialize an AnyOf<TFirst, TSecond, ...> type. AnyOf;types;AnyOfJsonConverter;JsonConverter;json;Newtonsoft AnyOfTypes.Newtonsoft.Json + 11 + enable - - - - diff --git a/src/AnyOf.Newtonsoft.Json/AnyOfJsonConverter.cs b/src/AnyOf.Newtonsoft.Json/AnyOfJsonConverter.cs index 73eea9a..bc195ae 100644 --- a/src/AnyOf.Newtonsoft.Json/AnyOfJsonConverter.cs +++ b/src/AnyOf.Newtonsoft.Json/AnyOfJsonConverter.cs @@ -5,7 +5,7 @@ using System.Reflection; using AnyOfTypes.System.Text.Json.Extensions; using AnyOfTypes.System.Text.Json.Matcher; -using AnyOfTypes.System.Text.Json.Matcher.Models; +using AnyOfTypes.System.Text.Json.Models; using Nelibur.ObjectMapper; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -187,10 +187,8 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s { var target = Activator.CreateInstance(bestType); - using (JsonReader jObjectReader = CopyReaderForObject(reader, jObject)) - { - serializer.Populate(jObjectReader, target); - } + using JsonReader jObjectReader = CopyReaderForObject(reader, jObject); + serializer.Populate(jObjectReader, target); return target; } @@ -200,7 +198,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s public override bool CanConvert(Type objectType) { - return objectType.FullName.StartsWith("AnyOfTypes.AnyOf`"); + return objectType.FullName?.StartsWith("AnyOfTypes.AnyOf`") == true; } private static JsonReader CopyReaderForObject(JsonReader reader, JObject jObject) diff --git a/src/AnyOf.System.Text.Json/AnyOf.System.Text.Json.csproj b/src/AnyOf.System.Text.Json/AnyOf.System.Text.Json.csproj index f657327..5d28ee3 100644 --- a/src/AnyOf.System.Text.Json/AnyOf.System.Text.Json.csproj +++ b/src/AnyOf.System.Text.Json/AnyOf.System.Text.Json.csproj @@ -2,11 +2,13 @@ AnyOf.System.Text.Json - netstandard2.0;net50;netcoreapp3.1 + netstandard2.0;netcoreapp3.1;net6.0;net7.0 {41D0104F-3E63-4223-A4AD-372E68A6B0CB} Contains an AnyOfJsonConverter which can be used to serialize and deserialize an AnyOf<TFirst, TSecond, ...> type. AnyOf;types;AnyOfJsonConverter;JsonConverter;json AnyOfTypes.System.Text.Json + 11 + enable diff --git a/src/AnyOf.System.Text.Json/AnyOfJsonConverter.cs b/src/AnyOf.System.Text.Json/AnyOfJsonConverter.cs index b041a41..727128a 100644 --- a/src/AnyOf.System.Text.Json/AnyOfJsonConverter.cs +++ b/src/AnyOf.System.Text.Json/AnyOfJsonConverter.cs @@ -6,231 +6,231 @@ using System.Text.Json.Serialization; using AnyOfTypes.System.Text.Json.Extensions; using AnyOfTypes.System.Text.Json.Matcher; -using AnyOfTypes.System.Text.Json.Matcher.Models; +using AnyOfTypes.System.Text.Json.Models; using Nelibur.ObjectMapper; -namespace AnyOfTypes.System.Text.Json +namespace AnyOfTypes.System.Text.Json; + +public class AnyOfJsonConverter : JsonConverter { - public class AnyOfJsonConverter : JsonConverter + private readonly bool _ignoreCase; + + public AnyOfJsonConverter(bool ignoreCase = true) { - private readonly bool _ignoreCase; + _ignoreCase = ignoreCase; + } - public AnyOfJsonConverter(bool ignoreCase = true) - { - _ignoreCase = ignoreCase; - } + public override object? Read(ref Utf8JsonReader reader, Type? typeToConvert, JsonSerializerOptions options) + { + object? value; - public override object? Read(ref Utf8JsonReader reader, Type? typeToConvert, JsonSerializerOptions options) - { - object? value; + var jsonElement = GetConverter(options).Read(ref reader, typeof(object), options); - var jsonElement = GetConverter(options).Read(ref reader, typeof(object), options); + switch (jsonElement.ValueKind) + { + case JsonValueKind.Array: + value = FindBestArrayMatch(jsonElement, typeToConvert, options); + break; - switch (jsonElement.ValueKind) - { - case JsonValueKind.Array: - value = FindBestArrayMatch(jsonElement, typeToConvert, options); - break; + case JsonValueKind.Object: + value = FindBestObjectMatch(jsonElement, typeToConvert?.GetGenericArguments() ?? Type.EmptyTypes, options); + break; - case JsonValueKind.Object: - value = FindBestObjectMatch(jsonElement, typeToConvert?.GetGenericArguments() ?? new Type[0], options); - break; + default: + value = GetSimpleValue(jsonElement); + break; + } - default: - value = GetSimpleValue(jsonElement); - break; - } + if (typeToConvert is null) + { + return value; + } - if (typeToConvert is null) - { - return value; - } + if (value is null) + { + return Activator.CreateInstance(typeToConvert); + } - if (value is null) - { - return Activator.CreateInstance(typeToConvert); - } + return Activator.CreateInstance(typeToConvert, value); + } - return Activator.CreateInstance(typeToConvert, value); - } + private static object? GetSimpleValue(JsonElement reader) + { - private static object? GetSimpleValue(JsonElement reader) + switch (reader.ValueKind) { + case JsonValueKind.String: + if (reader.TryGetDateTime(out var date)) + { + return date; + } - switch (reader.ValueKind) - { - case JsonValueKind.String: - if (reader.TryGetDateTime(out var date)) - { - return date; - } - - return reader.GetString(); + return reader.GetString(); - case JsonValueKind.Number: - if (reader.TryGetInt32(out var i)) - { - return i; - } + case JsonValueKind.Number: + if (reader.TryGetInt32(out var i)) + { + return i; + } - if (reader.TryGetInt64(out var l)) - { - return l; - } + if (reader.TryGetInt64(out var l)) + { + return l; + } - if (reader.TryGetUInt32(out var ui)) - { - return ui; - } + if (reader.TryGetUInt32(out var ui)) + { + return ui; + } - if (reader.TryGetUInt64(out var ul)) - { - return ul; - } + if (reader.TryGetUInt64(out var ul)) + { + return ul; + } - return reader.GetDecimal(); + return reader.GetDecimal(); - case JsonValueKind.True: - return true; + case JsonValueKind.True: + return true; - case JsonValueKind.False: - return false; + case JsonValueKind.False: + return false; - case JsonValueKind.Null: - return null; + case JsonValueKind.Null: + return null; - default: - throw new JsonException($"The ValueKind '{reader.ValueKind}' is not supported."); - } + default: + throw new JsonException($"The ValueKind '{reader.ValueKind}' is not supported."); } + } - private object? FindBestArrayMatch(JsonElement jsonElement, Type? typeToConvert, JsonSerializerOptions options) - { - var enumerableTypes = typeToConvert?.GetGenericArguments().Where(t => typeof(IEnumerable).IsAssignableFrom(t)).ToArray() ?? new Type[0]; - var types = enumerableTypes.Select(t => t.GetElementTypeX()).ToArray(); + private object? FindBestArrayMatch(JsonElement jsonElement, Type? typeToConvert, JsonSerializerOptions options) + { + var enumerableTypes = typeToConvert?.GetGenericArguments().Where(t => typeof(IEnumerable).IsAssignableFrom(t)).ToArray() ?? Type.EmptyTypes; + var types = enumerableTypes.Select(t => t.GetElementTypeX()).ToArray(); - var list = new List(); + var list = new List(); - Type? elementType = null; - foreach (var arrayElement in jsonElement.EnumerateArray()) + Type? elementType = null; + foreach (var arrayElement in jsonElement.EnumerateArray()) + { + object? value; + if (arrayElement.ValueKind == JsonValueKind.Object) { - object? value; - if (arrayElement.ValueKind == JsonValueKind.Object) - { - value = FindBestObjectMatch(arrayElement, types, options); - } - else - { - value = GetSimpleValue(arrayElement); - } - - if (elementType is null) - { - elementType = value?.GetType(); - } - - list.Add(value); + value = FindBestObjectMatch(arrayElement, types, options); } - - if (elementType is null) + else { - return null; + value = GetSimpleValue(arrayElement); } - var typedListDetails = list.CastToTypedList(elementType); - - foreach (var knownIEnumerableType in enumerableTypes) + if (elementType is null) { - if (knownIEnumerableType.GetElementTypeX() == elementType) - { - TinyMapper.Bind(typedListDetails.ListType, knownIEnumerableType); - return TinyMapper.Map(typedListDetails.ListType, knownIEnumerableType, typedListDetails.List); - } + elementType = value?.GetType(); } - return null; + list.Add(value); } - private object? FindBestObjectMatch(JsonElement objectElement, Type[] types, JsonSerializerOptions options) + if (elementType is null) { - var properties = new List(); - foreach (var element in objectElement.EnumerateObject()) - { - var propertyDetails = new PropertyDetails - { - CanRead = true, - CanWrite = true, - IsPublic = true, - Name = element.Name - }; - - object? val; - switch (element.Value.ValueKind) - { - case JsonValueKind.Object: - val = FindBestObjectMatch(element.Value, types, options); - break; - - default: - val = GetSimpleValue(element.Value); - break; - } - - propertyDetails.PropertyType = val?.GetType(); - propertyDetails.IsValueType = val?.GetType().IsValueType == true; + return null; + } - properties.Add(propertyDetails); - } + var typedListDetails = list.CastToTypedList(elementType); - var mostSuitableType = MatchFinder.FindBestType(_ignoreCase, properties, types); - if (mostSuitableType is not null) + foreach (var knownIEnumerableType in enumerableTypes) + { + if (knownIEnumerableType.GetElementTypeX() == elementType) { - return ToObject(objectElement, mostSuitableType, options); + TinyMapper.Bind(typedListDetails.ListType, knownIEnumerableType); + return TinyMapper.Map(typedListDetails.ListType, knownIEnumerableType, typedListDetails.List); } - - throw new JsonException("No suitable type found."); } - public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + return null; + } + + private object? FindBestObjectMatch(JsonElement objectElement, Type[] types, JsonSerializerOptions options) + { + var properties = new List(); + foreach (var element in objectElement.EnumerateObject()) { - if (value is null) + var propertyDetails = new PropertyDetails { - writer.WriteNullValue(); - return; - } - - var currentValue = value.GetNullablePropertyValue("CurrentValue"); - if (currentValue is null) + CanRead = true, + CanWrite = true, + IsPublic = true, + Name = element.Name + }; + + object? val; + switch (element.Value.ValueKind) { - writer.WriteNullValue(); - return; + case JsonValueKind.Object: + val = FindBestObjectMatch(element.Value, types, options); + break; + + default: + val = GetSimpleValue(element.Value); + break; } - var currentType = value.GetPropertyValue("CurrentValueType"); - JsonSerializer.Serialize(writer, currentValue, currentType, options); + propertyDetails.PropertyType = val?.GetType(); + propertyDetails.IsValueType = val?.GetType().IsValueType == true; + + properties.Add(propertyDetails); } - public override bool CanConvert(Type objectType) + var mostSuitableType = MatchFinder.FindBestType(_ignoreCase, properties, types); + if (mostSuitableType is not null) { - return objectType.FullName.StartsWith("AnyOfTypes.AnyOf`"); + return ToObject(objectElement, mostSuitableType, options); } - /// - /// - https://stackoverflow.com/questions/58138793/system-text-json-jsonelement-toobject-workaround - /// - https://stackoverflow.com/a/58193164/255966 - /// - private static object? ToObject(JsonElement element, Type returnType, JsonSerializerOptions? options = null) + // Just return object + return ToObject(objectElement, typeof(object), options); + } + + public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options) + { + if (value is null) { - var json = element.GetRawText(); - return JsonSerializer.Deserialize(json, returnType, options); + writer.WriteNullValue(); + return; } - /// - /// https://github.com/dahomey-technologies/Dahomey.Json/blob/master/src/Dahomey.Json/Util/Utf8JsonReaderExtensions.cs - /// - private static JsonConverter GetConverter(JsonSerializerOptions options) + var currentValue = value.GetNullablePropertyValue("CurrentValue"); + if (currentValue is null) { - return (JsonConverter)options.GetConverter(typeof(T)); + writer.WriteNullValue(); + return; } + + var currentType = value.GetPropertyValue("CurrentValueType"); + JsonSerializer.Serialize(writer, currentValue, currentType, options); + } + + public override bool CanConvert(Type objectType) + { + return objectType.FullName?.StartsWith("AnyOfTypes.AnyOf`") == true; + } + + /// + /// - https://stackoverflow.com/questions/58138793/system-text-json-jsonelement-toobject-workaround + /// - https://stackoverflow.com/a/58193164/255966 + /// + private static object? ToObject(JsonElement element, Type returnType, JsonSerializerOptions? options = null) + { + var json = element.GetRawText(); + return JsonSerializer.Deserialize(json, returnType, options); + } + + /// + /// https://github.com/dahomey-technologies/Dahomey.Json/blob/master/src/Dahomey.Json/Util/Utf8JsonReaderExtensions.cs + /// + private static JsonConverter GetConverter(JsonSerializerOptions options) + { + return (JsonConverter)options.GetConverter(typeof(T)); } } \ No newline at end of file diff --git a/src/AnyOf.System.Text.Json/Extensions/ReflectionHelpers.cs b/src/AnyOf.System.Text.Json/Extensions/ReflectionHelpers.cs index e927153..8321bfe 100644 --- a/src/AnyOf.System.Text.Json/Extensions/ReflectionHelpers.cs +++ b/src/AnyOf.System.Text.Json/Extensions/ReflectionHelpers.cs @@ -3,14 +3,14 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using AnyOfTypes.System.Text.Json.Matcher.Models; +using AnyOfTypes.System.Text.Json.Models; namespace AnyOfTypes.System.Text.Json.Extensions; internal static class ReflectionHelpers { [ThreadStatic] - static readonly Dictionary, bool> ImplicitCastCache = new Dictionary, bool>(); + private static readonly Dictionary, bool> ImplicitCastCache = new(); public static T GetPropertyValue(this object instance, string name) { @@ -94,7 +94,7 @@ public static bool IsImplicitlyCastableTo(this Type? from, Type? to) try { var val = Activator.CreateInstance(from); - Convert.ChangeType(val, to); + _ = Convert.ChangeType(val, to); changeType = true; } catch diff --git a/src/AnyOf.System.Text.Json/Extensions/Utf8JsonReaderExtensions.cs b/src/AnyOf.System.Text.Json/Extensions/Utf8JsonReaderExtensions.cs index 5f4f79b..bc914dd 100644 --- a/src/AnyOf.System.Text.Json/Extensions/Utf8JsonReaderExtensions.cs +++ b/src/AnyOf.System.Text.Json/Extensions/Utf8JsonReaderExtensions.cs @@ -1,15 +1,13 @@ using System; using System.Buffers; -using System.Linq; using System.Text.Json; -namespace AnyOfTypes.System.Text.Json.Extensions +namespace AnyOfTypes.System.Text.Json.Extensions; + +internal static class Utf8JsonReaderExtensions { - internal static class Utf8JsonReaderExtensions + public static ReadOnlySpan GetRawString(this Utf8JsonReader reader) { - public static ReadOnlySpan GetRawString(this Utf8JsonReader reader) - { - return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; - } + return reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; } } \ No newline at end of file diff --git a/src/AnyOf.System.Text.Json/Matcher/MatchFinder.cs b/src/AnyOf.System.Text.Json/Matcher/MatchFinder.cs index fcdc19c..e753411 100644 --- a/src/AnyOf.System.Text.Json/Matcher/MatchFinder.cs +++ b/src/AnyOf.System.Text.Json/Matcher/MatchFinder.cs @@ -3,79 +3,78 @@ using System.Linq; using System.Reflection; using AnyOfTypes.System.Text.Json.Extensions; -using AnyOfTypes.System.Text.Json.Matcher.Models; +using AnyOfTypes.System.Text.Json.Models; -namespace AnyOfTypes.System.Text.Json.Matcher +namespace AnyOfTypes.System.Text.Json.Matcher; + +internal static class MatchFinder { - internal static class MatchFinder - { #if NETSTANDARD1_3 - private static readonly StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase; + private static readonly StringComparison IgnoreCase = StringComparison.OrdinalIgnoreCase; #else - private static readonly StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase; + private static readonly StringComparison IgnoreCase = StringComparison.InvariantCultureIgnoreCase; #endif - public static Type? FindBestType(bool ignoreCase, IEnumerable sourceProperties, Type[] targetTypes, bool returnNullIfNoMatchFound = true) - { - Type? mostSuitableType = null; - int countOfMaxMatchingProperties = -1; + public static Type? FindBestType(bool ignoreCase, IReadOnlyList sourceProperties, Type[] targetTypes, bool returnNullIfNoMatchFound = true) + { + Type? mostSuitableType = null; + int countOfMaxMatchingProperties = -1; - foreach (var targetType in targetTypes) + foreach (var targetType in targetTypes) + { + var propMap = GetMatchingProperties(ignoreCase, sourceProperties, Map(targetType.GetProperties())); + if (propMap.Count > countOfMaxMatchingProperties) { - var propMap = GetMatchingProperties(ignoreCase, sourceProperties, Map(targetType.GetProperties())); - if (propMap.Count > countOfMaxMatchingProperties) - { - mostSuitableType = targetType; - countOfMaxMatchingProperties = propMap.Count; - } + mostSuitableType = targetType; + countOfMaxMatchingProperties = propMap.Count; } - - return countOfMaxMatchingProperties == 0 && returnNullIfNoMatchFound ? null : mostSuitableType; } - private static IList GetMatchingProperties( - bool ignoreCase, - IEnumerable sourceProperties, - IEnumerable targetProperties) - { - return - ( - from s in sourceProperties - from t in targetProperties - where - ignoreCase ? string.Equals(s.Name, t.Name, IgnoreCase) : s.Name == t.Name && - s.CanRead && - t.CanWrite && - s.IsPublic && - t.IsPublic && - (s.PropertyType == t.PropertyType || s.PropertyType.IsImplicitlyCastableTo(t.PropertyType) || t.PropertyType.IsImplicitlyCastableTo(s.PropertyType)) && - ( - (s.IsValueType && t.IsValueType) || (s.PropertyType == typeof(string) && t.PropertyType == typeof(string)) - ) - select new PropertyMap - { - SourceProperty = s, - TargetProperty = t - } - ).ToList(); - } + return countOfMaxMatchingProperties == 0 && returnNullIfNoMatchFound ? null : mostSuitableType; + } - public static IList GetMatchingProperties(bool ignoreCase, Type sourceType, Type targetType) - { - return GetMatchingProperties(ignoreCase, Map(sourceType.GetProperties()), Map(targetType.GetProperties())); - } + private static IList GetMatchingProperties( + bool ignoreCase, + IEnumerable sourceProperties, + IEnumerable targetProperties) + { + return + ( + from s in sourceProperties + from t in targetProperties + where + ignoreCase ? string.Equals(s.Name, t.Name, IgnoreCase) : s.Name == t.Name && + s.CanRead && + t.CanWrite && + s.IsPublic && + t.IsPublic && + (s.PropertyType == t.PropertyType || s.PropertyType.IsImplicitlyCastableTo(t.PropertyType) || t.PropertyType.IsImplicitlyCastableTo(s.PropertyType)) && + ( + (s.IsValueType && t.IsValueType) || (s.PropertyType == typeof(string) && t.PropertyType == typeof(string)) + ) + select new PropertyMap + { + SourceProperty = s, + TargetProperty = t + } + ).ToList(); + } + + public static IList GetMatchingProperties(bool ignoreCase, Type sourceType, Type targetType) + { + return GetMatchingProperties(ignoreCase, Map(sourceType.GetProperties()), Map(targetType.GetProperties())); + } - private static IEnumerable Map(PropertyInfo[] properties) + private static IEnumerable Map(PropertyInfo[] properties) + { + return properties.Select(p => new PropertyDetails { - return properties.Select(p => new PropertyDetails - { - CanRead = p.CanRead, - CanWrite = p.CanWrite, - IsPublic = p.PropertyType.GetTypeInfo().IsPublic, - IsValueType = p.PropertyType.GetTypeInfo().IsValueType, - Name = p.Name, - PropertyType = p.PropertyType - }); - } + CanRead = p.CanRead, + CanWrite = p.CanWrite, + IsPublic = p.PropertyType.GetTypeInfo().IsPublic, + IsValueType = p.PropertyType.GetTypeInfo().IsValueType, + Name = p.Name, + PropertyType = p.PropertyType + }); } } \ No newline at end of file diff --git a/src/AnyOf.System.Text.Json/Models/ListDetails.cs b/src/AnyOf.System.Text.Json/Models/ListDetails.cs index 98572e7..6ba189a 100644 --- a/src/AnyOf.System.Text.Json/Models/ListDetails.cs +++ b/src/AnyOf.System.Text.Json/Models/ListDetails.cs @@ -1,12 +1,11 @@ using System; using System.Collections; -namespace AnyOfTypes.System.Text.Json.Matcher.Models +namespace AnyOfTypes.System.Text.Json.Models; + +internal struct ListDetails { - internal struct ListDetails - { - public IList List { get; set; } + public IList List { get; set; } - public Type ListType { get; set; } - } -} + public Type ListType { get; set; } +} \ No newline at end of file diff --git a/src/AnyOf.System.Text.Json/Models/PropertyDetails.cs b/src/AnyOf.System.Text.Json/Models/PropertyDetails.cs index 035c1c8..92a923f 100644 --- a/src/AnyOf.System.Text.Json/Models/PropertyDetails.cs +++ b/src/AnyOf.System.Text.Json/Models/PropertyDetails.cs @@ -1,19 +1,18 @@ using System; -namespace AnyOfTypes.System.Text.Json.Matcher.Models +namespace AnyOfTypes.System.Text.Json.Models; + +internal struct PropertyDetails { - internal struct PropertyDetails - { - public string Name { get; set; } + public string Name { get; set; } - public bool CanRead { get; set; } + public bool CanRead { get; set; } - public bool CanWrite { get; set; } + public bool CanWrite { get; set; } - public bool IsPublic { get; set; } + public bool IsPublic { get; set; } - public bool IsValueType { get; set; } + public bool IsValueType { get; set; } - public Type? PropertyType { get; set; } - } + public Type? PropertyType { get; set; } } \ No newline at end of file diff --git a/src/AnyOf.System.Text.Json/Models/PropertyMap.cs b/src/AnyOf.System.Text.Json/Models/PropertyMap.cs index a8d4aa0..8e2f018 100644 --- a/src/AnyOf.System.Text.Json/Models/PropertyMap.cs +++ b/src/AnyOf.System.Text.Json/Models/PropertyMap.cs @@ -1,9 +1,8 @@ -namespace AnyOfTypes.System.Text.Json.Matcher.Models +namespace AnyOfTypes.System.Text.Json.Models; + +internal struct PropertyMap { - internal struct PropertyMap - { - public PropertyDetails SourceProperty { get; set; } + public PropertyDetails SourceProperty { get; set; } - public PropertyDetails TargetProperty { get; set; } - } + public PropertyDetails TargetProperty { get; set; } } \ No newline at end of file diff --git a/tests/AnyOf.Newtonsoft.Json.Tests/AnyOf.Newtonsoft.Json.Tests.csproj b/tests/AnyOf.Newtonsoft.Json.Tests/AnyOf.Newtonsoft.Json.Tests.csproj index eb3be07..280ab19 100644 --- a/tests/AnyOf.Newtonsoft.Json.Tests/AnyOf.Newtonsoft.Json.Tests.csproj +++ b/tests/AnyOf.Newtonsoft.Json.Tests/AnyOf.Newtonsoft.Json.Tests.csproj @@ -5,6 +5,10 @@ false + + + + diff --git a/tests/AnyOf.Newtonsoft.Json.Tests/AnyOfJsonConverterTests.cs b/tests/AnyOf.Newtonsoft.Json.Tests/AnyOfJsonConverterTests.cs index 868dc85..0d2c978 100644 --- a/tests/AnyOf.Newtonsoft.Json.Tests/AnyOfJsonConverterTests.cs +++ b/tests/AnyOf.Newtonsoft.Json.Tests/AnyOfJsonConverterTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using AnyOf.System.Text.Json.Tests.TestModels; +using AnyOfTypes; using AnyOfTypes.Newtonsoft.Json; using FluentAssertions; using Newtonsoft.Json; @@ -103,6 +104,31 @@ public void Serialize_AnyOf_With_MixedTypes() json.Should().Be("{\"IntOrStringOrAOrB\":1}"); } + [Fact] + public void Deserialize_Issue17() + { + // Arrange + var expected = new ATest + { + StringValue = "1", + SubClass = new SampleSubClassTest + { + SampleProperty = "Abc" + } + }; + + var options = new JsonSerializerSettings(); + options.Converters.Add(new AnyOfJsonConverter()); + + var json = "{\"StringValue\": \"1\",\"SubClass\": {\"SampleProperty\": \"Abc\"}}"; + + // Act + var result = JsonConvert.DeserializeObject>(json, options); + + // Assert + result.First.Should().BeEquivalentTo(expected); + } + [Fact] public void Deserialize_AnyOf_With_SimpleTypes() { diff --git a/src/AnyOf.Newtonsoft.Json/Class1.cs b/tests/AnyOf.Newtonsoft.Json.Tests/Class1.cs similarity index 100% rename from src/AnyOf.Newtonsoft.Json/Class1.cs rename to tests/AnyOf.Newtonsoft.Json.Tests/Class1.cs diff --git a/tests/AnyOf.System.Text.Json.Tests/AnyOf.System.Text.Json.Tests.csproj b/tests/AnyOf.System.Text.Json.Tests/AnyOf.System.Text.Json.Tests.csproj index 71a1aad..65bda92 100644 --- a/tests/AnyOf.System.Text.Json.Tests/AnyOf.System.Text.Json.Tests.csproj +++ b/tests/AnyOf.System.Text.Json.Tests/AnyOf.System.Text.Json.Tests.csproj @@ -3,6 +3,7 @@ net7.0 false + 11 diff --git a/tests/AnyOf.System.Text.Json.Tests/AnyOfJsonConverterTests.cs b/tests/AnyOf.System.Text.Json.Tests/AnyOfJsonConverterTests.cs index aa3aa64..88e3806 100644 --- a/tests/AnyOf.System.Text.Json.Tests/AnyOfJsonConverterTests.cs +++ b/tests/AnyOf.System.Text.Json.Tests/AnyOfJsonConverterTests.cs @@ -1,243 +1,270 @@ using System.Collections.Generic; using System.Text.Json; using AnyOf.System.Text.Json.Tests.TestModels; +using AnyOfTypes; using AnyOfTypes.System.Text.Json; using FluentAssertions; using Xunit; -namespace AnyOf.System.Text.Json.Tests +namespace AnyOf.System.Text.Json.Tests; + +public class AnyOfJsonConverterTests { - public class AnyOfJsonConverterTests + [Fact] + public void Serialize_AnyOf_With_SimpleTypes() { - [Fact] - public void Serialize_AnyOf_With_SimpleTypes() + // Arrange + var test = new TestSimpleTypes { - // Arrange - var test = new TestSimpleTypes - { - IntOrString = 1 - }; + IntOrString = 1 + }; - // Act - var options = new JsonSerializerOptions - { - WriteIndented = false - }; - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions + { + WriteIndented = false + }; + options.Converters.Add(new AnyOfJsonConverter()); - var json = JsonSerializer.Serialize(test, options); + var json = JsonSerializer.Serialize(test, options); - // Assert - json.Should().Be("{\"IntOrString\":1}"); - } + // Assert + json.Should().Be("{\"IntOrString\":1}"); + } - [Fact] - public void Serialize_AnyOf_With_ComplexTypes() + [Fact] + public void Serialize_AnyOf_With_ComplexTypes() + { + // Arrange + var test = new TestComplexTypes { - // Arrange - var test = new TestComplexTypes + AorB = new A { - AorB = new A - { - Id = 1 - } - }; + Id = 1 + } + }; - // Act - var options = new JsonSerializerOptions - { - WriteIndented = false - }; - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions + { + WriteIndented = false + }; + options.Converters.Add(new AnyOfJsonConverter()); - var json = JsonSerializer.Serialize(test, options); + var json = JsonSerializer.Serialize(test, options); - // Assert - json.Should().Be("{\"AorB\":{\"Id\":1}}"); - } + // Assert + json.Should().Be("{\"AorB\":{\"Id\":1}}"); + } - [Fact] - public void Serialize_AnyOf_With_MixedTypes() + [Fact] + public void Serialize_AnyOf_With_MixedTypes() + { + // Arrange + var test = new TestMixedTypes { - // Arrange - var test = new TestMixedTypes - { - IntOrStringOrAOrB = 1 - }; + IntOrStringOrAOrB = 1 + }; - // Act - var options = new JsonSerializerOptions - { - WriteIndented = false - }; - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions + { + WriteIndented = false + }; + options.Converters.Add(new AnyOfJsonConverter()); - var json = JsonSerializer.Serialize(test, options); + var json = JsonSerializer.Serialize(test, options); - // Assert - json.Should().Be("{\"IntOrStringOrAOrB\":1}"); - } + // Assert + json.Should().Be("{\"IntOrStringOrAOrB\":1}"); + } - [Fact] - public void Serialize_AnyOf_With_IntArray() + [Fact] + public void Serialize_AnyOf_With_IntArray() + { + // Arrange + var test = new TestComplexArray { - // Arrange - var test = new TestComplexArray - { - X = new int[] { 42 } - }; + X = new int[] { 42 } + }; - // Act - var options = new JsonSerializerOptions - { - WriteIndented = false - }; - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions + { + WriteIndented = false + }; + options.Converters.Add(new AnyOfJsonConverter()); - var json = JsonSerializer.Serialize(test, options); + var json = JsonSerializer.Serialize(test, options); - // Assert - json.Should().Be("{\"X\":[42]}"); - } + // Assert + json.Should().Be("{\"X\":[42]}"); + } - [Fact] - public void Serialize_AnyOf_With_ObjectList() + [Fact] + public void Serialize_AnyOf_With_ObjectList() + { + // Arrange + var test = new TestComplexArray { - // Arrange - var test = new TestComplexArray - { - X = new List { new A { Id = 1 }, new A { Id = 2 } } - }; + X = new List { new A { Id = 1 }, new A { Id = 2 } } + }; - // Act - var options = new JsonSerializerOptions - { - WriteIndented = false - }; - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions + { + WriteIndented = false + }; + options.Converters.Add(new AnyOfJsonConverter()); - var json = JsonSerializer.Serialize(test, options); + var json = JsonSerializer.Serialize(test, options); - // Assert - json.Should().Be("{\"X\":[{\"Id\":1},{\"Id\":2}]}"); - } + // Assert + json.Should().Be("{\"X\":[{\"Id\":1},{\"Id\":2}]}"); + } - [Fact] - public void Deserialize_AnyOf_With_SimpleTypes() + [Fact] + public void Deserialize_Issue17() + { + // Arrange + var expected = new ATest { - // Arrange - var expected = new TestSimpleTypes + StringValue = "1", + SubClass = new SampleSubClassTest { - IntOrString = 1 - }; + SampleProperty = "Abc" + } + }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); + options.PropertyNameCaseInsensitive = true; - var result = JsonSerializer.Deserialize("{\"IntOrString\":1}", options); + var json = "{\"StringValue\": \"1\",\"SubClass\": {\"SampleProperty\": \"Abc\"}}"; - // Assert - result.Should().BeEquivalentTo(expected); - } + // Act + var result = JsonSerializer.Deserialize>(json, options); + + // Assert + result.First.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_ComplexTypes() + [Fact] + public void Deserialize_AnyOf_With_SimpleTypes() + { + // Arrange + var expected = new TestSimpleTypes { - // Arrange - var expected = new A - { - Id = 1 - }; + IntOrString = 1 + }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"AorB\":{\"Id\":1}}", options); + var result = JsonSerializer.Deserialize("{\"IntOrString\":1}", options); - // Assert - result.AorB.First.Should().BeEquivalentTo(expected); - } + // Assert + result.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_ComplexTypes_DifferentCasing() + [Fact] + public void Deserialize_AnyOf_With_ComplexTypes() + { + // Arrange + var expected = new A { - // Arrange - var expected = new A2 - { - id = 1 - }; + Id = 1 + }; - // Act - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"AorB\":{\"Id\":1}}", options); + var result = JsonSerializer.Deserialize("{\"AorB\":{\"Id\":1}}", options); - // Assert - result.AorB.First.Should().BeEquivalentTo(expected); - } + // Assert + result.AorB.First.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_MixedTypes() + [Fact] + public void Deserialize_AnyOf_With_ComplexTypes_DifferentCasing() + { + // Arrange + var expected = new A2 { - // Arrange - var expected = new TestMixedTypes - { - IntOrStringOrAOrB = 1 - }; + id = 1 + }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }; + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"IntOrStringOrAOrB\":1}", options); + var result = JsonSerializer.Deserialize("{\"AorB\":{\"Id\":1}}", options); - // Assert - result.Should().BeEquivalentTo(expected); - } + // Assert + result.AorB.First.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_IntArray() + [Fact] + public void Deserialize_AnyOf_With_MixedTypes() + { + // Arrange + var expected = new TestMixedTypes { - // Arrange - var expected = new int[] { 42 }; + IntOrStringOrAOrB = 1 + }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"X\":[42]}", options); + var result = JsonSerializer.Deserialize("{\"IntOrStringOrAOrB\":1}", options); - // Assert - result.X.First.Should().BeEquivalentTo(expected); - } + // Assert + result.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_StringList() - { - // Arrange - var expected = new[] { "a", "b" }; + [Fact] + public void Deserialize_AnyOf_With_IntArray() + { + // Arrange + var expected = new int[] { 42 }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"X\":[\"a\", \"b\"]}", options); + var result = JsonSerializer.Deserialize("{\"X\":[42]}", options); - // Assert - result.X.Second.Should().BeEquivalentTo(expected); - } + // Assert + result.X.First.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_ObjectList_A() - { - // Arrange - var expected = new List + [Fact] + public void Deserialize_AnyOf_With_StringList() + { + // Arrange + var expected = new[] { "a", "b" }; + + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); + + var result = JsonSerializer.Deserialize("{\"X\":[\"a\", \"b\"]}", options); + + // Assert + result.X.Second.Should().BeEquivalentTo(expected); + } + + [Fact] + public void Deserialize_AnyOf_With_ObjectList_A() + { + // Arrange + var expected = new List { new A { @@ -249,21 +276,21 @@ public void Deserialize_AnyOf_With_ObjectList_A() } }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"X\":[{\"Id\":1},{\"Id\":2}]}", options); + var result = JsonSerializer.Deserialize("{\"X\":[{\"Id\":1},{\"Id\":2}]}", options); - // Assert - result.X.Third.Should().BeEquivalentTo(expected); - } + // Assert + result.X.Third.Should().BeEquivalentTo(expected); + } - [Fact] - public void Deserialize_AnyOf_With_ObjectList_B() - { - // Arrange - var expected = new List + [Fact] + public void Deserialize_AnyOf_With_ObjectList_B() + { + // Arrange + var expected = new List { new B { @@ -275,14 +302,13 @@ public void Deserialize_AnyOf_With_ObjectList_B() } }; - // Act - var options = new JsonSerializerOptions(); - options.Converters.Add(new AnyOfJsonConverter()); + // Act + var options = new JsonSerializerOptions(); + options.Converters.Add(new AnyOfJsonConverter()); - var result = JsonSerializer.Deserialize("{\"X\":[{\"Guid\":\"a\"},{\"Guid\":\"b\"}]}", options); + var result = JsonSerializer.Deserialize("{\"X\":[{\"Guid\":\"a\"},{\"Guid\":\"b\"}]}", options); - // Assert - result.X.Fourth.Should().BeEquivalentTo(expected); - } + // Assert + result.X.Fourth.Should().BeEquivalentTo(expected); } -} \ No newline at end of file +} diff --git a/tests/AnyOf.System.Text.Json.Tests/TestModels/TestModels.cs b/tests/AnyOf.System.Text.Json.Tests/TestModels/TestModels.cs index 55585d8..808d614 100644 --- a/tests/AnyOf.System.Text.Json.Tests/TestModels/TestModels.cs +++ b/tests/AnyOf.System.Text.Json.Tests/TestModels/TestModels.cs @@ -47,4 +47,21 @@ public class B { public string Guid { get; set; } } + + public class SampleSubClassTest + { + public string SampleProperty { get; set; } + } + + class ATest + { + public SampleSubClassTest SubClass { get; set; } + + public string StringValue { get; set; } + } + + class BTest + { + public string Value { get; set; } + } } \ No newline at end of file diff --git a/tests/AnyOfTests/AnyOfConverterTests.cs b/tests/AnyOfTests/AnyOfConverterTests.cs index 628ae6a..e01371e 100644 --- a/tests/AnyOfTests/AnyOfConverterTests.cs +++ b/tests/AnyOfTests/AnyOfConverterTests.cs @@ -53,7 +53,22 @@ public void ConvertFrom_ShouldReturnExpectedValue(object value, object expectedV var converter = new AnyOfConverter(); // Act - var result = (AnyOf)converter.ConvertFrom(_typeDescriptorContext, null!, value); + var result = (AnyOf)converter.ConvertFrom(_typeDescriptorContext, null!, value)!; + + // Assert + result.CurrentValue.Should().Be(expectedValue); + } + + [Theory] + [InlineData(42, 42)] + [InlineData("foo", "foo")] + public void ConvertFrom_UsingTypeDescriptor_ShouldReturnExpectedValue(object value, object expectedValue) + { + // Arrange + var converter = TypeDescriptor.GetConverter(typeof(AnyOfConverter)); + + // Act + var result = (AnyOf)converter.ConvertFrom(_typeDescriptorContext, null!, value)!; // Assert result.CurrentValue.Should().Be(expectedValue);