From 44d8fdc0be9de24c69cfca5f179c30c1f42846c8 Mon Sep 17 00:00:00 2001 From: Neil Dobson Date: Wed, 7 Mar 2018 21:41:24 +1100 Subject: [PATCH 1/5] Support IReadOnlyDictionary in FromObjectDictionary --- src/ServiceStack.Text/PlatformExtensions.cs | 6 +++--- .../AutoMappingObjectDictionaryTests.cs | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/ServiceStack.Text/PlatformExtensions.cs b/src/ServiceStack.Text/PlatformExtensions.cs index b458d696f..da76879c4 100644 --- a/src/ServiceStack.Text/PlatformExtensions.cs +++ b/src/ServiceStack.Text/PlatformExtensions.cs @@ -674,9 +674,9 @@ public static Dictionary ToObjectDictionary(this object obj) return dict; } - public static object FromObjectDictionary(this Dictionary values, Type type) + public static object FromObjectDictionary(this IReadOnlyDictionary values, Type type) { - var alreadyDict = type == typeof(Dictionary); + var alreadyDict = type == typeof(IReadOnlyDictionary); if (alreadyDict) return true; @@ -696,7 +696,7 @@ public static object FromObjectDictionary(this Dictionary values return to; } - public static T FromObjectDictionary(this Dictionary values) + public static T FromObjectDictionary(this IReadOnlyDictionary values) { return (T)values.FromObjectDictionary(typeof(T)); } diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs index 385434a8c..89a2cbe49 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.ObjectModel; using Northwind.Common.DataModel; using NUnit.Framework; @@ -79,6 +80,25 @@ public void Can_Convert_from_ObjectDictionary_with_Different_Types_with_camelCas Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); } + [Test] + public void Can_Convert_from_ObjectDictionary_with_Read_Only_Dictionary() + { + var map = new Dictionary + { + { "FirstName", 1 }, + { "LastName", true }, + { "Car", new SubCar { Age = 10, Name = "SubCar", Custom = "Custom"} }, + }; + + var readOnlyMap = new ReadOnlyDictionary(map); + + var fromDict = (User)readOnlyMap.FromObjectDictionary(typeof(User)); + Assert.That(fromDict.FirstName, Is.EqualTo("1")); + Assert.That(fromDict.LastName, Is.EqualTo(bool.TrueString)); + Assert.That(fromDict.Car.Age, Is.EqualTo(10)); + Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); + } + public class QueryCustomers : QueryDb { public string CustomerId { get; set; } From ced0e5a9761b311d97a44c054828c9ca384a5d92 Mon Sep 17 00:00:00 2001 From: Neil Dobson Date: Thu, 24 May 2018 07:37:18 +1000 Subject: [PATCH 2/5] Support nullable types --- src/ServiceStack.Text/AutoMappingUtils.cs | 10 ++--- .../AutoMappingObjectDictionaryTests.cs | 38 +++++++++++++++++++ .../AutoMappingTests.cs | 10 +++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/ServiceStack.Text/AutoMappingUtils.cs b/src/ServiceStack.Text/AutoMappingUtils.cs index 471155d2f..423fd4e1a 100644 --- a/src/ServiceStack.Text/AutoMappingUtils.cs +++ b/src/ServiceStack.Text/AutoMappingUtils.cs @@ -810,23 +810,19 @@ public static GetMemberDelegate CreateTypeConverter(Type fromType, Type toType) if (underlyingToType.IsIntegerType()) return fromValue => Convert.ChangeType(fromValue, underlyingToType, null); } - else if (toType.IsNullableType()) - { - return null; - } else if (typeof(IEnumerable).IsAssignableFrom(fromType)) { return fromValue => { var listResult = TranslateListWithElements.TryTranslateCollections( - fromType, toType, fromValue); + fromType, underlyingToType, fromValue); return listResult ?? fromValue; }; } - else if (toType.IsValueType) + else if (underlyingToType.IsValueType) { - return fromValue => Convert.ChangeType(fromValue, toType, provider: null); + return fromValue => Convert.ChangeType(fromValue, underlyingToType, provider: null); } else { diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs index 2cbf9051d..fcda42628 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -99,6 +99,44 @@ public void Can_Convert_from_ObjectDictionary_with_Read_Only_Dictionary() Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); } + [Test] + public void Can_Convert_from_ObjectDictionary_with_SubClass_Type() + { + var map = new Dictionary + { + { "FirstName", "Foo" }, + { "LastName", "Bar" }, + { "Car", "Jag" }, + { "Age", 21 }, + }; + + var userDto = map.FromObjectDictionary(); + + Assert.That(userDto.FirstName, Is.EqualTo("Foo")); + Assert.That(userDto.LastName, Is.EqualTo("Bar")); + Assert.That(userDto.Car, Is.EqualTo("Jag")); + Assert.That(userDto.Age, Is.EqualTo(21)); + } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_SubClass_Type_With_Nullable_Properties() + { + var map = new Dictionary + { + { "FirstName", "Foo" }, + { "LastName", "Bar" }, + { "Car", "Jag" }, + { "Age", 21 }, + }; + + var userDto = map.FromObjectDictionary(); + + Assert.That(userDto.FirstName, Is.EqualTo("Foo")); + Assert.That(userDto.LastName, Is.EqualTo("Bar")); + Assert.That(userDto.Car, Is.EqualTo("Jag")); + Assert.That(userDto.Age, Is.EqualTo(21)); + } + public class QueryCustomers : QueryDb { public string CustomerId { get; set; } diff --git a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs index 45901159c..27b55a9de 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs @@ -49,6 +49,16 @@ public class UserDto public string Car { get; set; } } + public class AgedUserWithNullable : UserDto + { + public long? Age { get; set; } + } + + public class AgedUser : UserDto + { + public long Age { get; set; } + } + public enum Color { Red, From c4ed7b766f30962718aa23dae941273fa24c9662 Mon Sep 17 00:00:00 2001 From: Neil Dobson Date: Sun, 27 May 2018 13:13:49 +1000 Subject: [PATCH 3/5] Refactor tests --- .../AutoMappingObjectDictionaryTests.cs | 89 +++++++++++-------- .../AutoMappingTests.cs | 10 --- 2 files changed, 51 insertions(+), 48 deletions(-) diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs index fcda42628..d13c8fe37 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -2,6 +2,7 @@ using System.Collections.ObjectModel; using Northwind.Common.DataModel; using NUnit.Framework; +using ServiceStack.Common.Tests.Models; namespace ServiceStack.Text.Tests { @@ -99,44 +100,6 @@ public void Can_Convert_from_ObjectDictionary_with_Read_Only_Dictionary() Assert.That(fromDict.Car.Name, Is.EqualTo("SubCar")); } - [Test] - public void Can_Convert_from_ObjectDictionary_with_SubClass_Type() - { - var map = new Dictionary - { - { "FirstName", "Foo" }, - { "LastName", "Bar" }, - { "Car", "Jag" }, - { "Age", 21 }, - }; - - var userDto = map.FromObjectDictionary(); - - Assert.That(userDto.FirstName, Is.EqualTo("Foo")); - Assert.That(userDto.LastName, Is.EqualTo("Bar")); - Assert.That(userDto.Car, Is.EqualTo("Jag")); - Assert.That(userDto.Age, Is.EqualTo(21)); - } - - [Test] - public void Can_Convert_from_ObjectDictionary_with_SubClass_Type_With_Nullable_Properties() - { - var map = new Dictionary - { - { "FirstName", "Foo" }, - { "LastName", "Bar" }, - { "Car", "Jag" }, - { "Age", 21 }, - }; - - var userDto = map.FromObjectDictionary(); - - Assert.That(userDto.FirstName, Is.EqualTo("Foo")); - Assert.That(userDto.LastName, Is.EqualTo("Bar")); - Assert.That(userDto.Car, Is.EqualTo("Jag")); - Assert.That(userDto.Age, Is.EqualTo(21)); - } - public class QueryCustomers : QueryDb { public string CustomerId { get; set; } @@ -200,6 +163,56 @@ Dictionary MergeObjects(params object[] sources) { Assert.That(employee.DisplayName, Is.EqualTo("John Z Doe")); } + [Test, TestCaseSource(nameof(TestDataFromObjectDictionaryWithNullableTypes))] + public void Can_Convert_from_ObjectDictionary_with_Nullable_Properties( + Dictionary map, + ModelWithFieldsOfNullableTypes expected) + { + var actual = map.FromObjectDictionary(); + + ModelWithFieldsOfNullableTypes.AssertIsEqual(actual, expected); + } + + private static IEnumerable TestDataFromObjectDictionaryWithNullableTypes + { + get + { + var defaults = ModelWithFieldsOfNullableTypes.CreateConstant(1); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NId", defaults.NId }, + { "NLongId", defaults.NLongId }, + { "NGuid", defaults.NGuid }, + { "NBool", defaults.NBool }, + { "NDateTime", defaults.NDateTime }, + { "NFloat", defaults.NFloat }, + { "NDouble", defaults.NDouble }, + { "NDecimal", defaults.NDecimal }, + { "NTimeSpan", defaults.NTimeSpan } + }, + defaults).SetName("All values populated"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id.ToString() }, + { "NId", defaults.NId.ToString() }, + { "NLongId", defaults.NLongId.ToString() }, + { "NGuid", defaults.NGuid.ToString() }, + { "NBool", defaults.NBool.ToString() }, + { "NDateTime", defaults.NDateTime.ToString() }, + { "NFloat", defaults.NFloat.ToString() }, + { "NDouble", defaults.NDouble.ToString() }, + { "NDecimal", defaults.NDecimal.ToString() }, + { "NTimeSpan", defaults.NTimeSpan.ToString() } + }, + defaults).SetName("All values populated as strings"); + } + } } + } \ No newline at end of file diff --git a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs index 27b55a9de..45901159c 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingTests.cs @@ -49,16 +49,6 @@ public class UserDto public string Car { get; set; } } - public class AgedUserWithNullable : UserDto - { - public long? Age { get; set; } - } - - public class AgedUser : UserDto - { - public long Age { get; set; } - } - public enum Color { Red, From 8ff70b8094834a58a9e9fd777d6a6f4043a6cfec Mon Sep 17 00:00:00 2001 From: Neil Dobson Date: Sun, 27 May 2018 20:39:20 +1000 Subject: [PATCH 4/5] Add tests --- .../AutoMappingObjectDictionaryTests.cs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs index d13c8fe37..6553208f2 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Linq; using Northwind.Common.DataModel; using NUnit.Framework; using ServiceStack.Common.Tests.Models; @@ -210,8 +211,115 @@ private static IEnumerable TestDataFromObjectDictionaryWithNullabl { "NTimeSpan", defaults.NTimeSpan.ToString() } }, defaults).SetName("All values populated as strings"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NId", null }, + { "NLongId", null }, + { "NGuid", null }, + { "NBool", null }, + { "NDateTime", null }, + { "NFloat", null }, + { "NDouble", null }, + { "NDecimal", null }, + { "NTimeSpan", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id + }).SetName("Nullables set to null"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id + }).SetName("Nullables unassigned"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NLongId", 2 }, + { "NFloat", "3.1" }, + { "NDecimal", 4.2d }, + { "NTimeSpan", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id, + NLongId = 2, + NFloat = 3.1f, + NDecimal = 4.2m + }).SetName("Mixed properties"); + + yield return new TestCaseData( + new Dictionary + { + { "Id", defaults.Id }, + { "NMadeUp", 99.9 }, + { "NLongId", 2 }, + { "NFloat", "3.1" }, + { "NRandom", "RANDOM" }, + { "NDecimal", 4.2d }, + { "NTimeSpan", null }, + { "NNull", null } + }, + new ModelWithFieldsOfNullableTypes + { + Id = defaults.Id, + NLongId = 2, + NFloat = 3.1f, + NDecimal = 4.2m + }).SetName("Mixed properties with some foreign key/values"); } } + + [Test] + public void Can_Convert_from_ObjectDictionary_with_Nullable_Collection_Properties() + { + var map = new Dictionary + { + { "Id", 1 }, + { "Users", new[] { new User { FirstName = "Foo", LastName = "Bar", Car = new Car { Name = "Jag", Age = 25 }}}}, + { "Cars", new List { new Car { Name = "Toyota", Age = 2 }, new Car { Name = "Lexus", Age = 1 }}}, + { "Colors", null } + }; + + var actual = map.FromObjectDictionary(); + + Assert.That(actual.Id, Is.EqualTo(1)); + Assert.That(actual.Users, Is.Not.Null); + Assert.That(actual.Users.Count(), Is.EqualTo(1)); + var user = actual.Users.Single(); + Assert.That(user.FirstName, Is.EqualTo("Foo")); + Assert.That(user.LastName, Is.EqualTo("Bar")); + Assert.That(user.Car, Is.Not.Null); + Assert.That(user.Car.Name, Is.EqualTo("Jag")); + Assert.That(user.Car.Age, Is.EqualTo(25)); + Assert.That(actual.Cars, Is.Not.Null); + Assert.That(actual.Cars.Count, Is.EqualTo(2)); + var firstCar = actual.Cars.First(); + Assert.That(firstCar.Name, Is.EqualTo("Toyota")); + Assert.That(firstCar.Age, Is.EqualTo(2)); + var secondCar = actual.Cars.Last(); + Assert.That(secondCar.Name, Is.EqualTo("Lexus")); + Assert.That(secondCar.Age, Is.EqualTo(1)); + Assert.That(actual.Colors, Is.Null); + } + + public class ModelWithCollectionsOfNullableTypes + { + public int Id { get; set; } + public IEnumerable Users { get; set; } + public Car[] Cars { get; set; } + public IList Colors { get; set; } + } } From cbf535cf5f7b977a73793bd719bae2c185a32ac0 Mon Sep 17 00:00:00 2001 From: Neil Dobson Date: Mon, 28 May 2018 08:51:46 +1000 Subject: [PATCH 5/5] Fix localization issue - use ISO 8601 date as string --- .../ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs index 6553208f2..07948167a 100644 --- a/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs +++ b/tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs @@ -204,7 +204,7 @@ private static IEnumerable TestDataFromObjectDictionaryWithNullabl { "NLongId", defaults.NLongId.ToString() }, { "NGuid", defaults.NGuid.ToString() }, { "NBool", defaults.NBool.ToString() }, - { "NDateTime", defaults.NDateTime.ToString() }, + { "NDateTime", defaults.NDateTime?.ToString("o") }, { "NFloat", defaults.NFloat.ToString() }, { "NDouble", defaults.NDouble.ToString() }, { "NDecimal", defaults.NDecimal.ToString() },