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); }
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); }
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(); } }
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); }
/// <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("/")); }
/// <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("/")); }
public ModelClassMetaData(Type resource, JsonApiResource instance, Type model, bool isDefaultDeserializer, bool isForDataTransferOnly) { Resource = resource; Instance = instance; Model = model; IsDefaultDeserializer = isDefaultDeserializer; IsForDataTransferOnly = isForDataTransferOnly; }
/// <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>string, bool< foundAttributes; /// var collection = jsonDocument.ToObject(Activator.CreateInstance<ModelTypeApiResource>(), typeof(IEnumerable<ModelType>, out foundAttributes)); /// var singleItem = jsonDocument.ToObject(Activator.CreateInstance<ModelTypeApiResource>(), 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)); }
private void MapRelationships(dynamic resource, JsonApiResource jsonApiResource) { if (jsonApiResource.Relationships == null) { return; } foreach (var kvp in jsonApiResource.Relationships) { MapRelationship(resource, kvp.Key, kvp.Value); } }
/// <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)); }
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); } }
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()})"); } }
/// <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 })); }
/// <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<ModelTypeApiResource>(), typeof(IEnumerable<ModelType>)); /// var singleItem = jsonDocument.ToObject(Activator.CreateInstance<ModelTypeApiResource>(), 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); }
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)); }
/// <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("/")); }
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)); }
internal static IEnumerable <T> Cast <T>(JsonApiResourceObject data, JsonApiResource apiResource) { return(new List <T> { (T)data.ToObject(apiResource, typeof(T)) }); }
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(); } }
/// <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); }
public static TResult GetIncludedResource <TResult>(this JsonApiDocument document, object id, JsonApiResource apiResource) where TResult : class { return((TResult)document.GetIncludedResource(id, typeof(TResult), apiResource)); }
internal static IEnumerable <T> Cast <T>(IEnumerable <JsonApiResourceObject> data, JsonApiResource apiResource) { return(data.Select(r => (T)r.ToObject(apiResource, typeof(T))).ToList()); }
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)); }
public static object GetIncludedResource(this JsonApiDocument document, object id, Type type, JsonApiResource apiResource) { return(document.Included?.GetResource(id, apiResource)?.ToObject(apiResource, type)); }
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}" } }; } }