diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index d8d38390d8..ac7e1b3ade 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using JsonApiDotNetCore.Extensions; using JsonApiDotNetCore.Internal; using JsonApiDotNetCore.Models; using JsonApiDotNetCore.Services; @@ -46,9 +47,7 @@ public Document Build(IIdentifiable entity) public Documents Build(IEnumerable entities) { - var entityType = entities - .GetType() - .GenericTypeArguments[0]; + var entityType = entities.GetElementType(); var contextEntity = _contextGraph.GetContextEntity(entityType); @@ -229,7 +228,7 @@ private bool RelationshipIsIncluded(string relationshipName) private List> GetRelationships(IEnumerable entities) { - var objType = entities.GetType().GenericTypeArguments[0]; + var objType = entities.GetElementType(); var typeName = _jsonApiContext.ContextGraph.GetContextEntity(objType); diff --git a/src/JsonApiDotNetCore/Extensions/TypeExtensions.cs b/src/JsonApiDotNetCore/Extensions/TypeExtensions.cs new file mode 100644 index 0000000000..ccc4619966 --- /dev/null +++ b/src/JsonApiDotNetCore/Extensions/TypeExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace JsonApiDotNetCore.Extensions +{ + internal static class TypeExtensions + { + public static Type GetElementType(this IEnumerable enumerable) + { + var enumerableTypes = enumerable.GetType() + .GetInterfaces() + .Where(t => t.IsGenericType == true && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + .ToList(); + + var numberOfEnumerableTypes = enumerableTypes.Count; + + if (numberOfEnumerableTypes == 0) + { + throw new ArgumentException($"{nameof(enumerable)} of type {enumerable.GetType().FullName} does not implement a generic variant of {nameof(IEnumerable)}"); + } + + if (numberOfEnumerableTypes > 1) + { + throw new ArgumentException($"{nameof(enumerable)} of type {enumerable.GetType().FullName} implements more than one generic variant of {nameof(IEnumerable)}:\n" + + $"{string.Join("\n", enumerableTypes.Select(t => t.FullName))}"); + } + + var elementType = enumerableTypes[0].GenericTypeArguments[0]; + + return elementType; + } + } +} diff --git a/test/UnitTests/Builders/DocumentBuilder_Tests.cs b/test/UnitTests/Builders/DocumentBuilder_Tests.cs index cd1227ef52..2cc4e7f7a3 100644 --- a/test/UnitTests/Builders/DocumentBuilder_Tests.cs +++ b/test/UnitTests/Builders/DocumentBuilder_Tests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using JsonApiDotNetCore.Builders; using JsonApiDotNetCore.Configuration; @@ -118,6 +119,28 @@ public void Related_Links_Can_Be_Disabled() Assert.Null(document.Data.Relationships["related-model"].Links); } + [Fact] + public void Build_Can_Build_Arrays() + { + var entities = new[] { new Model() }; + var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + + var documents = documentBuilder.Build(entities); + + Assert.Equal(1, documents.Data.Count); + } + + [Fact] + public void Build_Can_Build_CustomIEnumerables() + { + var entities = new Models(new[] { new Model() }); + var documentBuilder = new DocumentBuilder(_jsonApiContextMock.Object); + + var documents = documentBuilder.Build(entities); + + Assert.Equal(1, documents.Data.Count); + } + private class Model : Identifiable { [HasOne("related-model", Link.None)] @@ -130,5 +153,25 @@ private class RelatedModel : Identifiable [HasMany("models")] public List Models { get; set; } } + + private class Models : IEnumerable + { + private readonly IEnumerable models; + + public Models(IEnumerable models) + { + this.models = models; + } + + public IEnumerator GetEnumerator() + { + return models.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return models.GetEnumerator(); + } + } } }