From 63e8c5cb63733a897f89513b2b5fe45d246080d6 Mon Sep 17 00:00:00 2001 From: David Perfors Date: Fri, 10 Nov 2017 20:21:12 +0100 Subject: [PATCH 1/3] Add SerializerSettings to JsonApiOptions --- .../Configuration/JsonApiOptions.cs | 98 +++-- .../Serialization/JsonApiDeSerializer.cs | 391 ++++++++--------- .../Serialization/JsonApiSerializer.cs | 139 +++--- .../Serialization/JsonApiDeSerializerTests.cs | 413 +++++++++--------- .../Serialization/JsonApiSerializerTests.cs | 92 ++-- 5 files changed, 568 insertions(+), 565 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index 3d169998a3..c77bfe502d 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -3,47 +3,65 @@ using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Serialization; using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace JsonApiDotNetCore.Configuration { - public class JsonApiOptions - { - public string Namespace { get; set; } - public int DefaultPageSize { get; set; } - public bool IncludeTotalRecordCount { get; set; } - public bool AllowClientGeneratedIds { get; set; } - public IContextGraph ContextGraph { get; set; } - public bool RelativeLinks { get; set; } - - /// - /// This flag is experimental and could be perceived as a violation - /// of the v1 spec. However, we have decided that this is a real - /// requirement for users of this library and a gap in the specification. - /// It will likely be removed when the spec is updated to support this - /// requirement. - /// - public bool AllowCustomQueryParameters { get; set; } - public IContractResolver JsonContractResolver { get; set; } = new DasherizedResolver(); - internal IContextGraphBuilder ContextGraphBuilder { get; } = new ContextGraphBuilder(); - - public void BuildContextGraph(Action builder) - where TContext : DbContext - { - BuildContextGraph(builder); - - ContextGraphBuilder.AddDbContext(); - - ContextGraph = ContextGraphBuilder.Build(); - } - - public void BuildContextGraph(Action builder) - { - if (builder == null) return; - - builder(ContextGraphBuilder); - - ContextGraph = ContextGraphBuilder.Build(); - } - } -} + public class JsonApiOptions + { + public string Namespace { get; set; } + + public int DefaultPageSize { get; set; } + + public bool IncludeTotalRecordCount { get; set; } + + public bool AllowClientGeneratedIds { get; set; } + + public IContextGraph ContextGraph { get; set; } + + public bool RelativeLinks { get; set; } + + /// + /// This flag is experimental and could be perceived as a violation + /// of the v1 spec. However, we have decided that this is a real + /// requirement for users of this library and a gap in the specification. + /// It will likely be removed when the spec is updated to support this + /// requirement. + /// + public bool AllowCustomQueryParameters { get; set; } + + public IContractResolver JsonContractResolver + { + get => SerializerSettings.ContractResolver; + set => SerializerSettings.ContractResolver = value; + } + + internal IContextGraphBuilder ContextGraphBuilder { get; } = new ContextGraphBuilder(); + + public JsonSerializerSettings SerializerSettings { get; } = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DasherizedResolver() + }; + + public void BuildContextGraph(Action builder) + where TContext : DbContext + { + BuildContextGraph(builder); + + ContextGraphBuilder.AddDbContext(); + + ContextGraph = ContextGraphBuilder.Build(); + } + + public void BuildContextGraph(Action builder) + { + if (builder == null) return; + + builder(ContextGraphBuilder); + + ContextGraph = ContextGraphBuilder.Build(); + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 608a23b66e..e4fd0205e7 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -11,216 +11,211 @@ namespace JsonApiDotNetCore.Serialization { - public class JsonApiDeSerializer : IJsonApiDeSerializer - { - private readonly IJsonApiContext _jsonApiContext; - private readonly IGenericProcessorFactory _genericProcessorFactory; - - public JsonApiDeSerializer( - IJsonApiContext jsonApiContext, - IGenericProcessorFactory genericProcessorFactory) - { - _jsonApiContext = jsonApiContext; - _genericProcessorFactory = genericProcessorFactory; - } - - public object Deserialize(string requestBody) - { - try - { - var document = JsonConvert.DeserializeObject(requestBody); - var entity = DocumentToObject(document.Data); - return entity; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - public TEntity Deserialize(string requestBody) => (TEntity)Deserialize(requestBody); - - public object DeserializeRelationship(string requestBody) - { - try - { - var data = JToken.Parse(requestBody)["data"]; - - if (data is JArray) - return data.ToObject>(); - - return new List { data.ToObject() }; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - public List DeserializeList(string requestBody) - { - try - { - var documents = JsonConvert.DeserializeObject(requestBody); - - var deserializedList = new List(); - foreach (var data in documents.Data) - { - var entity = DocumentToObject(data); - deserializedList.Add((TEntity)entity); - } - - return deserializedList; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - private object DocumentToObject(DocumentData data) - { - var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(data.Type); - _jsonApiContext.RequestEntity = contextEntity; - - var entity = Activator.CreateInstance(contextEntity.EntityType); - - entity = SetEntityAttributes(entity, contextEntity, data.Attributes); - entity = SetRelationships(entity, contextEntity, data.Relationships); - - var identifiableEntity = (IIdentifiable)entity; - - if (data.Id != null) - identifiableEntity.StringId = data.Id; - - return identifiableEntity; - } - - private object SetEntityAttributes( - object entity, ContextEntity contextEntity, Dictionary attributeValues) - { - if (attributeValues == null || attributeValues.Count == 0) - return entity; - - var entityProperties = entity.GetType().GetProperties(); - - foreach (var attr in contextEntity.Attributes) - { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalAttributeName); - - if (entityProperty == null) - throw new ArgumentException($"{contextEntity.EntityType.Name} does not contain an attribute named {attr.InternalAttributeName}", nameof(entity)); - - if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue)) - { - var convertedValue = ConvertAttrValue(newValue, entityProperty.PropertyType); - entityProperty.SetValue(entity, convertedValue); - - if (attr.IsImmutable == false) - _jsonApiContext.AttributesToUpdate[attr] = convertedValue; - } - } - - return entity; - } - - private object ConvertAttrValue(object newValue, Type targetType) - { - if (newValue is JContainer jObject) - return DeserializeComplexType(jObject, targetType); - - var convertedValue = TypeHelper.ConvertType(newValue, targetType); - return convertedValue; - } - - private object DeserializeComplexType(JContainer obj, Type targetType) - { - var serializerSettings = new JsonSerializerSettings - { - ContractResolver = _jsonApiContext.Options.JsonContractResolver - }; - - return obj.ToObject(targetType, JsonSerializer.Create(serializerSettings)); - } - - private object SetRelationships( - object entity, - ContextEntity contextEntity, - Dictionary relationships) - { - if (relationships == null || relationships.Count == 0) - return entity; - - var entityProperties = entity.GetType().GetProperties(); - - foreach (var attr in contextEntity.Relationships) - { - entity = attr.IsHasOne - ? SetHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships) - : SetHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships); - } - - return entity; - } + public class JsonApiDeSerializer : IJsonApiDeSerializer + { + private readonly IJsonApiContext _jsonApiContext; + private readonly IGenericProcessorFactory _genericProcessorFactory; + + public JsonApiDeSerializer( + IJsonApiContext jsonApiContext, + IGenericProcessorFactory genericProcessorFactory) + { + _jsonApiContext = jsonApiContext; + _genericProcessorFactory = genericProcessorFactory; + } + + public object Deserialize(string requestBody) + { + try + { + var document = JsonConvert.DeserializeObject(requestBody); + var entity = DocumentToObject(document.Data); + return entity; + } + catch (Exception e) + { + throw new JsonApiException(400, "Failed to deserialize request body", e); + } + } + + public TEntity Deserialize(string requestBody) => (TEntity)Deserialize(requestBody); + + public object DeserializeRelationship(string requestBody) + { + try + { + var data = JToken.Parse(requestBody)["data"]; + + if (data is JArray) + return data.ToObject>(); + + return new List { data.ToObject() }; + } + catch (Exception e) + { + throw new JsonApiException(400, "Failed to deserialize request body", e); + } + } + + public List DeserializeList(string requestBody) + { + try + { + var documents = JsonConvert.DeserializeObject(requestBody); + + var deserializedList = new List(); + foreach (var data in documents.Data) + { + var entity = DocumentToObject(data); + deserializedList.Add((TEntity)entity); + } + + return deserializedList; + } + catch (Exception e) + { + throw new JsonApiException(400, "Failed to deserialize request body", e); + } + } + + private object DocumentToObject(DocumentData data) + { + var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(data.Type); + _jsonApiContext.RequestEntity = contextEntity; + + var entity = Activator.CreateInstance(contextEntity.EntityType); + + entity = SetEntityAttributes(entity, contextEntity, data.Attributes); + entity = SetRelationships(entity, contextEntity, data.Relationships); + + var identifiableEntity = (IIdentifiable)entity; + + if (data.Id != null) + identifiableEntity.StringId = data.Id; + + return identifiableEntity; + } + + private object SetEntityAttributes( + object entity, ContextEntity contextEntity, Dictionary attributeValues) + { + if (attributeValues == null || attributeValues.Count == 0) + return entity; + + var entityProperties = entity.GetType().GetProperties(); + + foreach (var attr in contextEntity.Attributes) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalAttributeName); + + if (entityProperty == null) + throw new ArgumentException($"{contextEntity.EntityType.Name} does not contain an attribute named {attr.InternalAttributeName}", nameof(entity)); + + if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue)) + { + var convertedValue = ConvertAttrValue(newValue, entityProperty.PropertyType); + entityProperty.SetValue(entity, convertedValue); + + if (attr.IsImmutable == false) + _jsonApiContext.AttributesToUpdate[attr] = convertedValue; + } + } + + return entity; + } + + private object ConvertAttrValue(object newValue, Type targetType) + { + if (newValue is JContainer jObject) + return DeserializeComplexType(jObject, targetType); + + var convertedValue = TypeHelper.ConvertType(newValue, targetType); + return convertedValue; + } + + private object DeserializeComplexType(JContainer obj, Type targetType) + { + return obj.ToObject(targetType, JsonSerializer.Create(_jsonApiContext.Options.SerializerSettings)); + } + + private object SetRelationships( + object entity, + ContextEntity contextEntity, + Dictionary relationships) + { + if (relationships == null || relationships.Count == 0) + return entity; + + var entityProperties = entity.GetType().GetProperties(); + + foreach (var attr in contextEntity.Relationships) + { + entity = attr.IsHasOne + ? SetHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships) + : SetHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships); + } + + return entity; + } + + private object SetHasOneRelationship(object entity, + PropertyInfo[] entityProperties, + RelationshipAttribute attr, + ContextEntity contextEntity, + Dictionary relationships) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id"); - private object SetHasOneRelationship(object entity, - PropertyInfo[] entityProperties, - RelationshipAttribute attr, - ContextEntity contextEntity, - Dictionary relationships) - { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id"); + if (entityProperty == null) + throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); - if (entityProperty == null) - throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); + var relationshipName = attr.PublicRelationshipName; - var relationshipName = attr.PublicRelationshipName; + if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) + { + var relationshipAttr = _jsonApiContext.RequestEntity.Relationships + .SingleOrDefault(r => r.PublicRelationshipName == relationshipName); - if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) - { - var relationshipAttr = _jsonApiContext.RequestEntity.Relationships - .SingleOrDefault(r => r.PublicRelationshipName == relationshipName); + var data = (Dictionary)relationshipData.ExposedData; - var data = (Dictionary)relationshipData.ExposedData; + if (data == null) return entity; - if (data == null) return entity; + var newValue = data["id"]; + var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType); - var newValue = data["id"]; - var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType); + _jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue; - _jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue; + entityProperty.SetValue(entity, convertedValue); + } - entityProperty.SetValue(entity, convertedValue); - } + return entity; + } - return entity; - } + private object SetHasManyRelationship(object entity, + PropertyInfo[] entityProperties, + RelationshipAttribute attr, + ContextEntity contextEntity, + Dictionary relationships) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName); - private object SetHasManyRelationship(object entity, - PropertyInfo[] entityProperties, - RelationshipAttribute attr, - ContextEntity contextEntity, - Dictionary relationships) - { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName); + if (entityProperty == null) + throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); - if (entityProperty == null) - throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); + var relationshipName = attr.PublicRelationshipName; - var relationshipName = attr.PublicRelationshipName; - - if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) - { - var data = (List>)relationshipData.ExposedData; - - if (data == null) return entity; + if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) + { + var data = (List>)relationshipData.ExposedData; - var genericProcessor = _genericProcessorFactory.GetProcessor(attr.Type); - var ids = relationshipData.ManyData.Select(r => r["id"]); - genericProcessor.SetRelationships(entity, attr, ids); - } + if (data == null) return entity; - return entity; - } - } -} + var genericProcessor = _genericProcessorFactory.GetProcessor(attr.Type); + var ids = relationshipData.ManyData.Select(r => r["id"]); + genericProcessor.SetRelationships(entity, attr, ids); + } + + return entity; + } + } +} \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index a7e14341b0..e69acc1d9c 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -8,85 +8,82 @@ namespace JsonApiDotNetCore.Serialization { - public class JsonApiSerializer : IJsonApiSerializer - { - private readonly IDocumentBuilder _documentBuilder; - private readonly ILogger _logger; - private readonly IJsonApiContext _jsonApiContext; + public class JsonApiSerializer : IJsonApiSerializer + { + private readonly IDocumentBuilder _documentBuilder; + private readonly ILogger _logger; + private readonly IJsonApiContext _jsonApiContext; - public JsonApiSerializer( - IJsonApiContext jsonApiContext, - IDocumentBuilder documentBuilder) - { - _jsonApiContext = jsonApiContext; - _documentBuilder = documentBuilder; - } + public JsonApiSerializer( + IJsonApiContext jsonApiContext, + IDocumentBuilder documentBuilder) + { + _jsonApiContext = jsonApiContext; + _documentBuilder = documentBuilder; + } - public JsonApiSerializer( - IJsonApiContext jsonApiContext, - IDocumentBuilder documentBuilder, - ILoggerFactory loggerFactory) - { - _jsonApiContext = jsonApiContext; - _documentBuilder = documentBuilder; - _logger = loggerFactory?.CreateLogger(); - } + public JsonApiSerializer( + IJsonApiContext jsonApiContext, + IDocumentBuilder documentBuilder, + ILoggerFactory loggerFactory) + { + _jsonApiContext = jsonApiContext; + _documentBuilder = documentBuilder; + _logger = loggerFactory?.CreateLogger(); + } - public string Serialize(object entity) - { - if (entity == null) - return GetNullDataResponse(); + public string Serialize(object entity) + { + if (entity == null) + return GetNullDataResponse(); - if (entity.GetType() == typeof(ErrorCollection) || _jsonApiContext.RequestEntity == null) - return GetErrorJson(entity, _logger); + if (entity.GetType() == typeof(ErrorCollection) || _jsonApiContext.RequestEntity == null) + return GetErrorJson(entity, _logger); - if (entity is IEnumerable) - return SerializeDocuments(entity); + if (entity is IEnumerable) + return SerializeDocuments(entity); - return SerializeDocument(entity); - } + return SerializeDocument(entity); + } - private string GetNullDataResponse() - { - return JsonConvert.SerializeObject(new Document - { - Data = null - }); - } + private string GetNullDataResponse() + { + return JsonConvert.SerializeObject(new Document + { + Data = null + }); + } - private string GetErrorJson(object responseObject, ILogger logger) - { - if (responseObject is ErrorCollection errorCollection) - { - return errorCollection.GetJson(); - } - else - { - logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); - return JsonConvert.SerializeObject(responseObject); - } - } + private string GetErrorJson(object responseObject, ILogger logger) + { + if (responseObject is ErrorCollection errorCollection) + { + return errorCollection.GetJson(); + } + else + { + logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); + return JsonConvert.SerializeObject(responseObject); + } + } - private string SerializeDocuments(object entity) - { - var entities = entity as IEnumerable; - var documents = _documentBuilder.Build(entities); - return _serialize(documents); - } + private string SerializeDocuments(object entity) + { + var entities = entity as IEnumerable; + var documents = _documentBuilder.Build(entities); + return _serialize(documents); + } - private string SerializeDocument(object entity) - { - var identifiableEntity = entity as IIdentifiable; - var document = _documentBuilder.Build(identifiableEntity); - return _serialize(document); - } + private string SerializeDocument(object entity) + { + var identifiableEntity = entity as IIdentifiable; + var document = _documentBuilder.Build(identifiableEntity); + return _serialize(document); + } - private string _serialize(object obj) - { - return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { - NullValueHandling = NullValueHandling.Ignore, - ContractResolver = _jsonApiContext.Options.JsonContractResolver - }); - } - } -} + private string _serialize(object obj) + { + return JsonConvert.SerializeObject(obj, _jsonApiContext.Options.SerializerSettings); + } + } +} \ No newline at end of file diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index 53edc9faad..51abc4698d 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -12,211 +12,208 @@ namespace UnitTests.Serialization { - public class JsonApiDeSerializerTests - { - [Fact] - public void Can_Deserialize_Complex_Types() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - { - JsonContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-member", new { compoundName = "testName" } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal("testName", result.ComplexMember.CompoundName); - } - - [Fact] - public void Can_Deserialize_Complex_List_Types() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - { - JsonContractResolver = new CamelCasePropertyNamesContractResolver() - }); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-members", new [] { - new { compoundName = "testName" } - } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMembers); - Assert.NotEmpty(result.ComplexMembers); - Assert.Equal("testName", result.ComplexMembers[0].CompoundName); - } - - [Fact] - public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - { - JsonContractResolver = new DasherizedResolver() // <--- - }); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-member", new Dictionary { { "compound-name", "testName" } } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal("testName", result.ComplexMember.CompoundName); - } - - [Fact] - public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var attributesToUpdate = new Dictionary(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); - - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions - { - JsonContractResolver = new DasherizedResolver() - }); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { "complex-member", new Dictionary { - { "compound-name", "testName" } } - }, - { "immutable", "value"} - } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal(1, attributesToUpdate.Count); - - foreach(var attr in attributesToUpdate) - Assert.False(attr.Key.IsImmutable); - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [Attr("immutable", isImmutable: true)] - public string Immutable { get; set; } - } - - private class TestResourceWithList : Identifiable - { - [Attr("complex-members")] - public List ComplexMembers { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - } -} + public class JsonApiDeSerializerTests + { + [Fact] + public void Can_Deserialize_Complex_Types() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { + "complex-member", new { compoundName = "testName" } + } + } + } + }; + + // act + var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); + + // assert + Assert.NotNull(result.ComplexMember); + Assert.Equal("testName", result.ComplexMember.CompoundName); + } + + [Fact] + public void Can_Deserialize_Complex_List_Types() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { + "complex-members", new [] { + new { compoundName = "testName" } + } + } + } + } + }; + + // act + var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); + + // assert + Assert.NotNull(result.ComplexMembers); + Assert.NotEmpty(result.ComplexMembers); + Assert.Equal("testName", result.ComplexMembers[0].CompoundName); + } + + [Fact] + public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { + "complex-member", new Dictionary { { "compound-name", "testName" } } + } + } + } + }; + + // act + var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); + + // assert + Assert.NotNull(result.ComplexMember); + Assert.Equal("testName", result.ComplexMember.CompoundName); + } + + [Fact] + public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var attributesToUpdate = new Dictionary(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { "complex-member", new Dictionary { + { "compound-name", "testName" } } + }, + { "immutable", "value"} + } + } + }; + + var contentString = JsonConvert.SerializeObject(content); + + // act + var result = deserializer.Deserialize(contentString); + + // assert + Assert.NotNull(result.ComplexMember); + Assert.Equal(1, attributesToUpdate.Count); + + foreach (var attr in attributesToUpdate) + Assert.False(attr.Key.IsImmutable); + } + + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + + [Attr("immutable", isImmutable: true)] + public string Immutable { get; set; } + } + + private class TestResourceWithList : Identifiable + { + [Attr("complex-members")] + public List ComplexMembers { get; set; } + } + + private class ComplexType + { + public string CompoundName { get; set; } + } + } +} \ No newline at end of file diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index e671f3fc0c..91d681ef24 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -1,64 +1,60 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Builders; +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; -using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; using Moq; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using Xunit; namespace UnitTests.Serialization { - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); + public class JsonApiSerializerTests + { + [Fact] + public void Can_Serialize_Complex_Types() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions { - JsonContractResolver = new DasherizedResolver() - }); - jsonApiContextMock.Setup(m => m.RequestEntity) - .Returns(contextGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager()); + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); + jsonApiContextMock.Setup(m => m.RequestEntity) + .Returns(contextGraph.GetContextEntity("test-resource")); + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); + jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager()); - var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - var resource = new TestResource { - ComplexMember = new ComplexType { - CompoundName = "testname" - } - }; + var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); + var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + var resource = new TestResource + { + ComplexMember = new ComplexType + { + CompoundName = "testname" + } + }; - // act - var result = serializer.Serialize(resource); + // act + var result = serializer.Serialize(resource); - // assert - Assert.NotNull(result); - Assert.Equal("{\"data\":{\"type\":\"test-resource\",\"id\":\"\",\"attributes\":{\"complex-member\":{\"compound-name\":\"testname\"}}}}", result); - } + // assert + Assert.NotNull(result); + Assert.Equal("{\"data\":{\"type\":\"test-resource\",\"id\":\"\",\"attributes\":{\"complex-member\":{\"compound-name\":\"testname\"}}}}", result); + } - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - } + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + } - private class ComplexType - { - public string CompoundName { get; set; } - } - } -} + private class ComplexType + { + public string CompoundName { get; set; } + } + } +} \ No newline at end of file From 9ed40a54e7f68c050be9bd5dec59681ac408aeee Mon Sep 17 00:00:00 2001 From: David Perfors Date: Sat, 11 Nov 2017 17:19:03 +0100 Subject: [PATCH 2/3] Fixed spacing, and added obsolete attribute to JsonContractResolver --- .../Configuration/JsonApiOptions.cs | 107 ++++++++---------- .../Serialization/JsonApiDeSerializer.cs | 2 +- .../Serialization/JsonApiSerializer.cs | 2 +- .../Serialization/JsonApiDeSerializerTests.cs | 2 +- .../Serialization/JsonApiSerializerTests.cs | 8 +- 5 files changed, 59 insertions(+), 62 deletions(-) diff --git a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs index c77bfe502d..58d1a33376 100644 --- a/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs +++ b/src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs @@ -8,60 +8,53 @@ namespace JsonApiDotNetCore.Configuration { - public class JsonApiOptions - { - public string Namespace { get; set; } - - public int DefaultPageSize { get; set; } - - public bool IncludeTotalRecordCount { get; set; } - - public bool AllowClientGeneratedIds { get; set; } - - public IContextGraph ContextGraph { get; set; } - - public bool RelativeLinks { get; set; } - - /// - /// This flag is experimental and could be perceived as a violation - /// of the v1 spec. However, we have decided that this is a real - /// requirement for users of this library and a gap in the specification. - /// It will likely be removed when the spec is updated to support this - /// requirement. - /// - public bool AllowCustomQueryParameters { get; set; } - - public IContractResolver JsonContractResolver - { - get => SerializerSettings.ContractResolver; - set => SerializerSettings.ContractResolver = value; - } - - internal IContextGraphBuilder ContextGraphBuilder { get; } = new ContextGraphBuilder(); - - public JsonSerializerSettings SerializerSettings { get; } = new JsonSerializerSettings() - { - NullValueHandling = NullValueHandling.Ignore, - ContractResolver = new DasherizedResolver() - }; - - public void BuildContextGraph(Action builder) - where TContext : DbContext - { - BuildContextGraph(builder); - - ContextGraphBuilder.AddDbContext(); - - ContextGraph = ContextGraphBuilder.Build(); - } - - public void BuildContextGraph(Action builder) - { - if (builder == null) return; - - builder(ContextGraphBuilder); - - ContextGraph = ContextGraphBuilder.Build(); - } - } -} \ No newline at end of file + public class JsonApiOptions + { + public string Namespace { get; set; } + public int DefaultPageSize { get; set; } + public bool IncludeTotalRecordCount { get; set; } + public bool AllowClientGeneratedIds { get; set; } + public IContextGraph ContextGraph { get; set; } + public bool RelativeLinks { get; set; } + + /// + /// This flag is experimental and could be perceived as a violation + /// of the v1 spec. However, we have decided that this is a real + /// requirement for users of this library and a gap in the specification. + /// It will likely be removed when the spec is updated to support this + /// requirement. + /// + public bool AllowCustomQueryParameters { get; set; } + [Obsolete("JsonContract resolver can now be set on SerializerSettings.")] + public IContractResolver JsonContractResolver + { + get => SerializerSettings.ContractResolver; + set => SerializerSettings.ContractResolver = value; + } + public JsonSerializerSettings SerializerSettings { get; } = new JsonSerializerSettings() + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DasherizedResolver() + }; + internal IContextGraphBuilder ContextGraphBuilder { get; } = new ContextGraphBuilder(); + + public void BuildContextGraph(Action builder) + where TContext : DbContext + { + BuildContextGraph(builder); + + ContextGraphBuilder.AddDbContext(); + + ContextGraph = ContextGraphBuilder.Build(); + } + + public void BuildContextGraph(Action builder) + { + if (builder == null) return; + + builder(ContextGraphBuilder); + + ContextGraph = ContextGraphBuilder.Build(); + } + } +} diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index e4fd0205e7..7ce9cf7414 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -208,7 +208,7 @@ private object SetHasManyRelationship(object entity, { var data = (List>)relationshipData.ExposedData; - if (data == null) return entity; + if (data == null) return entity; var genericProcessor = _genericProcessorFactory.GetProcessor(attr.Type); var ids = relationshipData.ManyData.Select(r => r["id"]); diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index e69acc1d9c..632fb50ec5 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -12,7 +12,7 @@ public class JsonApiSerializer : IJsonApiSerializer { private readonly IDocumentBuilder _documentBuilder; private readonly ILogger _logger; - private readonly IJsonApiContext _jsonApiContext; + private readonly IJsonApiContext _jsonApiContext; public JsonApiSerializer( IJsonApiContext jsonApiContext, diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index 51abc4698d..e6bad39a0c 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -216,4 +216,4 @@ private class ComplexType public string CompoundName { get; set; } } } -} \ No newline at end of file +} diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index 91d681ef24..0193a2880a 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -1,10 +1,14 @@ -using JsonApiDotNetCore.Builders; +using System.Collections.Generic; +using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Internal; +using JsonApiDotNetCore.Internal.Generics; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Serialization; using JsonApiDotNetCore.Services; using Moq; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; using Xunit; namespace UnitTests.Serialization @@ -57,4 +61,4 @@ private class ComplexType public string CompoundName { get; set; } } } -} \ No newline at end of file +} From 4923a88e73bb2fa104e1ce7c90932c862f862563 Mon Sep 17 00:00:00 2001 From: David Perfors Date: Sat, 11 Nov 2017 17:21:02 +0100 Subject: [PATCH 3/3] Correctly fix spacing. --- .../Serialization/JsonApiDeSerializer.cs | 384 ++++++++--------- .../Serialization/JsonApiSerializer.cs | 132 +++--- .../Serialization/JsonApiDeSerializerTests.cs | 408 +++++++++--------- .../Serialization/JsonApiSerializerTests.cs | 84 ++-- 4 files changed, 504 insertions(+), 504 deletions(-) diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs index 7ce9cf7414..d8cbf245bf 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs @@ -11,211 +11,211 @@ namespace JsonApiDotNetCore.Serialization { - public class JsonApiDeSerializer : IJsonApiDeSerializer - { - private readonly IJsonApiContext _jsonApiContext; - private readonly IGenericProcessorFactory _genericProcessorFactory; - - public JsonApiDeSerializer( - IJsonApiContext jsonApiContext, - IGenericProcessorFactory genericProcessorFactory) - { - _jsonApiContext = jsonApiContext; - _genericProcessorFactory = genericProcessorFactory; - } - - public object Deserialize(string requestBody) - { - try - { - var document = JsonConvert.DeserializeObject(requestBody); - var entity = DocumentToObject(document.Data); - return entity; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - public TEntity Deserialize(string requestBody) => (TEntity)Deserialize(requestBody); - - public object DeserializeRelationship(string requestBody) - { - try - { - var data = JToken.Parse(requestBody)["data"]; - - if (data is JArray) - return data.ToObject>(); - - return new List { data.ToObject() }; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - public List DeserializeList(string requestBody) - { - try - { - var documents = JsonConvert.DeserializeObject(requestBody); - - var deserializedList = new List(); - foreach (var data in documents.Data) - { - var entity = DocumentToObject(data); - deserializedList.Add((TEntity)entity); - } - - return deserializedList; - } - catch (Exception e) - { - throw new JsonApiException(400, "Failed to deserialize request body", e); - } - } - - private object DocumentToObject(DocumentData data) - { - var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(data.Type); - _jsonApiContext.RequestEntity = contextEntity; - - var entity = Activator.CreateInstance(contextEntity.EntityType); - - entity = SetEntityAttributes(entity, contextEntity, data.Attributes); - entity = SetRelationships(entity, contextEntity, data.Relationships); - - var identifiableEntity = (IIdentifiable)entity; - - if (data.Id != null) - identifiableEntity.StringId = data.Id; - - return identifiableEntity; - } - - private object SetEntityAttributes( - object entity, ContextEntity contextEntity, Dictionary attributeValues) - { - if (attributeValues == null || attributeValues.Count == 0) - return entity; - - var entityProperties = entity.GetType().GetProperties(); - - foreach (var attr in contextEntity.Attributes) - { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalAttributeName); - - if (entityProperty == null) - throw new ArgumentException($"{contextEntity.EntityType.Name} does not contain an attribute named {attr.InternalAttributeName}", nameof(entity)); - - if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue)) - { - var convertedValue = ConvertAttrValue(newValue, entityProperty.PropertyType); - entityProperty.SetValue(entity, convertedValue); - - if (attr.IsImmutable == false) - _jsonApiContext.AttributesToUpdate[attr] = convertedValue; - } - } - - return entity; - } - - private object ConvertAttrValue(object newValue, Type targetType) - { - if (newValue is JContainer jObject) - return DeserializeComplexType(jObject, targetType); - - var convertedValue = TypeHelper.ConvertType(newValue, targetType); - return convertedValue; - } - - private object DeserializeComplexType(JContainer obj, Type targetType) - { - return obj.ToObject(targetType, JsonSerializer.Create(_jsonApiContext.Options.SerializerSettings)); - } + public class JsonApiDeSerializer : IJsonApiDeSerializer + { + private readonly IJsonApiContext _jsonApiContext; + private readonly IGenericProcessorFactory _genericProcessorFactory; + + public JsonApiDeSerializer( + IJsonApiContext jsonApiContext, + IGenericProcessorFactory genericProcessorFactory) + { + _jsonApiContext = jsonApiContext; + _genericProcessorFactory = genericProcessorFactory; + } + + public object Deserialize(string requestBody) + { + try + { + var document = JsonConvert.DeserializeObject(requestBody); + var entity = DocumentToObject(document.Data); + return entity; + } + catch (Exception e) + { + throw new JsonApiException(400, "Failed to deserialize request body", e); + } + } + + public TEntity Deserialize(string requestBody) => (TEntity)Deserialize(requestBody); + + public object DeserializeRelationship(string requestBody) + { + try + { + var data = JToken.Parse(requestBody)["data"]; + + if (data is JArray) + return data.ToObject>(); + + return new List { data.ToObject() }; + } + catch (Exception e) + { + throw new JsonApiException(400, "Failed to deserialize request body", e); + } + } + + public List DeserializeList(string requestBody) + { + try + { + var documents = JsonConvert.DeserializeObject(requestBody); + + var deserializedList = new List(); + foreach (var data in documents.Data) + { + var entity = DocumentToObject(data); + deserializedList.Add((TEntity)entity); + } + + return deserializedList; + } + catch (Exception e) + { + throw new JsonApiException(400, "Failed to deserialize request body", e); + } + } + + private object DocumentToObject(DocumentData data) + { + var contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(data.Type); + _jsonApiContext.RequestEntity = contextEntity; + + var entity = Activator.CreateInstance(contextEntity.EntityType); + + entity = SetEntityAttributes(entity, contextEntity, data.Attributes); + entity = SetRelationships(entity, contextEntity, data.Relationships); + + var identifiableEntity = (IIdentifiable)entity; + + if (data.Id != null) + identifiableEntity.StringId = data.Id; + + return identifiableEntity; + } + + private object SetEntityAttributes( + object entity, ContextEntity contextEntity, Dictionary attributeValues) + { + if (attributeValues == null || attributeValues.Count == 0) + return entity; + + var entityProperties = entity.GetType().GetProperties(); + + foreach (var attr in contextEntity.Attributes) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalAttributeName); + + if (entityProperty == null) + throw new ArgumentException($"{contextEntity.EntityType.Name} does not contain an attribute named {attr.InternalAttributeName}", nameof(entity)); + + if (attributeValues.TryGetValue(attr.PublicAttributeName, out object newValue)) + { + var convertedValue = ConvertAttrValue(newValue, entityProperty.PropertyType); + entityProperty.SetValue(entity, convertedValue); + + if (attr.IsImmutable == false) + _jsonApiContext.AttributesToUpdate[attr] = convertedValue; + } + } + + return entity; + } + + private object ConvertAttrValue(object newValue, Type targetType) + { + if (newValue is JContainer jObject) + return DeserializeComplexType(jObject, targetType); + + var convertedValue = TypeHelper.ConvertType(newValue, targetType); + return convertedValue; + } + + private object DeserializeComplexType(JContainer obj, Type targetType) + { + return obj.ToObject(targetType, JsonSerializer.Create(_jsonApiContext.Options.SerializerSettings)); + } + + private object SetRelationships( + object entity, + ContextEntity contextEntity, + Dictionary relationships) + { + if (relationships == null || relationships.Count == 0) + return entity; + + var entityProperties = entity.GetType().GetProperties(); + + foreach (var attr in contextEntity.Relationships) + { + entity = attr.IsHasOne + ? SetHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships) + : SetHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships); + } + + return entity; + } + + private object SetHasOneRelationship(object entity, + PropertyInfo[] entityProperties, + RelationshipAttribute attr, + ContextEntity contextEntity, + Dictionary relationships) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id"); + + if (entityProperty == null) + throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); - private object SetRelationships( - object entity, - ContextEntity contextEntity, - Dictionary relationships) - { - if (relationships == null || relationships.Count == 0) - return entity; + var relationshipName = attr.PublicRelationshipName; + + if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) + { + var relationshipAttr = _jsonApiContext.RequestEntity.Relationships + .SingleOrDefault(r => r.PublicRelationshipName == relationshipName); - var entityProperties = entity.GetType().GetProperties(); - - foreach (var attr in contextEntity.Relationships) - { - entity = attr.IsHasOne - ? SetHasOneRelationship(entity, entityProperties, attr, contextEntity, relationships) - : SetHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships); - } + var data = (Dictionary)relationshipData.ExposedData; - return entity; - } - - private object SetHasOneRelationship(object entity, - PropertyInfo[] entityProperties, - RelationshipAttribute attr, - ContextEntity contextEntity, - Dictionary relationships) - { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == $"{attr.InternalRelationshipName}Id"); - - if (entityProperty == null) - throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); - - var relationshipName = attr.PublicRelationshipName; - - if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) - { - var relationshipAttr = _jsonApiContext.RequestEntity.Relationships - .SingleOrDefault(r => r.PublicRelationshipName == relationshipName); - - var data = (Dictionary)relationshipData.ExposedData; - - if (data == null) return entity; + if (data == null) return entity; - var newValue = data["id"]; - var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType); + var newValue = data["id"]; + var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType); - _jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue; + _jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue; - entityProperty.SetValue(entity, convertedValue); - } + entityProperty.SetValue(entity, convertedValue); + } - return entity; - } + return entity; + } - private object SetHasManyRelationship(object entity, - PropertyInfo[] entityProperties, - RelationshipAttribute attr, - ContextEntity contextEntity, - Dictionary relationships) - { - var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName); + private object SetHasManyRelationship(object entity, + PropertyInfo[] entityProperties, + RelationshipAttribute attr, + ContextEntity contextEntity, + Dictionary relationships) + { + var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName); - if (entityProperty == null) - throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); + if (entityProperty == null) + throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}"); - var relationshipName = attr.PublicRelationshipName; + var relationshipName = attr.PublicRelationshipName; - if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) - { - var data = (List>)relationshipData.ExposedData; + if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData)) + { + var data = (List>)relationshipData.ExposedData; if (data == null) return entity; - var genericProcessor = _genericProcessorFactory.GetProcessor(attr.Type); - var ids = relationshipData.ManyData.Select(r => r["id"]); - genericProcessor.SetRelationships(entity, attr, ids); - } + var genericProcessor = _genericProcessorFactory.GetProcessor(attr.Type); + var ids = relationshipData.ManyData.Select(r => r["id"]); + genericProcessor.SetRelationships(entity, attr, ids); + } - return entity; - } - } + return entity; + } + } } \ No newline at end of file diff --git a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs index 632fb50ec5..8e94835266 100644 --- a/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs +++ b/src/JsonApiDotNetCore/Serialization/JsonApiSerializer.cs @@ -8,82 +8,82 @@ namespace JsonApiDotNetCore.Serialization { - public class JsonApiSerializer : IJsonApiSerializer - { - private readonly IDocumentBuilder _documentBuilder; - private readonly ILogger _logger; + public class JsonApiSerializer : IJsonApiSerializer + { + private readonly IDocumentBuilder _documentBuilder; + private readonly ILogger _logger; private readonly IJsonApiContext _jsonApiContext; - public JsonApiSerializer( - IJsonApiContext jsonApiContext, - IDocumentBuilder documentBuilder) - { - _jsonApiContext = jsonApiContext; - _documentBuilder = documentBuilder; - } + public JsonApiSerializer( + IJsonApiContext jsonApiContext, + IDocumentBuilder documentBuilder) + { + _jsonApiContext = jsonApiContext; + _documentBuilder = documentBuilder; + } - public JsonApiSerializer( - IJsonApiContext jsonApiContext, - IDocumentBuilder documentBuilder, - ILoggerFactory loggerFactory) - { - _jsonApiContext = jsonApiContext; - _documentBuilder = documentBuilder; - _logger = loggerFactory?.CreateLogger(); - } + public JsonApiSerializer( + IJsonApiContext jsonApiContext, + IDocumentBuilder documentBuilder, + ILoggerFactory loggerFactory) + { + _jsonApiContext = jsonApiContext; + _documentBuilder = documentBuilder; + _logger = loggerFactory?.CreateLogger(); + } - public string Serialize(object entity) - { - if (entity == null) - return GetNullDataResponse(); + public string Serialize(object entity) + { + if (entity == null) + return GetNullDataResponse(); - if (entity.GetType() == typeof(ErrorCollection) || _jsonApiContext.RequestEntity == null) - return GetErrorJson(entity, _logger); + if (entity.GetType() == typeof(ErrorCollection) || _jsonApiContext.RequestEntity == null) + return GetErrorJson(entity, _logger); - if (entity is IEnumerable) - return SerializeDocuments(entity); + if (entity is IEnumerable) + return SerializeDocuments(entity); - return SerializeDocument(entity); - } + return SerializeDocument(entity); + } - private string GetNullDataResponse() - { - return JsonConvert.SerializeObject(new Document - { - Data = null - }); - } + private string GetNullDataResponse() + { + return JsonConvert.SerializeObject(new Document + { + Data = null + }); + } - private string GetErrorJson(object responseObject, ILogger logger) - { - if (responseObject is ErrorCollection errorCollection) - { - return errorCollection.GetJson(); - } - else - { - logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); - return JsonConvert.SerializeObject(responseObject); - } - } + private string GetErrorJson(object responseObject, ILogger logger) + { + if (responseObject is ErrorCollection errorCollection) + { + return errorCollection.GetJson(); + } + else + { + logger?.LogInformation("Response was not a JSONAPI entity. Serializing as plain JSON."); + return JsonConvert.SerializeObject(responseObject); + } + } - private string SerializeDocuments(object entity) - { - var entities = entity as IEnumerable; - var documents = _documentBuilder.Build(entities); - return _serialize(documents); - } + private string SerializeDocuments(object entity) + { + var entities = entity as IEnumerable; + var documents = _documentBuilder.Build(entities); + return _serialize(documents); + } - private string SerializeDocument(object entity) - { - var identifiableEntity = entity as IIdentifiable; - var document = _documentBuilder.Build(identifiableEntity); - return _serialize(document); - } + private string SerializeDocument(object entity) + { + var identifiableEntity = entity as IIdentifiable; + var document = _documentBuilder.Build(identifiableEntity); + return _serialize(document); + } - private string _serialize(object obj) - { - return JsonConvert.SerializeObject(obj, _jsonApiContext.Options.SerializerSettings); - } - } + private string _serialize(object obj) + { + return JsonConvert.SerializeObject(obj, _jsonApiContext.Options.SerializerSettings); + } + } } \ No newline at end of file diff --git a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs index e6bad39a0c..3e54c7b393 100644 --- a/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiDeSerializerTests.cs @@ -12,208 +12,208 @@ namespace UnitTests.Serialization { - public class JsonApiDeSerializerTests - { - [Fact] - public void Can_Deserialize_Complex_Types() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-member", new { compoundName = "testName" } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal("testName", result.ComplexMember.CompoundName); - } - - [Fact] - public void Can_Deserialize_Complex_List_Types() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-members", new [] { - new { compoundName = "testName" } - } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMembers); - Assert.NotEmpty(result.ComplexMembers); - Assert.Equal("testName", result.ComplexMembers[0].CompoundName); - } - - [Fact] - public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { - "complex-member", new Dictionary { { "compound-name", "testName" } } - } - } - } - }; - - // act - var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal("testName", result.ComplexMember.CompoundName); - } - - [Fact] - public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); - - var attributesToUpdate = new Dictionary(); - - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); - - var jsonApiOptions = new JsonApiOptions(); - jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); - jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); - - var genericProcessorFactoryMock = new Mock(); - - var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); - - var content = new Document - { - Data = new DocumentData - { - Type = "test-resource", - Id = "1", - Attributes = new Dictionary { - { "complex-member", new Dictionary { - { "compound-name", "testName" } } - }, - { "immutable", "value"} - } - } - }; - - var contentString = JsonConvert.SerializeObject(content); - - // act - var result = deserializer.Deserialize(contentString); - - // assert - Assert.NotNull(result.ComplexMember); - Assert.Equal(1, attributesToUpdate.Count); - - foreach (var attr in attributesToUpdate) - Assert.False(attr.Key.IsImmutable); - } - - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - - [Attr("immutable", isImmutable: true)] - public string Immutable { get; set; } - } - - private class TestResourceWithList : Identifiable - { - [Attr("complex-members")] - public List ComplexMembers { get; set; } - } - - private class ComplexType - { - public string CompoundName { get; set; } - } - } + public class JsonApiDeSerializerTests + { + [Fact] + public void Can_Deserialize_Complex_Types() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { + "complex-member", new { compoundName = "testName" } + } + } + } + }; + + // act + var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); + + // assert + Assert.NotNull(result.ComplexMember); + Assert.Equal("testName", result.ComplexMember.CompoundName); + } + + [Fact] + public void Can_Deserialize_Complex_List_Types() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { + "complex-members", new [] { + new { compoundName = "testName" } + } + } + } + } + }; + + // act + var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); + + // assert + Assert.NotNull(result.ComplexMembers); + Assert.NotEmpty(result.ComplexMembers); + Assert.Equal("testName", result.ComplexMembers[0].CompoundName); + } + + [Fact] + public void Can_Deserialize_Complex_Types_With_Dasherized_Attrs() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary()); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); // <-- + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { + "complex-member", new Dictionary { { "compound-name", "testName" } } + } + } + } + }; + + // act + var result = deserializer.Deserialize(JsonConvert.SerializeObject(content)); + + // assert + Assert.NotNull(result.ComplexMember); + Assert.Equal("testName", result.ComplexMember.CompoundName); + } + + [Fact] + public void Immutable_Attrs_Are_Not_Included_In_AttributesToUpdate() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); + + var attributesToUpdate = new Dictionary(); + + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(attributesToUpdate); + + var jsonApiOptions = new JsonApiOptions(); + jsonApiOptions.SerializerSettings.ContractResolver = new DasherizedResolver(); + jsonApiContextMock.Setup(m => m.Options).Returns(jsonApiOptions); + + var genericProcessorFactoryMock = new Mock(); + + var deserializer = new JsonApiDeSerializer(jsonApiContextMock.Object, genericProcessorFactoryMock.Object); + + var content = new Document + { + Data = new DocumentData + { + Type = "test-resource", + Id = "1", + Attributes = new Dictionary { + { "complex-member", new Dictionary { + { "compound-name", "testName" } } + }, + { "immutable", "value"} + } + } + }; + + var contentString = JsonConvert.SerializeObject(content); + + // act + var result = deserializer.Deserialize(contentString); + + // assert + Assert.NotNull(result.ComplexMember); + Assert.Equal(1, attributesToUpdate.Count); + + foreach (var attr in attributesToUpdate) + Assert.False(attr.Key.IsImmutable); + } + + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + + [Attr("immutable", isImmutable: true)] + public string Immutable { get; set; } + } + + private class TestResourceWithList : Identifiable + { + [Attr("complex-members")] + public List ComplexMembers { get; set; } + } + + private class ComplexType + { + public string CompoundName { get; set; } + } + } } diff --git a/test/UnitTests/Serialization/JsonApiSerializerTests.cs b/test/UnitTests/Serialization/JsonApiSerializerTests.cs index 0193a2880a..3630cd6452 100644 --- a/test/UnitTests/Serialization/JsonApiSerializerTests.cs +++ b/test/UnitTests/Serialization/JsonApiSerializerTests.cs @@ -13,52 +13,52 @@ namespace UnitTests.Serialization { - public class JsonApiSerializerTests - { - [Fact] - public void Can_Serialize_Complex_Types() - { - // arrange - var contextGraphBuilder = new ContextGraphBuilder(); - contextGraphBuilder.AddResource("test-resource"); - var contextGraph = contextGraphBuilder.Build(); + public class JsonApiSerializerTests + { + [Fact] + public void Can_Serialize_Complex_Types() + { + // arrange + var contextGraphBuilder = new ContextGraphBuilder(); + contextGraphBuilder.AddResource("test-resource"); + var contextGraph = contextGraphBuilder.Build(); - var jsonApiContextMock = new Mock(); - jsonApiContextMock.SetupAllProperties(); - jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); - jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); - jsonApiContextMock.Setup(m => m.RequestEntity) - .Returns(contextGraph.GetContextEntity("test-resource")); - jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); - jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager()); + var jsonApiContextMock = new Mock(); + jsonApiContextMock.SetupAllProperties(); + jsonApiContextMock.Setup(m => m.ContextGraph).Returns(contextGraph); + jsonApiContextMock.Setup(m => m.Options).Returns(new JsonApiOptions()); + jsonApiContextMock.Setup(m => m.RequestEntity) + .Returns(contextGraph.GetContextEntity("test-resource")); + jsonApiContextMock.Setup(m => m.MetaBuilder).Returns(new MetaBuilder()); + jsonApiContextMock.Setup(m => m.PageManager).Returns(new PageManager()); - var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); - var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); - var resource = new TestResource - { - ComplexMember = new ComplexType - { - CompoundName = "testname" - } - }; + var documentBuilder = new DocumentBuilder(jsonApiContextMock.Object); + var serializer = new JsonApiSerializer(jsonApiContextMock.Object, documentBuilder); + var resource = new TestResource + { + ComplexMember = new ComplexType + { + CompoundName = "testname" + } + }; - // act - var result = serializer.Serialize(resource); + // act + var result = serializer.Serialize(resource); - // assert - Assert.NotNull(result); - Assert.Equal("{\"data\":{\"type\":\"test-resource\",\"id\":\"\",\"attributes\":{\"complex-member\":{\"compound-name\":\"testname\"}}}}", result); - } + // assert + Assert.NotNull(result); + Assert.Equal("{\"data\":{\"type\":\"test-resource\",\"id\":\"\",\"attributes\":{\"complex-member\":{\"compound-name\":\"testname\"}}}}", result); + } - private class TestResource : Identifiable - { - [Attr("complex-member")] - public ComplexType ComplexMember { get; set; } - } + private class TestResource : Identifiable + { + [Attr("complex-member")] + public ComplexType ComplexMember { get; set; } + } - private class ComplexType - { - public string CompoundName { get; set; } - } - } + private class ComplexType + { + public string CompoundName { get; set; } + } + } }