public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var jsonApiContractResolver = (JsonApiContractResolver)serializer.ContractResolver; var contract = jsonApiContractResolver.ResolveContract(objectType); // we be a ResourceObject rather than a RelationshpObject // if so we will just read the data property of the resource object if (!(contract is ResourceRelationshipContract rrc)) { return(ReadJsonDataPropertyAsResourceObject(reader, objectType, existingValue, serializer, jsonApiContractResolver)); } if (ReaderUtil.TryUseCustomConvertor(reader, objectType, existingValue, serializer, this, out object customConvertedValue)) { return(customConvertedValue); } // if the value has been explicitly set to null then the value of the element is simply null if (reader.TokenType == JsonToken.Null) { return(null); } //create a new relationship object and start populating the properties var obj = rrc.DefaultCreator(); foreach (var propName in ReaderUtil.IterateProperties(reader)) { switch (propName) { case PropertyNames.Data: ReaderUtil.TryPopulateProperty( serializer, obj, rrc.Properties.GetClosestMatchProperty(propName), reader, overrideConverter: jsonApiContractResolver.ResourceIdentifierConverter); break; default: ReaderUtil.TryPopulateProperty( serializer, obj, rrc.Properties.GetClosestMatchProperty(propName), reader); break; } } return(obj); }
private object ReadJsonAsExplicitResourceIdentifier(ForkableJsonReader reader, Type objectType, JsonSerializer serializer) { if (ReaderUtil.TryUseCustomConvertor(reader, objectType, null, serializer, this, out object customConvertedValue)) { return(customConvertedValue); } // if the value has been explicitly set to null then the value of the element is simply null if (reader.TokenType == JsonToken.Null) { return(null); } var serializationData = SerializationData.GetSerializationData(reader); var jsonApiContractResolver = (JsonApiContractResolver)serializer.ContractResolver; var resourceIdentifierContract = (ResourceIdentifierContract)jsonApiContractResolver.ResolveContract(objectType); var resourceIdentifier = resourceIdentifierContract.DefaultCreator(); var reference = ReaderUtil.ReadAheadToIdentifyObject(reader); var explicitResourceIdentifierReader = reader.Fork(); foreach (var innerPropName in ReaderUtil.IterateProperties(explicitResourceIdentifierReader)) { ReaderUtil.TryPopulateProperty( serializer, resourceIdentifier, resourceIdentifierContract.Properties.GetClosestMatchProperty(innerPropName), explicitResourceIdentifierReader); } var resourceObject = ReadJsonAsResourceObject( reader, resourceIdentifierContract.ResourceObjectProperty.PropertyType, serializer); //we will only set the resource object if we have rendered the included //value somehwere, if we have not it means the value was actaully provided var valueProvider = resourceIdentifierContract.ResourceObjectProperty.ValueProvider; serializationData.PostProcessingActions.Add(() => { if (serializationData.RenderedIncluded.Contains(reference)) { valueProvider.SetValue(resourceIdentifier, resourceObject); } }); return(resourceIdentifier); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //we may be starting the deserialization here, if thats the case we need to resolve this object as the root var serializationData = SerializationData.GetSerializationData(reader); if (!serializationData.HasProcessedDocumentRoot) { return(DocumentRootConverter.ResolveAsRootData(reader, objectType, serializer)); } if (ReaderUtil.TryUseCustomConvertor(reader, objectType, existingValue, serializer, this, out object customConvertedValue)) { return(customConvertedValue); } //if the value has been explicitly set to null then the value of the element is simply null if (reader.TokenType == JsonToken.Null) { return(null); } serializationData.ConverterStack.Push(this); var forkableReader = reader as ForkableJsonReader ?? new ForkableJsonReader(reader); reader = forkableReader; var contractResolver = (JsonApiContractResolver)serializer.ContractResolver; var reference = ReaderUtil.ReadAheadToIdentifyObject(forkableReader); //if we dont have this object already we will create it existingValue = existingValue ?? CreateObject(objectType, reference.Type, serializer); JsonContract rawContract = contractResolver.ResolveContract(existingValue.GetType()); if (!(rawContract is JsonObjectContract contract)) { throw new JsonApiFormatException( forkableReader.FullPath, $"Expected created object to be a resource object, but found '{existingValue}'", "Resource indentifier objects MUST contain 'id' members"); } foreach (var propName in ReaderUtil.IterateProperties(reader)) { var successfullyPopulateProperty = ReaderUtil.TryPopulateProperty( serializer, existingValue, contract.Properties.GetClosestMatchProperty(propName), reader); //flatten out attributes onto the object if (!successfullyPopulateProperty && propName == PropertyNames.Attributes) { foreach (var innerPropName in ReaderUtil.IterateProperties(reader)) { ReaderUtil.TryPopulateProperty( serializer, existingValue, contract.Properties.GetClosestMatchProperty(innerPropName), reader); } } //flatten out relationships onto the object if (!successfullyPopulateProperty && propName == PropertyNames.Relationships) { foreach (var innerPropName in ReaderUtil.IterateProperties(reader)) { var prop = contract.Properties.GetClosestMatchProperty(innerPropName); if (prop == null) { continue; } // This is a massive hack. MemberConverters used to work and allowed // modifying a members create object. Unfortunatly Resource Identifiers // are no longer created by this object converter. We are passing the // convertor via the serialization data so ResourceIdentifierConverter // can access it down the line. // next breaking change remove support for ResourceObjectConverter // member converters if (prop.MemberConverter != null) { serializationData.ConverterStack.Push(prop.MemberConverter); } ReaderUtil.TryPopulateProperty( serializer, existingValue, contract.Properties.GetClosestMatchProperty(innerPropName), reader, overrideConverter: contractResolver.ResourceRelationshipConverter); if (prop.MemberConverter != null) { serializationData.ConverterStack.Pop(); } } } } //we have rendered this so we will tag it as included serializationData.RenderedIncluded.Add(reference); serializationData.Included[reference] = existingValue; serializationData.ConverterStack.Pop(); return(existingValue); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { //we may be starting the deserialization here, if thats the case we need to resolve this object as the root var serializationData = SerializationData.GetSerializationData(reader); if (!serializationData.HasProcessedDocumentRoot) { return(DocumentRootConverter.ResolveAsRootData(reader, objectType, serializer)); } if (ReaderUtil.TryUseCustomConvertor(reader, objectType, existingValue, serializer, this, out object customConvertedValue)) { return(customConvertedValue); } //if the value has been explicitly set to null then the value of the element is simply null if (reader.TokenType == JsonToken.Null) { return(null); } serializationData.ConverterStack.Push(this); var forkableReader = reader as ForkableJsonReader ?? new ForkableJsonReader(reader); reader = forkableReader; var contractResolver = (JsonApiContractResolver)serializer.ContractResolver; var reference = ReaderUtil.ReadAheadToIdentifyObject(forkableReader); //if we dont have this object already we will create it existingValue = existingValue ?? CreateObject(objectType, reference.Type, serializer); //mark this object as a possible include. We need to do this before deserialiazing //the relationship; It could have relationships that reference back to this object serializationData.Included[reference] = existingValue; JsonContract rawContract = contractResolver.ResolveContract(existingValue.GetType()); if (!(rawContract is JsonObjectContract contract)) { throw new JsonApiFormatException( forkableReader.FullPath, $"Expected created object to be a resource object, but found '{existingValue}'", "Resource indentifier objects MUST contain 'id' members"); } foreach (var propName in ReaderUtil.IterateProperties(reader)) { var successfullyPopulateProperty = ReaderUtil.TryPopulateProperty( serializer, existingValue, contract.Properties.GetClosestMatchProperty(propName), reader); //flatten out attributes onto the object if (!successfullyPopulateProperty && propName == PropertyNames.Attributes) { // AH 2020-08-27 - store unknown Attributes in this dictionary. Dictionary <string, object> extraAttributes = new Dictionary <string, object>(); JsonProperty extraAttributesProperty = contract.Properties.GetClosestMatchProperty("extraAttributes"); foreach (var innerPropName in ReaderUtil.IterateProperties(reader)) { JsonProperty matchedProperty = contract.Properties.GetClosestMatchProperty(innerPropName); // regular behavior if (matchedProperty != null) { ReaderUtil.TryPopulateProperty( serializer, existingValue, matchedProperty, //contract.Properties.GetClosestMatchProperty(innerPropName), reader); } // modified behavior - store any unknown custom attributes into an extraAttributes collection else if (extraAttributesProperty != null) { object extraAttributeValue = null; // this is a Custom Attribute or an Attribute that is a list if (reader.TokenType == JsonToken.StartArray) { JArray jarr = JArray.Load(reader); if (jarr.Count != 0) { // if we have an object it's a custom attribute. if (jarr[0].Type == JTokenType.Object) { JObject jobj = jarr[0].ToObject <JObject>(); extraAttributeValue = jobj; } //otherwise the array itself is the value for the attribute (like entity_types.entity_attributes) else { extraAttributeValue = jarr; } } // don't read here. Next iteration expects us to be at the end of array/object //reader.Read(); } // It's a regular Attribute that we don't expect. Just store it's value. else { extraAttributeValue = reader.Value; } extraAttributes[innerPropName] = extraAttributeValue; } } if (extraAttributesProperty != null) { extraAttributesProperty.ValueProvider.SetValue(existingValue, extraAttributes); } } //flatten out relationships onto the object if (!successfullyPopulateProperty && propName == PropertyNames.Relationships) { foreach (var innerPropName in ReaderUtil.IterateProperties(reader)) { var prop = contract.Properties.GetClosestMatchProperty(innerPropName); if (prop == null) { continue; } // This is a massive hack. MemberConverters used to work and allowed // modifying a members create object. Unfortunatly Resource Identifiers // are no longer created by this object converter. We are passing the // convertor via the serialization data so ResourceIdentifierConverter // can access it down the line. // next breaking change remove support for ResourceObjectConverter // member converters if (prop.MemberConverter != null) { serializationData.ConverterStack.Push(prop.MemberConverter); } ReaderUtil.TryPopulateProperty( serializer, existingValue, contract.Properties.GetClosestMatchProperty(innerPropName), reader, overrideConverter: contractResolver.ResourceRelationshipConverter); if (prop.MemberConverter != null) { serializationData.ConverterStack.Pop(); } } } } //we have read the object so we will tag it as 'rendered' serializationData.RenderedIncluded.Add(reference); serializationData.ConverterStack.Pop(); return(existingValue); }