public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var forkableReader = reader as ForkableJsonReader ?? new ForkableJsonReader(reader); var reference = ReaderUtil.ReadAheadToIdentifyObject(forkableReader); var existingObject = serializer.ReferenceResolver.ResolveReference(null, reference.ToString()); if (existingObject == null) { //we dont know what type this object should be so we will just save it as a JObject var unknownObject = serializer.Deserialize <JObject>(forkableReader); serializer.ReferenceResolver.AddReference(null, reference.ToString(), unknownObject); return(unknownObject); } else if (existingObject is JObject) { //we already have a resolved object that we dont know what the type is, we will keep the first one return(existingObject); } else { //We have an existing object, its likely our included data has more detail than what //is currently on the object so we will pass the reader so it can be deserialized again var type = existingObject.GetType(); var existingObjectContract = serializer.ContractResolver.ResolveContract(type); return(existingObjectContract.Converter.ReadJson(reader, type, existingObject, serializer)); } }
private object ReadJsonAsResourceObject(ForkableJsonReader reader, Type objectType, JsonSerializer serializer) { // 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 reference = ReaderUtil.ReadAheadToIdentifyObject(reader); if (serializationData.Included.TryGetValue(reference, out object resourceObject)) { if (resourceObject is JObject resoruceObjectJObject) { // sometimes the value in the reference resolver is a JObject. This occurs when we // did not know what type it should be when we first read it (i.e. included was processed // before the item). In these cases we now know what type it should be so will read it // as such var resourceObjectReader = new ForkableJsonReader(resoruceObjectJObject.CreateReader(), reader.SerializationDataToken); resourceObjectReader.Read(); //JObject readers begin at Not Started resourceObject = jsonApiContractResolver.ResourceObjectConverter.ReadJson( resourceObjectReader, objectType, null, serializer); } //push the reader to the end, we dont need anything else out of the reference ReaderUtil.ReadUntilEnd(reader, reader.Path); } else { var contract = (JsonObjectContract)jsonApiContractResolver.ResolveContract(objectType); resourceObject = ReaderUtil.CreateObject(serializationData, objectType, reference.Type, serializer); // for placeholders we will just read the top level properties // it is unlikely to have attributes/relationships present foreach (var propName in ReaderUtil.IterateProperties(reader)) { var successfullyPopulateProperty = ReaderUtil.TryPopulateProperty( serializer, resourceObject, contract.Properties.GetClosestMatchProperty(propName), reader); } serializationData.Included[reference] = resourceObject; } if (!TypeInfoShim.IsInstanceOf(objectType.GetTypeInfo(), resourceObject)) { throw new JsonSerializationException($"Unable to assign object '{resourceObject}' to type '{objectType}' at path {reader.FullPath}"); } return(resourceObject); }
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 object obj; if (DocumentRootConverter.TryResolveAsRootData(reader, objectType, serializer, out obj)) { return(obj); } //read into the 'Data' element return(ReaderUtil.ReadInto( reader as ForkableJsonReader ?? new ForkableJsonReader(reader), DataReadPathRegex, dataReader => { //if the value has been explicitly set to null then the value of the element is simply null if (dataReader.TokenType == JsonToken.Null) { return null; } JsonObjectContract contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType); var serializationData = SerializationData.GetSerializationData(dataReader); //if we arent given an existing value check the references to see if we have one in there //if we dont have one there then create a new object to populate if (existingValue == null) { var reference = ReaderUtil.ReadAheadToIdentifyObject(dataReader); if (!serializationData.Included.TryGetValue(reference, out existingValue)) { existingValue = contract.DefaultCreator(); serializationData.Included.Add(reference, existingValue); } if (existingValue is JObject) { //sometimes the value in the reference resolver is a JObject. This occurs when we //did not know what type it should be when we first read it (i.e. included was processed //before the item). In these cases we will create a new object and read data from the JObject dataReader = new ForkableJsonReader(((JObject)existingValue).CreateReader(), dataReader.SerializationDataToken); dataReader.Read(); //JObject readers begin at Not Started existingValue = contract.DefaultCreator(); serializationData.Included[reference] = existingValue; } } PopulateProperties(serializer, existingValue, dataReader, contract); return existingValue; })); }
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); }
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 if (DocumentRootConverter.TryResolveAsRootData(reader, objectType, serializer, out object obj)) { return(obj); } //read into the 'Data' element return(ReaderUtil.ReadInto( reader as ForkableJsonReader ?? new ForkableJsonReader(reader), DataReadPathRegex, dataReader => { //if they have custom convertors registered, we will respect them var customConvertor = serializer.Converters.FirstOrDefault(x => x.CanRead && x.CanConvert(objectType)); if (customConvertor != null && customConvertor != this) { return customConvertor.ReadJson(reader, objectType, existingValue, serializer); } //if the value has been explicitly set to null then the value of the element is simply null if (dataReader.TokenType == JsonToken.Null) { return null; } var serializationData = SerializationData.GetSerializationData(dataReader); //if we arent given an existing value check the references to see if we have one in there //if we dont have one there then create a new object to populate if (existingValue == null) { var reference = ReaderUtil.ReadAheadToIdentifyObject(dataReader); if (!serializationData.Included.TryGetValue(reference, out existingValue)) { existingValue = CreateObject(objectType, reference.Type, serializer); serializationData.Included.Add(reference, existingValue); } if (existingValue is JObject existingValueJObject) { //sometimes the value in the reference resolver is a JObject. This occurs when we //did not know what type it should be when we first read it (i.e. included was processed //before the item). In these cases we will create a new object and read data from the JObject dataReader = new ForkableJsonReader(existingValueJObject.CreateReader(), dataReader.SerializationDataToken); dataReader.Read(); //JObject readers begin at Not Started existingValue = CreateObject(objectType, reference.Type, serializer); serializationData.Included[reference] = existingValue; } } //additional check to ensure the object we created is of the correct type if (!TypeInfoShim.IsInstanceOf(objectType.GetTypeInfo(), existingValue)) { throw new JsonSerializationException($"Unable to assign object '{existingValue}' to type '{objectType}'"); } PopulateProperties(serializer, existingValue, dataReader); return existingValue; })); }