Skip to content
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
98 changes: 87 additions & 11 deletions JSONAPI.Tests/Core/ResourceTypeRegistrarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,25 +187,38 @@ private AttributeGrabBag InitializeGrabBag()

private void AssertAttribute<TPropertyType, TTokenType>(IResourceTypeRegistration reg, string attributeName,
JToken tokenToSet, TPropertyType expectedPropertyValue, TTokenType expectedTokenAfterSet, Func<AttributeGrabBag, TPropertyType> getPropertyFunc)
{
AssertAttributeHelper(reg, attributeName, tokenToSet, grabBag =>
{
var propertyValueAfterSet = getPropertyFunc(grabBag);
propertyValueAfterSet.Should().Be(expectedPropertyValue);
}, token =>
{
if (expectedTokenAfterSet == null)
token.Should().BeNull();
else
{
var convertedTokenValue = token.Value<TTokenType>();
convertedTokenValue.Should().Be(expectedTokenAfterSet);
}
});
}

private void AssertAttributeHelper(IResourceTypeRegistration reg, string attributeName,
JToken tokenToSet, Action<AttributeGrabBag> testPropertyValueAfterSet,
Action<JToken> testTokenAfterSetAndGet)
{
var grabBag = InitializeGrabBag();

var field = reg.GetFieldByName(attributeName);
var attribute = (ResourceTypeAttribute) field;
var attribute = (ResourceTypeAttribute)field;
attribute.JsonKey.Should().Be(attributeName);

attribute.SetValue(grabBag, tokenToSet);
var propertyValueAfterSet = getPropertyFunc(grabBag);
propertyValueAfterSet.Should().Be(expectedPropertyValue);

testPropertyValueAfterSet(grabBag);

var convertedToken = attribute.GetValue(grabBag);
if (expectedTokenAfterSet == null)
convertedToken.Should().BeNull();
else
{
var convertedTokenValue = convertedToken.Value<TTokenType>();
convertedTokenValue.Should().Be(expectedTokenAfterSet);
}
testTokenAfterSetAndGet(convertedToken);
}

[TestMethod]
Expand Down Expand Up @@ -733,5 +746,68 @@ public void BuildRegistration_sets_up_correct_attribute_for_nullable_enum_field(
AssertAttribute(reg, "nullable-enum-field", (int)SampleEnum.Value1, SampleEnum.Value1, (int)SampleEnum.Value1, g => g.NullableEnumField);
AssertAttribute(reg, "nullable-enum-field", null, null, (SampleEnum?)null, g => g.NullableEnumField);
}

[TestMethod]
public void BuildRegistration_sets_up_correct_attribute_for_to_one_complex_field()
{
// Arrange
var registrar = new ResourceTypeRegistrar(new DefaultNamingConventions(new PluralizationService()));

// Act
var reg = registrar.BuildRegistration(typeof(AttributeGrabBag));

// Assert
AssertAttributeHelper(reg, "to-one-complex-type-field",
new JObject { { "intProp", 32 }, { "StringProp", "qux" } },
grabBag =>
{
grabBag.ToOneComplexTypeField.Should().NotBeNull();
grabBag.ToOneComplexTypeField.IntProp.Should().Be(32);
grabBag.ToOneComplexTypeField.StringProp.Should().Be("qux");
},
token =>
{
((int)token["intProp"]).Should().Be(32);
((string)token["StringProp"]).Should().Be("qux");
});
AssertAttribute(reg, "to-one-complex-type-field", null, null, (SampleComplexType)null, g => g.ToOneComplexTypeField);
}

[TestMethod]
public void BuildRegistration_sets_up_correct_attribute_for_to_many_complex_field()
{
// Arrange
var registrar = new ResourceTypeRegistrar(new DefaultNamingConventions(new PluralizationService()));

// Act
var reg = registrar.BuildRegistration(typeof(AttributeGrabBag));

// Assert
AssertAttributeHelper(reg, "to-many-complex-type-field",
new JArray
{
new JObject { { "intProp", 49 }, { "StringProp", "blue" } },
new JObject { { "intProp", 67 }, { "StringProp", "orange" } }
},
grabBag =>
{
var result = grabBag.ToManyComplexTypeField.ToArray();
result.Length.Should().Be(2);
result[0].IntProp.Should().Be(49);
result[0].StringProp.Should().Be("blue");
result[1].IntProp.Should().Be(67);
result[1].StringProp.Should().Be("orange");
},
token =>
{
var jarray = (JArray) token;
jarray.Count.Should().Be(2);
((int)jarray[0]["intProp"]).Should().Be(49);
((string)jarray[0]["StringProp"]).Should().Be("blue");
((int)jarray[1]["intProp"]).Should().Be(67);
((string)jarray[1]["StringProp"]).Should().Be("orange");
});
AssertAttribute(reg, "to-many-complex-type-field", null, null, (SampleComplexType[])null, g => g.ToManyComplexTypeField);
}
}
}
16 changes: 16 additions & 0 deletions JSONAPI.Tests/Models/AttributeGrabBag.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using JSONAPI.Attributes;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

namespace JSONAPI.Tests.Models
{
Expand All @@ -9,6 +12,13 @@ public enum SampleEnum
Value2 = 2
}

public class SampleComplexType
{
[JsonProperty("intProp")]
public Int32 IntProp { get; set; }
public string StringProp { get; set; }
}

public class AttributeGrabBag
{
public string Id { get; set; }
Expand Down Expand Up @@ -48,5 +58,11 @@ public class AttributeGrabBag

[SerializeAsComplex]
public string ComplexAttributeField { get; set; }

[SerializeAsComplex]
public SampleComplexType ToOneComplexTypeField { get; set; }

[SerializeAsComplex]
public virtual ICollection<SampleComplexType> ToManyComplexTypeField { get; set; }
}
}
2 changes: 1 addition & 1 deletion JSONAPI/Core/ComplexAttributeValueConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace JSONAPI.Core
{
/// <summary>
/// Implementation of <see cref="IAttributeValueConverter" /> suitable for
/// use with complex attributes.
/// use with complex attributes that deserialize to strings.
/// </summary>
public class ComplexAttributeValueConverter : IAttributeValueConverter
{
Expand Down
39 changes: 39 additions & 0 deletions JSONAPI/Core/ObjectComplexAttributeValueConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Reflection;
using Newtonsoft.Json.Linq;

namespace JSONAPI.Core
{
/// <summary>
/// Implementation of <see cref="IAttributeValueConverter" /> suitable for
/// use with complex attributes that deserialize to custom types.
/// </summary>
public class ObjectComplexAttributeValueConverter : IAttributeValueConverter
{
private readonly PropertyInfo _property;
private readonly bool _isToMany;

/// <summary>
/// Creates a new ComplexAttributeValueConverter
/// </summary>
/// <param name="property"></param>
/// <param name="isToMany"></param>
public ObjectComplexAttributeValueConverter(PropertyInfo property, bool isToMany)
{
_property = property;
_isToMany = isToMany;
}

public JToken GetValue(object resource)
{
var value = _property.GetValue(resource);
if (value == null) return null;
return _isToMany ? (JToken)JArray.FromObject(value) : JObject.FromObject(value);
}

public void SetValue(object resource, JToken value)
{
var deserialized = value?.ToObject(_property.PropertyType);
_property.SetValue(resource, deserialized);
}
}
}
12 changes: 10 additions & 2 deletions JSONAPI/Core/ResourceTypeRegistrar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,15 @@ protected virtual IAttributeValueConverter GetValueConverterForProperty(Property
{
var serializeAsComplexAttribute = prop.GetCustomAttribute<SerializeAsComplexAttribute>();
if (serializeAsComplexAttribute != null)
return new ComplexAttributeValueConverter(prop);
{
if (prop.PropertyType == typeof (string))
return new ComplexAttributeValueConverter(prop);

var isToMany =
prop.PropertyType.IsArray ||
(prop.PropertyType.GetInterfaces().Contains(typeof(System.Collections.IEnumerable)) && prop.PropertyType.IsGenericType);
return new ObjectComplexAttributeValueConverter(prop, isToMany);
}

if (prop.PropertyType == typeof(DateTime))
return new DateTimeAttributeValueConverter(prop, false);
Expand Down Expand Up @@ -150,7 +158,7 @@ protected virtual ResourceTypeField CreateResourceTypeField(PropertyInfo prop)

var type = prop.PropertyType;

if (prop.PropertyType.CanWriteAsJsonApiAttribute())
if (prop.PropertyType.CanWriteAsJsonApiAttribute() || prop.GetCustomAttributes<SerializeAsComplexAttribute>().Any())
{
var converter = GetValueConverterForProperty(prop);
return new ResourceTypeAttribute(converter, prop, jsonKey);
Expand Down
1 change: 1 addition & 0 deletions JSONAPI/JSONAPI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<Compile Include="Core\IEphemeralRelatedResourceReader.cs" />
<Compile Include="Core\INamingConventions.cs" />
<Compile Include="Core\IResourceTypeRegistrar.cs" />
<Compile Include="Core\ObjectComplexAttributeValueConverter.cs" />
<Compile Include="Core\PathVisitor.cs" />
<Compile Include="Core\ResourceTypeAttribute.cs" />
<Compile Include="Core\ResourceTypeRegistrar.cs" />
Expand Down