private void WriteExplicitIdentifierJson(JsonWriter writer, object value, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(writer); var valueType = value.GetType(); var resourceIdentifierContract = (ResourceIdentifierContract)serializer.ContractResolver.ResolveContract(valueType); var resourceObject = resourceIdentifierContract.ResourceObjectProperty.ValueProvider.GetValue(value); if (resourceObject == null) { writer.WriteNull(); return; } var resourceObjectType = resourceObject.GetType(); var resourceObjectContract = (ResourceObjectContract)serializer.ContractResolver.ResolveContract(resourceObjectType); writer.WriteStartObject(); //A "resource identifier object" MUST contain type and id members. //serialize id WriterUtil.ShouldWriteStringProperty(writer, resourceObject, resourceObjectContract.IdProperty, serializer, out string id); writer.WritePropertyName(PropertyNames.Id); writer.WriteValue(id); //serialize type. Will always out put a type WriterUtil.ShouldWriteStringProperty(writer, resourceObject, resourceObjectContract.TypeProperty, serializer, out string type); type = type ?? WriterUtil.CalculateDefaultJsonApiType(resourceObject, serializationData, serializer); writer.WritePropertyName(PropertyNames.Type); writer.WriteValue(type); for (var i = 0; i < resourceIdentifierContract.Properties.Count; i++) { var resourceIdentifierProp = resourceIdentifierContract.Properties[i]; if (resourceIdentifierProp == resourceIdentifierContract.ResourceObjectProperty) { continue; } switch (resourceIdentifierProp.PropertyName) { case PropertyNames.Id: case PropertyNames.Type: break; default: if (WriterUtil.ShouldWriteProperty(value, resourceIdentifierProp, serializer, out object propValue)) { writer.WritePropertyName(resourceIdentifierProp.PropertyName); serializer.Serialize(writer, propValue); } break; } } writer.WriteEndObject(); var reference = new ResourceObjectReference(id, type); serializationData.Included[reference] = resourceObject; }
private void WriteResourceObjectJson(JsonWriter writer, object resourceObject, JsonSerializer serializer) { if (resourceObject == null) { writer.WriteNull(); return; } var serializationData = SerializationData.GetSerializationData(writer); var resourceObjectType = resourceObject.GetType(); var resourceObjectContract = (ResourceObjectContract)serializer.ContractResolver.ResolveContract(resourceObjectType); writer.WriteStartObject(); //A "resource identifier object" MUST contain type and id members. //serialize id WriterUtil.ShouldWriteProperty(resourceObject, resourceObjectContract.IdProperty, serializer, out string id); writer.WritePropertyName(PropertyNames.Id); writer.WriteValue(id); //serialize type. Will always out put a type WriterUtil.ShouldWriteProperty(resourceObject, resourceObjectContract.TypeProperty, serializer, out string type); type = type ?? WriterUtil.CalculateDefaultJsonApiType(resourceObject, serializationData, serializer); writer.WritePropertyName(PropertyNames.Type); writer.WriteValue(type); //we will only write the object to included if there are properties that have have data //that we cant include within the reference var willWriteObjectToIncluded = WriterUtil.ShouldWriteProperty(resourceObject, resourceObjectContract.LinksProperty, serializer, out object _); for (var i = 0; i < resourceObjectContract.Attributes.Length && !willWriteObjectToIncluded; i++) { willWriteObjectToIncluded = WriterUtil.ShouldWriteProperty(resourceObject, resourceObjectContract.Attributes[i], serializer, out object _); } for (var i = 0; i < resourceObjectContract.Relationships.Length && !willWriteObjectToIncluded; i++) { willWriteObjectToIncluded = WriterUtil.ShouldWriteProperty(resourceObject, resourceObjectContract.Relationships[i], serializer, out object _); } // typically we would just write the meta in the included. But if we are not going to // have something in included we will write the meta inline here if (!willWriteObjectToIncluded && WriterUtil.ShouldWriteProperty(resourceObject, resourceObjectContract.MetaProperty, serializer, out object metaVal)) { writer.WritePropertyName(PropertyNames.Meta); serializer.Serialize(writer, metaVal); } writer.WriteEndObject(); if (willWriteObjectToIncluded) { var reference = new ResourceObjectReference(id, type); serializationData.Included[reference] = resourceObject; } }
/// <summary> /// Forks the reader and reads ahead to find the id and type of an object reference /// </summary> /// <param name="reader">The reader.</param> /// <returns></returns> public static ResourceObjectReference ReadAheadToIdentifyObject(ForkableJsonReader reader) { var lookAheadReader = reader.Fork(); var reference = new ResourceObjectReference(); foreach (var propName in ReaderUtil.IterateProperties(lookAheadReader)) { if (propName == PropertyNames.Id) { if (lookAheadReader.TokenType != JsonToken.String) { throw new JsonApiFormatException(lookAheadReader.FullPath, $"Expected to find string at {lookAheadReader.FullPath}", "The value of 'id' MUST be a string"); } reference.Id = (string)lookAheadReader.Value; } else if (propName == PropertyNames.Type) { if (lookAheadReader.TokenType != JsonToken.String) { throw new JsonApiFormatException(lookAheadReader.FullPath, $"Expected to find string at {lookAheadReader.FullPath}", "The value of 'type' MUST be a string"); } reference.Type = (string)lookAheadReader.Value; } if (reference.Id != null && reference.Type != null) { break; } } return(reference); }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(writer); if (!serializationData.HasProcessedDocumentRoot) { //treat this value as a document root DocumentRootConverter.ResolveAsRootData(writer, value, serializer); return; } // if they have custom convertors registered, we will respect them if (WriterUtil.TryUseCustomConvertor(writer, value, serializer, excludeConverter: this)) { return; } var jsonApiContractResolver = (JsonApiContractResolver)serializer.ContractResolver; var valueType = value.GetType(); if (!(serializer.ContractResolver.ResolveContract(valueType) is ResourceObjectContract metadata)) { throw new JsonApiFormatException( writer.Path, $"Expected to find to find resource object, but found '{value}'", "Resource indentifier objects MUST contain 'id' members"); } serializationData.ConverterStack.Push(this); writer.WriteStartObject(); //serialize id string id = null; if (WriterUtil.ShouldWriteProperty(value, metadata.IdProperty, serializer, out object objId)) { //we will allow some non-string properties if it is trival to convert to a string if (!AllowedIdTypes.Contains(metadata.IdProperty.PropertyType)) { throw new JsonApiFormatException( writer.Path, $"Expected Id property to be a string or primitive but found it to be '{objId?.GetType()}'", "The values of the id member MUST be a string"); } id = objId as string ?? objId?.ToString(); writer.WritePropertyName(PropertyNames.Id); writer.WriteValue(id); } //serialize type. Will always out put a type WriterUtil.ShouldWriteProperty <string>(value, metadata.TypeProperty, serializer, out string type); type = type ?? WriterUtil.CalculateDefaultJsonApiType(value, serializationData, serializer); writer.WritePropertyName(PropertyNames.Type); writer.WriteValue(type); //serialize links if (WriterUtil.ShouldWriteProperty(value, metadata.LinksProperty, serializer, out object links)) { writer.WritePropertyName(PropertyNames.Links); serializer.Serialize(writer, links); } //serialize meta if (WriterUtil.ShouldWriteProperty(value, metadata.MetaProperty, serializer, out object meta)) { writer.WritePropertyName(PropertyNames.Meta); serializer.Serialize(writer, meta); } // store all the relationships, that appear to be attributes from the // property declared type, types but the runtime type shows they are // actaully relationships List <KeyValuePair <JsonProperty, object> > undeclaredRelationships = null; //serialize attributes var startedAttributeSection = false; for (var i = 0; i < metadata.Attributes.Length; i++) { var attributeProperty = metadata.Attributes[i]; if (WriterUtil.ShouldWriteProperty(value, attributeProperty, serializer, out object attributeValue)) { // some relationships are not decalred as such. They exist in properties // with declared types of `object` but the runtime object within is a // relationship. We will check here if this attribute property is really // a relationship, and if it is store it to process later // NOTE: this behviour it leads to nulls being inconsistantly attribute/relationship. // leaving in for backward compatability but remove on next breaking change var attributeValueType = attributeValue?.GetType(); if (attributeValueType != null && attributeProperty.PropertyType != attributeValueType && jsonApiContractResolver.ResourceRelationshipConverter.CanConvert(attributeValueType)) { undeclaredRelationships = undeclaredRelationships ?? new List <KeyValuePair <JsonProperty, object> >(); undeclaredRelationships.Add(new KeyValuePair <JsonProperty, object>(attributeProperty, attributeValue)); continue; } //serialize out the attribute if (!startedAttributeSection) { startedAttributeSection = true; writer.WritePropertyName(PropertyNames.Attributes); writer.WriteStartObject(); } writer.WritePropertyName(attributeProperty.PropertyName); if (attributeProperty.MemberConverter?.CanWrite == true) { attributeProperty.MemberConverter.WriteJson(writer, attributeValue, serializer); } else if (attributeValue is string attributeString) { writer.WriteValue(attributeString); } else if (attributeValue is bool attributeBool) { writer.WriteValue(attributeBool); } else if (attributeValue is int attributeInt) { writer.WriteValue(attributeValue); } else { serializer.Serialize(writer, attributeValue); } } } if (startedAttributeSection) { writer.WriteEndObject(); } //serialize relationships var startedRelationshipSection = false; //first go through our relationships that were originally declared as attributes for (var i = 0; undeclaredRelationships != null && i < undeclaredRelationships.Count; i++) { var relationshipProperty = undeclaredRelationships[i].Key; var relationshipValue = undeclaredRelationships[i].Value; if (!startedRelationshipSection) { startedRelationshipSection = true; writer.WritePropertyName(PropertyNames.Relationships); writer.WriteStartObject(); } if (relationshipProperty.MemberConverter != null) { serializationData.ConverterStack.Push(relationshipProperty.MemberConverter); } writer.WritePropertyName(relationshipProperty.PropertyName); jsonApiContractResolver.ResourceRelationshipConverter.WriteNullableJson( writer, relationshipProperty.PropertyType, relationshipValue, serializer); if (relationshipProperty.MemberConverter != null) { serializationData.ConverterStack.Pop(); } } //then go through the ones we know to be relationships for (var i = 0; i < metadata.Relationships.Length; i++) { var relationshipProperty = metadata.Relationships[i]; if (WriterUtil.ShouldWriteProperty(value, relationshipProperty, serializer, out object relationshipValue)) { if (!startedRelationshipSection) { startedRelationshipSection = true; writer.WritePropertyName(PropertyNames.Relationships); writer.WriteStartObject(); } if (relationshipProperty.MemberConverter != null) { serializationData.ConverterStack.Push(relationshipProperty.MemberConverter); } writer.WritePropertyName(relationshipProperty.PropertyName); jsonApiContractResolver.ResourceRelationshipConverter.WriteNullableJson( writer, relationshipProperty.PropertyType, relationshipValue, serializer); if (relationshipProperty.MemberConverter != null) { serializationData.ConverterStack.Pop(); } } } if (startedRelationshipSection) { writer.WriteEndObject(); } writer.WriteEndObject(); //add reference to this type, so others can reference it if (id != null) { var reference = new ResourceObjectReference(id, type); serializationData.RenderedIncluded.Add(reference); } serializationData.ConverterStack.Pop(); }
protected void WriteReferenceObjectJson(JsonWriter writer, object value, JsonSerializer serializer, JsonObjectContract contract = null) { contract = contract ?? (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType()); writer.WriteStartObject(); //A "resource identifier object" MUST contain type and id members. writer.WritePropertyName(PropertyNames.Id); var idProp = contract.Properties.GetClosestMatchProperty(PropertyNames.Id); var idVal = idProp?.ValueProvider?.GetValue(value) ?? string.Empty; serializer.Serialize(writer, idVal); writer.WritePropertyName(PropertyNames.Type); var typeProp = contract.Properties.GetClosestMatchProperty(PropertyNames.Type); var typeVal = typeProp?.ValueProvider?.GetValue(value) ?? GenerateDefaultTypeName(value.GetType()); serializer.Serialize(writer, typeVal); //we will only write the object to included if there are properties that have have data //that we cant include within the reference var willWriteObjectToIncluded = contract.Properties.Any(prop => { //ignore id, type, meta and ignored properties if (prop.PropertyName == PropertyNames.Id || prop.PropertyName == PropertyNames.Type || prop.PropertyName == PropertyNames.Meta || prop.Ignored) { return(false); } //ignore null properties var propValue = prop.ValueProvider.GetValue(value); if (propValue == null) { if (prop.NullValueHandling != null) { if (prop.NullValueHandling == NullValueHandling.Ignore) { return(false); } } else { if (serializer.NullValueHandling == NullValueHandling.Ignore) { return(false); } } } //we have another property with a value return(true); }); //typeically we would just write the meta in the included. But if we are not going to //have something in included we will write the meta inline here if (!willWriteObjectToIncluded) { var metaProp = contract.Properties.GetClosestMatchProperty(PropertyNames.Meta); var metaVal = metaProp?.ValueProvider?.GetValue(value); if (metaVal != null) { writer.WritePropertyName(PropertyNames.Meta); serializer.Serialize(writer, metaVal); } } writer.WriteEndObject(); if (willWriteObjectToIncluded) { var serializationData = SerializationData.GetSerializationData(writer); var reference = new ResourceObjectReference(idVal.ToString(), typeVal.ToString()); serializationData.Included[reference] = value; } }
protected void WriteFullObjectJson(JsonWriter writer, object value, JsonSerializer serializer) { var valueType = value.GetType(); var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(valueType); writer.WriteStartObject(); //will capture id and type as we go through object id = null; object type = null; //A resource object MUST contain at least the following top-level members: type var typeProp = contract.Properties.GetClosestMatchProperty("type"); if (typeProp == null) { writer.WritePropertyName("type"); type = GenerateDefaultTypeName(valueType); serializer.Serialize(writer, GenerateDefaultTypeName(valueType)); } List <JsonWriterCapture> attributes = new List <JsonWriterCapture>(); List <JsonWriterCapture> relationships = new List <JsonWriterCapture>(); foreach (var prop in contract.Properties.Where(x => !x.Ignored)) { var propValue = prop.ValueProvider.GetValue(value); if (propValue == null && (prop.NullValueHandling ?? serializer.NullValueHandling) == NullValueHandling.Ignore) { continue; } switch (prop.PropertyName) { //In addition, a resource object MAY contain any of these top - level members: links, meta, attributes, relationships case PropertyNames.Id: //Id is optional on base objects id = propValue; writer.WritePropertyName(prop.PropertyName); serializer.Serialize(writer, id); break; case PropertyNames.Links: case PropertyNames.Meta: writer.WritePropertyName(prop.PropertyName); serializer.Serialize(writer, propValue); break; case PropertyNames.Type: writer.WritePropertyName("type"); type = typeProp?.ValueProvider?.GetValue(value) ?? GenerateDefaultTypeName(valueType); serializer.Serialize(writer, type); break; default: //we do not know if it is an Attribute or a Relationship //so we will send out a probe to determine which one it is var probe = new AttributeOrRelationshipProbe(writer); probe.WritePropertyName(prop.PropertyName); WriterUtil.WritePropertyValue(serializer, prop, propValue, probe); (probe.PropertyType == AttributeOrRelationshipProbe.Type.Attribute ? attributes : relationships).Add(probe); break; } } //add reference to this type, so others can reference it var serializationData = SerializationData.GetSerializationData(writer); var reference = new ResourceObjectReference(id?.ToString(), type?.ToString()); serializationData.Included[reference] = value; serializationData.RenderedIncluded.Add(reference); //output our attibutes in an attribute tag if (attributes.Count > 0) { writer.WritePropertyName(PropertyNames.Attributes); writer.WriteStartObject(); foreach (var attribute in attributes) { attribute.ApplyCaptured(); } writer.WriteEndObject(); } //output our relationships in a relationship tag if (relationships.Count > 0) { writer.WritePropertyName(PropertyNames.Relationships); writer.WriteStartObject(); foreach (var relationship in relationships) { relationship.ApplyCaptured(); } writer.WriteEndObject(); } writer.WriteEndObject(); }
protected void WriteReferenceObjectJson(JsonWriter writer, object value, JsonSerializer serializer) { var contractResolver = serializer.ContractResolver; if (!(contractResolver.ResolveContract(value.GetType()) is JsonObjectContract contract && contract.Converter is ResourceObjectConverter)) { throw new JsonApiFormatException(writer.Path, $"Expected to find to find resource object, but found '{value}'", "Resource indentifier objects MUST contain 'id' members"); } writer.WriteStartObject(); //A "resource identifier object" MUST contain type and id members. writer.WritePropertyName(PropertyNames.Id); var idProp = contract.Properties.GetClosestMatchProperty(PropertyNames.Id); var idVal = idProp?.ValueProvider?.GetValue(value) ?? string.Empty; serializer.Serialize(writer, idVal); writer.WritePropertyName(PropertyNames.Type); var typeProp = contract.Properties.GetClosestMatchProperty(PropertyNames.Type); var typeVal = typeProp?.ValueProvider?.GetValue(value) ?? GenerateDefaultTypeName(value.GetType()); serializer.Serialize(writer, typeVal); //we will only write the object to included if there are properties that have have data //that we cant include within the reference var willWriteObjectToIncluded = contract.Properties.Any(prop => { //ignore id, type, meta and ignored properties if (prop.PropertyName == PropertyNames.Id || prop.PropertyName == PropertyNames.Type || prop.PropertyName == PropertyNames.Meta || prop.Ignored) { return(false); } //ignore null properties var propValue = prop.ValueProvider.GetValue(value); return(propValue != null || (prop.NullValueHandling ?? serializer.NullValueHandling) == NullValueHandling.Include); }); //typeically we would just write the meta in the included. But if we are not going to //have something in included we will write the meta inline here if (!willWriteObjectToIncluded) { var metaProp = contract.Properties.GetClosestMatchProperty(PropertyNames.Meta); var metaVal = metaProp?.ValueProvider?.GetValue(value); if (metaVal != null) { writer.WritePropertyName(PropertyNames.Meta); serializer.Serialize(writer, metaVal); } } writer.WriteEndObject(); if (willWriteObjectToIncluded) { var serializationData = SerializationData.GetSerializationData(writer); var reference = new ResourceObjectReference(idVal.ToString(), typeVal.ToString()); serializationData.Included[reference] = value; } }
protected void WriteFullObjectJson(JsonWriter writer, object value, JsonSerializer serializer) { var valueType = value.GetType(); var contractResolver = serializer.ContractResolver; if (!(contractResolver.ResolveContract(valueType) is JsonObjectContract contract && contract.Converter is ResourceObjectConverter)) { throw new JsonApiFormatException(writer.Path, $"Expected to find to find resource object, but found '{value}'", "Resource indentifier objects MUST contain 'id' members"); } //prepare to start capturing attributes and relationships var attributes = new List <KeyValuePair <JsonProperty, object> >(); var relationships = new List <KeyValuePair <JsonProperty, object> >(); //will capture id and type as we go through object id = null; object type = null; writer.WriteStartObject(); //A resource object MUST contain at least the following top-level members: type var typeProp = contract.Properties.GetClosestMatchProperty("type"); if (typeProp == null) { writer.WritePropertyName("type"); type = GenerateDefaultTypeName(valueType); serializer.Serialize(writer, type); } foreach (var prop in contract.Properties.Where(x => !x.Ignored)) { var propValue = prop.ValueProvider.GetValue(value); if (propValue == null && (prop.NullValueHandling ?? serializer.NullValueHandling) == NullValueHandling.Ignore) { continue; } var propType = propValue?.GetType() ?? prop.PropertyType; switch (prop.PropertyName) { //In addition, a resource object MAY contain any of these top - level members: links, meta, attributes, relationships case PropertyNames.Id: //Id is optional on base objects id = propValue; writer.WritePropertyName(PropertyNames.Id); serializer.Serialize(writer, id); break; case PropertyNames.Links: case PropertyNames.Meta: writer.WritePropertyName(prop.PropertyName); serializer.Serialize(writer, propValue); break; case PropertyNames.Type: writer.WritePropertyName(PropertyNames.Type); type = typeProp?.ValueProvider?.GetValue(value) ?? GenerateDefaultTypeName(valueType); serializer.Serialize(writer, type); break; case var _ when TryParseAsRelationship(contractResolver.ResolveContract(propType), propValue, out var relationshipObj): relationships.Add(new KeyValuePair <JsonProperty, object>(prop, relationshipObj)); break; default: attributes.Add(new KeyValuePair <JsonProperty, object>(prop, propValue)); break; } } //output our attibutes if (attributes.Count > 0) { writer.WritePropertyName(PropertyNames.Attributes); writer.WriteStartObject(); foreach (var attribute in attributes) { writer.WritePropertyName(attribute.Key.PropertyName); WriterUtil.SerializeValueWithMemberConvertor(serializer, writer, attribute.Key, attribute.Value); } writer.WriteEndObject(); } //output our relationships if (relationships.Count > 0) { writer.WritePropertyName(PropertyNames.Relationships); writer.WriteStartObject(); foreach (var relationship in relationships) { writer.WritePropertyName(relationship.Key.PropertyName); serializer.Serialize(writer, relationship.Value); } writer.WriteEndObject(); } writer.WriteEndObject(); //add reference to this type, so others can reference it var serializationData = SerializationData.GetSerializationData(writer); var reference = new ResourceObjectReference(id?.ToString(), type?.ToString()); serializationData.Included[reference] = value; serializationData.RenderedIncluded.Add(reference); }