Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions src/ServiceStack.Text/AutoMappingUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
159 changes: 159 additions & 0 deletions tests/ServiceStack.Text.Tests/AutoMappingObjectDictionaryTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Northwind.Common.DataModel;
using NUnit.Framework;
using ServiceStack.Common.Tests.Models;

namespace ServiceStack.Text.Tests
{
Expand Down Expand Up @@ -162,6 +164,163 @@ Dictionary<string,object> 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<string, object> map,
ModelWithFieldsOfNullableTypes expected)
{
var actual = map.FromObjectDictionary<ModelWithFieldsOfNullableTypes>();

ModelWithFieldsOfNullableTypes.AssertIsEqual(actual, expected);
}

private static IEnumerable<TestCaseData> TestDataFromObjectDictionaryWithNullableTypes
{
get
{
var defaults = ModelWithFieldsOfNullableTypes.CreateConstant(1);

yield return new TestCaseData(
new Dictionary<string, object>
{
{ "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<string, object>
{
{ "Id", defaults.Id.ToString() },
{ "NId", defaults.NId.ToString() },
{ "NLongId", defaults.NLongId.ToString() },
{ "NGuid", defaults.NGuid.ToString() },
{ "NBool", defaults.NBool.ToString() },
{ "NDateTime", defaults.NDateTime?.ToString("o") },
{ "NFloat", defaults.NFloat.ToString() },
{ "NDouble", defaults.NDouble.ToString() },
{ "NDecimal", defaults.NDecimal.ToString() },
{ "NTimeSpan", defaults.NTimeSpan.ToString() }
},
defaults).SetName("All values populated as strings");

yield return new TestCaseData(
new Dictionary<string, object>
{
{ "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<string, object>
{
{ "Id", defaults.Id }
},
new ModelWithFieldsOfNullableTypes
{
Id = defaults.Id
}).SetName("Nullables unassigned");

yield return new TestCaseData(
new Dictionary<string, object>
{
{ "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<string, object>
{
{ "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<string, object>
{
{ "Id", 1 },
{ "Users", new[] { new User { FirstName = "Foo", LastName = "Bar", Car = new Car { Name = "Jag", Age = 25 }}}},
{ "Cars", new List<Car> { new Car { Name = "Toyota", Age = 2 }, new Car { Name = "Lexus", Age = 1 }}},
{ "Colors", null }
};

var actual = map.FromObjectDictionary<ModelWithCollectionsOfNullableTypes>();

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<User> Users { get; set; }
public Car[] Cars { get; set; }
public IList<Color> Colors { get; set; }
}
}


}