Exemple #1
0
        internal static IncludePathNode GenerateIncludeTree(JsonApiResource apiResource, string path)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                return(null);
            }
            var subPaths = path.Split(new[] { '.' }, 2);

            var relationshipName     = subPaths[0].ToJsonRelationshipName();
            var relationshipResource = apiResource.Relationships.FirstOrDefault(x => x.Name == relationshipName);

            if (relationshipResource == null)
            {
                throw new Exception($"Cannot include resource {path}: Path does not exist.");
            }

            var resultNode = new IncludePathNode
            {
                PropertyPath = relationshipName,
                IncludeApiResourceRelationship = relationshipResource,
                Child = subPaths.Length > 1
                    ? GenerateIncludeTree(relationshipResource.RelatedResource, subPaths[1])
                    : null
            };

            return(resultNode);
        }
Exemple #2
0
        public dynamic ToObject(JsonApiResource jsonApiResource)
        {
            if (jsonApiResource == null)
            {
                return(null);
            }

            var typeResolver = _settings.TypeResolver;
            var resolvedType = typeResolver.ResolveType(jsonApiResource.Type);

            if (resolvedType == null)
            {
                throw new JsonApiTypeNotFoundException(string.Format("No type found for {0}", jsonApiResource.Type));
            }

            var resource = (dynamic)Activator.CreateInstance(resolvedType);

            var propResolver = _settings.PropertyResolver;

            var idProperty = propResolver.ResolveJsonApiId(resolvedType);

            SetParseableStringProperty(resource, idProperty, jsonApiResource.Id);

            var typeProperty = propResolver.ResolveJsonApiType(resolvedType);

            SetProperty(resource, typeProperty, jsonApiResource.Type);

            MapAttributes(resource, jsonApiResource);
            MapRelationships(resource, jsonApiResource);

            return(resource);
        }
Exemple #3
0
        private async Task WriteJsonApiOutputAsync([NotNull] HttpResponse context,
                                                   [NotNull] Encoding selectedEncoding,
                                                   [CanBeNull] object modelData,
                                                   [NotNull] Type modelDataType,
                                                   [NotNull] JsonApiResource modelResource,
                                                   [CanBeNull] QueryInfo queryInfo)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (selectedEncoding == null)
            {
                throw new ArgumentNullException(nameof(selectedEncoding));
            }
            if (modelDataType == null)
            {
                throw new ArgumentNullException(nameof(modelDataType));
            }
            if (modelResource == null)
            {
                throw new ArgumentNullException(nameof(modelResource));
            }

            var serializer = JsonSerializer.Create(JsonSerializerSettings);

            using (var streamWriter = new StreamWriter(context.Body, selectedEncoding))
            {
                serializer.Serialize(streamWriter, modelData, modelDataType);
                await streamWriter.FlushAsync();
            }
        }
Exemple #4
0
        public static void IncludeRelation(this IJsonApiDocument document, JsonApiResource dataApiResource, object data, string path, string baseUrl = null)
        {
            // TODO: include relations for collections

            // parse paths
            var subPaths = path.Split(',');

            if (data is IEnumerable <object> collection)
            {
                foreach (var item in collection)
                {
                    foreach (var includePath in subPaths)
                    {
                        // generate tree
                        var includePathTree = GenerateIncludeTree(dataApiResource, includePath);
                        // process tree
                        ProcessIncludeTree(document, includePathTree, item, baseUrl);
                    }
                }
            }
            else
            {
                foreach (var includePath in subPaths)
                {
                    // generate tree
                    var includePathTree = GenerateIncludeTree(dataApiResource, includePath);
                    // process tree
                    ProcessIncludeTree(document, includePathTree, data, baseUrl);
                }
            }
        }
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var resourceToken = JToken.Load(reader);

            if (resourceToken == null)
            {
                return(null);
            }

            if (resourceToken.Type != JTokenType.Object)
            {
                throw new JsonApiFormatException("Individual resource element must be an Object");
            }

            var resourceObj = (JObject)resourceToken;

            var jsonApiResource = new JsonApiResource((string)resourceObj["type"], (string)resourceObj["id"]);

            jsonApiResource.Attributes    = ReadAttributes(resourceObj["attributes"], jsonApiResource, serializer);
            jsonApiResource.Relationships = ReadProperty <JsonApiRelationships>(resourceObj, "relationships", serializer);
            jsonApiResource.Links         = ReadProperty <JsonApiLinks>(resourceObj, "links", serializer);
            jsonApiResource.Meta          = ReadProperty <JsonApiMeta>(resourceObj, "meta", serializer);

            return(jsonApiResource);
        }
Exemple #6
0
 /// <summary>
 /// Returns a path in the form `/resource.UrlPath/id/`.
 /// </summary>
 /// <param name="resource">The resource this path refers to.</param>
 /// <param name="id">The unique id of the resource.</param>
 /// <returns>A <see cref="string"/> containing the path.</returns>
 public virtual string BuildCanonicalPath(JsonApiResource resource, string id)
 {
     return('/'.TrimJoin(
                BuildCanonicalPath(resource), id)
            .EnsureStartsWith("/")
            .EnsureEndsWith("/"));
 }
Exemple #7
0
 /// <summary>
 /// Returns a path in the form `/resource.UrlPath/id/relationships/relationship.UrlPath/`.
 /// </summary>
 /// <param name="resource">The resource this path is related to.</param>
 /// <param name="id">The unique id of the resource.</param>
 /// <param name="relationship">The relationship this path refers to.</param>
 /// <returns>A <see cref="string"/> containing the path.</returns>
 public virtual string BuildRelationshipPath(JsonApiResource resource, string id, ResourceRelationship relationship)
 {
     return('/'.TrimJoin(
                BuildCanonicalPath(resource, id), "relationships", relationship.UrlPath)
            .EnsureStartsWith("/")
            .EnsureEndsWith("/"));
 }
Exemple #8
0
 public ModelClassMetaData(Type resource, JsonApiResource instance, Type model, bool isDefaultDeserializer, bool isForDataTransferOnly)
 {
     Resource = resource;
     Instance = instance;
     Model    = model;
     IsDefaultDeserializer = isDefaultDeserializer;
     IsForDataTransferOnly = isForDataTransferOnly;
 }
Exemple #9
0
        /// <summary>
        /// Extract primary Data from the JsonApiDocument.
        /// </summary>
        /// <param name="apiResource">instance of JsonApiResource used to extract the Data.</param>
        /// <param name="targetType">The Type of the extracted Data.</param>
        /// <param name="foundAttributes">A function to determine which attributes were found in the JsonDocument's primary data</param>
        /// <returns>An instance containing the model data.</returns>
        /// <example>
        /// <code>
        /// Func&gt;string, bool&lt; foundAttributes;
        /// var collection = jsonDocument.ToObject(Activator.CreateInstance&lt;ModelTypeApiResource&gt;(), typeof(IEnumerable&lt;ModelType&gt;, out foundAttributes));
        /// var singleItem = jsonDocument.ToObject(Activator.CreateInstance&lt;ModelTypeApiResource&gt;(), typeof(ModelType), out foundAttributes);
        /// </code>
        /// </example>
        public static object ToObjectInternal(this JsonApiDocument document, JsonApiResource apiResource, Type targetType, out Func <int, string, bool> foundAttributes)
        {
            var attrs     = document.Data.Attributes;
            var relations = document.Data.Relationships;

            foundAttributes = (idx, attrName) => (attrs?.ContainsKey(attrName.ToJsonAttributeName()) ?? false) || (relations?.ContainsKey(attrName.ToRelationshipName(apiResource.GetType())) ?? false);
            return(document.ToObject(apiResource, targetType));
        }
Exemple #10
0
        private void MapRelationships(dynamic resource, JsonApiResource jsonApiResource)
        {
            if (jsonApiResource.Relationships == null)
            {
                return;
            }

            foreach (var kvp in jsonApiResource.Relationships)
            {
                MapRelationship(resource, kvp.Key, kvp.Value);
            }
        }
Exemple #11
0
 /// <summary>
 /// Returns a path in the form `/relatedResource.UrlPath/relatedResource.Id/`.
 /// </summary>
 /// <param name="resource">The resource this path is related to.</param>
 /// <param name="id">The unique id of the resource.</param>
 /// <param name="relationship">The relationship this path refers to.</param>
 /// <param name="relatedResourceId">The id of the related resource.</param>
 /// <returns>A <see cref="string"/> containing the path.</returns>
 public override string BuildRelationshipPath(
     JsonApiResource resource,
     string id,
     ResourceRelationship relationship,
     string relatedResourceId)
 {
     // empty if no id, because e.g. /api/people != /api/companies/1/employees
     // (all people is not the same as all employees for a company)
     return(string.IsNullOrEmpty(relatedResourceId)
         ? null
         : BuildCanonicalPath(relationship.RelatedResource, relatedResourceId));
 }
Exemple #12
0
        private void MapAttributes(dynamic resource, JsonApiResource jsonApiResource)
        {
            if (jsonApiResource.Attributes == null)
            {
                return;
            }

            foreach (var kvp in jsonApiResource.Attributes)
            {
                var attrProperty = _settings.PropertyResolver.ResolveJsonApiAttribute(((object)resource).GetType(), kvp.Key);
                SetProperty(resource, attrProperty, kvp.Value);
            }
        }
Exemple #13
0
        public static object ToObject(this IJsonApiDocument document, JsonApiResource apiResource, Type targetType, out Func <int, string, bool> foundAttributes)
        {
            switch (document)
            {
            case JsonApiDocument doc:
                return(doc.ToObjectInternal(apiResource, targetType, out foundAttributes));

            case JsonApiCollectionDocument collDoc:
                return(collDoc.ToObjectInternal(apiResource, targetType, out foundAttributes));

            default:
                throw new ArgumentException($"Parameter {nameof(document)} does not have a supported type. (Type={document?.GetType()})");
            }
        }
Exemple #14
0
        /// <summary>
        /// Extracts apiResource to an <see cref="IEnumerable{T}"/> of type targetType.
        /// </summary>
        /// <param name="document"></param>
        /// <param name="apiResource"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        public static object ToObjectCollection(this JsonApiDocument document, JsonApiResource apiResource, Type targetType)
        {
            if (targetType.IsNonStringEnumerable())
            {
                throw new Exception("Do not use a collection as target type!");
            }
            var primaryResourceObject = document.Data;

            if (primaryResourceObject == null)
            {
                throw new Exception("Json document contains no data.");
            }

            var method = typeof(JsonApiDocumentExtensions).GetMethod(nameof(Cast), BindingFlags.Static | BindingFlags.NonPublic).MakeGenericMethod(targetType);

            return(method.Invoke(null, new object[] { primaryResourceObject, apiResource }));
        }
Exemple #15
0
        /// <summary>
        /// Extract primary Data from the JsonApiDocument.
        /// </summary>
        /// <param name="apiResource">instance of JsonApiResource used to extract the Data.</param>
        /// <param name="targetType">The Type of the extracted Data.</param>
        /// <returns>An instance containing the model data.</returns>
        /// <example>
        /// <code>
        /// var collection = jsonDocument.ToObject(Activator.CreateInstance&lt;ModelTypeApiResource&gt;(), typeof(IEnumerable&lt;ModelType&gt;));
        /// var singleItem = jsonDocument.ToObject(Activator.CreateInstance&lt;ModelTypeApiResource&gt;(), typeof(ModelType));
        /// </code>
        /// </example>
        public static object ToObjectInternal(this JsonApiDocument document, JsonApiResource apiResource, Type targetType)
        {
            if (targetType.IsNonStringEnumerable())
            {
#if NET40
                var innerType = targetType.GetGenericArguments()[0];
#else
                var innerType = targetType.GenericTypeArguments[0];
#endif
                return(document.ToObjectCollection(apiResource, innerType));
            }
            var primaryResourceObject = document.Data;
            if (primaryResourceObject == null)
            {
                throw new Exception("Json document contains no data.");
            }

            // extract primary data
            return(primaryResourceObject.ToObject(apiResource, targetType));
        }
        private JsonApiAttributes ReadAttributes(JToken attributesToken, JsonApiResource resource, JsonSerializer serializer)
        {
            if (attributesToken == null)
            {
                return(null);
            }

            var resolvedType = _settings.TypeResolver.ResolveType(resource.Type);

            var attributesObject = (JObject)attributesToken;

            var obj = new JsonApiAttributes();

            foreach (var attributeProperty in attributesObject.Properties())
            {
                if (resolvedType != null)
                {
                    var propertyInfo = _settings.PropertyResolver.ResolveJsonApiAttribute(resolvedType, attributeProperty.Name);

                    if (propertyInfo != null)
                    {
                        obj[attributeProperty.Name] = attributeProperty.Value.ToObject(propertyInfo.PropertyType, serializer);
                    }
                    else
                    {
                        obj[attributeProperty.Name] = attributeProperty.Value.ToObject(typeof(object));
                    }
                }
                else
                {
                    obj[attributeProperty.Name] = attributeProperty.Value.ToObject(typeof(object));
                }
            }

            return(obj);
        }
Exemple #17
0
 internal static object ToObjectInternal(this JsonApiCollectionDocument document, JsonApiResource apiResource, Type targetType, out Func <int, string, bool> foundAttributes)
 {
     foundAttributes = (idx, attrName) => (document.Data.ElementAt(idx)?.Attributes?.ContainsKey(attrName.ToJsonAttributeName()) ?? false) || (document.Data.ElementAt(idx)?.Relationships?.ContainsKey(attrName.ToRelationshipName(apiResource.GetType())) ?? false);
     return(document.ToObjectInternal(apiResource, targetType));
 }
Exemple #18
0
 /// <summary>
 /// Returns the UrlPath of the resource, ensuring it starts and ends with '/'
 /// </summary>
 /// <param name="resource">The resource this path refers to.</param>
 /// <returns>A <see cref="string"/> containing the path.</returns>
 public virtual string BuildCanonicalPath(JsonApiResource resource)
 {
     return('/'.TrimJoin(_prefix, resource.UrlPath)
            .EnsureStartsWith("/")
            .EnsureEndsWith("/"));
 }
Exemple #19
0
 public static object ToObjectCollection(this JsonApiDocument document, JsonApiResource apiResource, Type targetType, out Func <string, bool> foundAttributes)
 {
     foundAttributes = (attrName) => (document.Data.Attributes?.ContainsKey(attrName.ToJsonAttributeName()) ?? false) || (document.Data.Relationships?.ContainsKey(attrName.ToRelationshipName(apiResource.GetType())) ?? false);
     return(document.ToObjectCollection(apiResource, targetType));
 }
        private dynamic CreateResource(JsonApiDocument document, JsonApiResource jsonApiResource)
        {
            var mapper = new ResourceMapper(document, _settings);

            return(mapper.ToObject(jsonApiResource));
        }
Exemple #21
0
 internal static IEnumerable <T> Cast <T>(JsonApiResourceObject data, JsonApiResource apiResource)
 {
     return(new List <T> {
         (T)data.ToObject(apiResource, typeof(T))
     });
 }
Exemple #22
0
        public static void FromApiResource(this JsonApiDocument document, object data, JsonApiResource apiResource, string baseUrl = null)
        {
            switch (data)
            {
            case null:
                return;

            case IEnumerable <object> _:
                throw new Exception("data cannot be a collection");
            }

            var rObject = new JsonApiResourceObject();

            rObject.FromApiResource(data, apiResource, baseUrl);
            document.Data = rObject;
            if (!string.IsNullOrWhiteSpace(baseUrl))
            {
                document.Links = new JsonApiLinksObject
                {
                    Self = new JsonApiLink
                    {
                        Href = $"{baseUrl}{apiResource.UrlPath}/{rObject.Id}"
                    }
                };
            }
        }
        private async Task WriteJsonOutputAsync(
            [NotNull] HttpResponse context,
            [NotNull] Encoding selectedEncoding,
            [CanBeNull] object modelData,
            [NotNull] Type modelDataType,
            [NotNull] JsonApiResource modelResource,
            [CanBeNull] QueryInfo queryInfo)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (selectedEncoding == null)
            {
                throw new ArgumentNullException(nameof(selectedEncoding));
            }
            if (modelDataType == null)
            {
                throw new ArgumentNullException(nameof(modelDataType));
            }
            if (modelResource == null)
            {
                throw new ArgumentNullException(nameof(modelResource));
            }

            var meta = new JsonApiMetaData();

            if (modelData is IQueryResult queryResult)
            {
                meta.PageCount   = queryResult.PageCount;
                meta.RecordCount = queryResult.RecordCount;
                modelData        = queryResult.Data;
            }

            IJsonApiDocument document;

            if (modelData is IEnumerable collectionValue)
            {
                var apiCollectionDocument = new JsonApiCollectionDocument();
                apiCollectionDocument.FromApiResource(collectionValue, modelResource);
                document = apiCollectionDocument;
            }
            else
            {
                var apiDocument = new JsonApiDocument();
                apiDocument.FromApiResource(modelData, modelResource);
                document = apiDocument;
            }

            document.Meta = meta;

            if (queryInfo?.Includes != null)
            {
                foreach (var include in queryInfo.Includes)
                {
                    document.IncludeRelation(modelResource, modelData, include);
                }
            }

            var serializer = JsonSerializer.Create(JsonSerializerSettings);

            using (var streamWriter = new StreamWriter(context.Body, selectedEncoding))
            {
                serializer.Serialize(streamWriter, document, document.GetType());
                await streamWriter.FlushAsync();
            }
        }
Exemple #24
0
        /// <summary>
        /// Attempts to instatiate an object of type <paramref name="targetType"/> with data from <paramref name="resourceObject"/> using the <paramref name="apiResource"/>.
        /// </summary>
        /// <param name="resourceObject"></param>
        /// <param name="apiResource"></param>
        /// <param name="targetType"></param>
        /// <returns></returns>
        public static object ToObject(this JsonApiResourceObject resourceObject, JsonApiResource apiResource, Type targetType)
        {
            var result = Activator.CreateInstance(targetType);

            // extract id
            if (!string.IsNullOrWhiteSpace(resourceObject.Id))
            {
                var idProp = targetType.GetProperty(apiResource.IdProperty);
                if (idProp != null)
                {
                    var idObject = BtbrdCoreIdConverters.ConvertFromString(resourceObject.Id, idProp.PropertyType);
                    idProp.SetValueFast(result, idObject);
                }
            }

            // TODO: better iterate over attributes and relations defined in the apiresource

            // extract attributes
            if (resourceObject.Attributes != null)
            {
                foreach (var attribute in resourceObject.Attributes)
                {
                    var resourceAttribute = apiResource.Attributes.Where(a => a.Name == attribute.Key).FirstOrDefault();
                    if (resourceAttribute == null)
                    {
                        continue;
                    }

                    var targetProperty = targetType.GetProperty(resourceAttribute.PropertyName);
                    if (targetProperty == null)
                    {
                        continue;
                    }

                    // Not handled: ienumerables of datetime,datetimeoffset and nullables

                    var underlying = Nullable.GetUnderlyingType(targetProperty.PropertyType) ?? targetProperty.PropertyType;

                    var value = attribute.Value;
                    if (value != null)
                    {
                        if (underlying == typeof(DateTime))
                        {
                            value = DateTime.Parse(value.ToString());
                        }
                        else if (underlying == typeof(DateTimeOffset))
                        {
                            value = DateTimeOffset.Parse(value.ToString());
                        }
                        else if (underlying.IsEnum)
                        {
                            value = Enum.ToObject(underlying, Convert.ChangeType(value, Enum.GetUnderlyingType(underlying)));
                        }
                        else
                        {
                            value = Convert.ChangeType(value, underlying);
                        }
                    }

                    targetProperty.SetValueFast(result, value);
                }
            }

            // extract relationships
            if (resourceObject.Relationships != null)
            {
                foreach (var relationship in resourceObject.Relationships)
                {
                    var relationResource = apiResource.Relationships.Where(r => r.Name == relationship.Key).FirstOrDefault();
                    if (relationResource == null)
                    {
                        continue;
                    }
                    if (relationResource.Kind == RelationshipKind.BelongsTo)
                    {
                        var relationshipObject = relationship.Value as JsonApiToOneRelationshipObject;
                        if (!string.IsNullOrWhiteSpace(relationshipObject.Data?.Id))
                        {
                            var idProp = targetType.GetProperty(relationResource.IdPropertyName);
                            if (idProp != null)
                            {
                                var idObject = BtbrdCoreIdConverters.ConvertFromString(relationshipObject.Data.Id, idProp.PropertyType);
                                idProp.SetValueFast(result, idObject);
                            }
                        }
                    }
                    else
                    {
                        var idProp = targetType.GetProperty(relationResource.IdPropertyName)
                                     ?? throw new Exception($"{nameof(JsonApiResourceObjectExtensions)}: Could not find relation property {relationResource.IdPropertyName}");

                        // get type of the id (e.g. long, string, ..)
                        Type innerType;
                        if (idProp.PropertyType.IsArray)
                        {
                            innerType = idProp.PropertyType.GetElementType();
                        }
                        else if (idProp.PropertyType.IsNonStringEnumerable())
#if (NET40)
                        { innerType = idProp.PropertyType.GetGenericArguments()[0]; }
#else
                        { innerType = idProp.PropertyType.GenericTypeArguments[0]; }
#endif
                        else
                        {
                            throw new Exception($"{nameof(JsonApiResourceObjectExtensions)}: Trying to read the relation, could not find element-type of type {idProp.PropertyType.FullName}.");
                        }

                        if (!(relationship.Value is JsonApiToManyRelationshipObject relationshipObject))
                        {
                            throw new Exception($"{nameof(JsonApiResourceObjectExtensions)}: Expected a {nameof(JsonApiToManyRelationshipObject)}, found {relationship.Value?.GetType().FullName ?? "null"}");
                        }

                        // create List instance
                        //   get the below defined method GetIdCollection for T=innerType
                        //   and executes it.
                        var instance = (typeof(JsonApiResourceObjectExtensions)
                                        .GetMethod(nameof(GetIdCollection))
                                        ?.MakeGenericMethod(innerType) ?? throw new Exception($"{nameof(JsonApiResourceObjectExtensions)}: Method {nameof(GetIdCollection)} not found."))
                                       .Invoke(null, new object[]
                        {
                            /* IEnumerable<JsonApiResourceIdentifierObject> ids : */ relationshipObject.Data,
                            /* bool makeArray : */ idProp.PropertyType.IsArray
                        });

                        idProp.SetValueFast(result, instance);
                    }
Exemple #25
0
 public static TResult GetIncludedResource <TResult>(this JsonApiDocument document, object id, JsonApiResource apiResource) where TResult : class
 {
     return((TResult)document.GetIncludedResource(id, typeof(TResult), apiResource));
 }
Exemple #26
0
 internal static IEnumerable <T> Cast <T>(IEnumerable <JsonApiResourceObject> data, JsonApiResource apiResource)
 {
     return(data.Select(r => (T)r.ToObject(apiResource, typeof(T))).ToList());
 }
Exemple #27
0
        public static void FromApiResource(this JsonApiCollectionDocument document, IEnumerable data, JsonApiResource apiResource, string baseUrl = null)
        {
            if (data == null)
            {
                return;
            }
            var resourceObjects = new List <JsonApiResourceObject>();

            foreach (var item in data)
            {
                var rObject = new JsonApiResourceObject();
                rObject.FromApiResource(item, apiResource, baseUrl);
                resourceObjects.Add(rObject);
            }
            document.Data = resourceObjects;
            if (!string.IsNullOrWhiteSpace(baseUrl))
            {
                document.Links = new JsonApiLinksObject
                {
                    Self = new JsonApiLink
                    {
                        Href = $"{baseUrl}{apiResource.UrlPath}"
                    }
                };
            }
        }
 public static JsonApiResourceObject GetResource(this JsonApiResourceObjectDictionary resourceDictionary, object id, JsonApiResource apiResource)
 {
     return(resourceDictionary.GetResource(BtbrdCoreIdConverters.ConvertToString(id), apiResource.ResourceType));
 }
Exemple #29
0
 public static object GetIncludedResource(this JsonApiDocument document, object id, Type type, JsonApiResource apiResource)
 {
     return(document.Included?.GetResource(id, apiResource)?.ToObject(apiResource, type));
 }
Exemple #30
0
 internal static void FromApiResource(this JsonApiResourceObject resourceObject, object data, JsonApiResource apiResource, string baseUrl)
 {
     resourceObject.SetIdAndType(data, apiResource);
     if (apiResource?.Attributes != null)
     {
         foreach (var attr in apiResource.Attributes)
         {
             resourceObject.AddAttribute(data, attr);
         }
     }
     if (apiResource?.Relationships != null)
     {
         foreach (var realtionship in apiResource.Relationships)
         {
             if (realtionship.Kind == RelationshipKind.BelongsTo)
             {
                 resourceObject.AddToOneRelationship(data, apiResource, realtionship, baseUrl);
             }
             else
             {
                 resourceObject.AddToManyRelationship(data, apiResource, realtionship, baseUrl);
             }
         }
     }
     if (!string.IsNullOrWhiteSpace(baseUrl))
     {
         resourceObject.Links = new JsonApiLinksObject
         {
             Self = new JsonApiLink
             {
                 Href = $"{baseUrl}{apiResource.UrlPath}/{resourceObject.Id}"
             }
         };
     }
 }