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) { var serializationData = SerializationData.GetSerializationData(reader); var forkableReader = reader as ForkableJsonReader ?? new ForkableJsonReader(reader); var jsonApiContractResolver = (JsonApiContractResolver)serializer.ContractResolver; var contract = jsonApiContractResolver.ResolveContract(objectType); switch (contract) { case ResourceObjectContract roc: return(ReadJsonAsResourceObject(forkableReader, objectType, serializer)); case ResourceIdentifierContract ric: return(ReadJsonAsExplicitResourceIdentifier(forkableReader, objectType, serializer)); case JsonArrayContract jac when forkableReader.TokenType == JsonToken.StartArray: var list = new List <object>(); foreach (var item in ReaderUtil.IterateList(forkableReader)) { list.Add(ReadJson(forkableReader, jac.CollectionItemType, null, serializer)); } return(ListUtil.CreateList(objectType, list)); default: return(serializer.Deserialize(reader, objectType)); throw new JsonApiFormatException( forkableReader.FullPath, $"Expected to find a resource identifier or resource object, but found '{objectType}'", "Resource indentifier objects MUST contain 'id' members"); } }
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 serializationData = SerializationData.GetSerializationData(forkableReader); if (!serializationData.Included.TryGetValue(reference, out var existingObject)) { //we dont know what type this object should be so we will just save it as a JObject var unknownObject = serializer.Deserialize <JObject>(forkableReader); serializationData.Included.Add(reference, 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 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; }
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; } var contractResolver = serializer.ContractResolver; var enumerable = value as IEnumerable <object> ?? Enumerable.Empty <object>(); writer.WriteStartArray(); foreach (var valueElement in enumerable) { if (valueElement == null || !(contractResolver.ResolveContract(valueElement.GetType()) is ResourceObjectContract)) { throw new JsonApiFormatException(writer.Path, $"Expected to find to find resource objects within lists, but found '{valueElement}'", "Resource identifier objects MUST contain 'id' members"); } serializer.Serialize(writer, valueElement); } writer.WriteEndArray(); }
internal static bool TryResolveAsRootError(JsonReader reader, Type objectType, JsonSerializer serializer, out IEnumerable <IError> obj) { var serializationData = SerializationData.GetSerializationData(reader); //if we already have a root object then we dont need to resolve the root object if (serializationData.HasProcessedDocumentRoot) { obj = null; return(false); } //determine the error class type. The type passed in could be an array or an object //so we need to determine the error type for both Type errorElementType; if (!ListUtil.IsList(objectType, out errorElementType)) { errorElementType = objectType; } //we do not have a root object, so this is probably the entry point, so we will resolve //a document root and return the data object var documentRootType = typeof(MinimalDocumentRoot <,>).MakeGenericType(typeof(object), errorElementType); var objContract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(documentRootType); var dataProp = objContract.Properties.GetClosestMatchProperty("errors"); var root = serializer.Deserialize(reader, documentRootType); obj = (IEnumerable <IError>)dataProp.ValueProvider.GetValue(root); return(true); }
internal static bool TryResolveAsRootError(JsonWriter writer, object value, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(writer); //if we already have a root object then we dont need to resolve the root object if (serializationData.HasProcessedDocumentRoot) { return(false); } //coerce single element into a list if (value is IError) { value = new List <IError>() { (IError)value } } ; //we do not have a root object, so this is probably the entry point, so we will resolve //it as a document root var documentRootType = typeof(MinimalDocumentRoot <,>).MakeGenericType(typeof(object), typeof(IError)); var objContract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(documentRootType); var rootObj = objContract.DefaultCreator(); //set the data property to be our current object var dataProp = objContract.Properties.GetClosestMatchProperty("errors"); dataProp.ValueProvider.SetValue(rootObj, value); serializer.Serialize(writer, rootObj); return(true); }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(writer); if (!serializationData.HasProcessedDocumentRoot) { DocumentRootConverter.ResolveAsRootError(writer, value, serializer); return; } var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType()); writer.WriteStartObject(); 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; } writer.WritePropertyName(prop.PropertyName); serializer.Serialize(writer, propValue); } writer.WriteEndObject(); }
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; } }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(reader); reader = new ForkableJsonReader(reader); var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType); var rootObject = contract.DefaultCreator(); serializationData.HasProcessedDocumentRoot = true; var includedConverter = new IncludedConverter(); foreach (var propName in ReaderUtil.IterateProperties(reader)) { switch (propName) { case PropertyNames.Data: var documentRootInterfaceType = TypeInfoShim.GetInterfaces(objectType.GetTypeInfo()) .Select(x => x.GetTypeInfo()) .FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDocumentRoot <>)); var dataType = documentRootInterfaceType.GenericTypeArguments[0]; var dataObj = serializer.Deserialize(reader, dataType); contract.Properties.GetClosestMatchProperty(PropertyNames.Data).ValueProvider.SetValue(rootObject, dataObj); break; case PropertyNames.Included: //if our object has an included property we will do our best to populate it var property = contract.Properties.GetClosestMatchProperty(propName); if (ReaderUtil.CanPopulateProperty(property)) { ReaderUtil.TryPopulateProperty(serializer, rootObject, contract.Properties.GetClosestMatchProperty(propName), ((ForkableJsonReader)reader).Fork()); } //still need to read our values so they are updated foreach (var obj in ReaderUtil.IterateList(reader)) { var includedObject = includedConverter.ReadJson(reader, typeof(object), null, serializer); } break; default: ReaderUtil.TryPopulateProperty(serializer, rootObject, contract.Properties.GetClosestMatchProperty(propName), reader); break; } } return(rootObject); }
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) { var errors = DocumentRootConverter.ResolveAsRootError(reader, objectType, serializer); return(ListUtil.CreateList(objectType, errors)); } Type elementType; ListUtil.IsList(objectType, out elementType); return(ListUtil.CreateList(objectType, ReaderUtil.IterateList(reader) .Select(x => serializer.Deserialize(reader, elementType)))); }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(writer); if (!serializationData.HasProcessedDocumentRoot) { DocumentRootConverter.ResolveAsRootError(writer, value, serializer); return; } var enumerable = value as IEnumerable <object> ?? Enumerable.Empty <object>(); writer.WriteStartArray(); foreach (var valueElement in enumerable) { serializer.Serialize(writer, valueElement); } writer.WriteEndArray(); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(reader); if (!serializationData.HasProcessedDocumentRoot) { return(DocumentRootConverter.ResolveAsRootData(reader, objectType, serializer)); } //we should be dealing with list types, but we also want the element type if (!ListUtil.IsList(objectType, out Type elementType)) { throw new ArgumentException($"{typeof(ResourceObjectListConverter)} can only read json lists", nameof(objectType)); } var itemsIterator = ReaderUtil.IterateList(reader).Select(x => serializer.Deserialize(reader, elementType)); var list = ListUtil.CreateList(objectType, itemsIterator); return(list); }
internal static bool TryResolveAsRootData(JsonReader reader, Type objectType, JsonSerializer serializer, out object obj) { var serializationData = SerializationData.GetSerializationData(reader); //if we already have a root object then we dont need to resolve the root object if (serializationData.HasProcessedDocumentRoot) { obj = null; return(false); } //we do not have a root object, so this is probably the entry point, so we will resolve //a document root and return the data object var documentRootType = typeof(MinimalDocumentRoot <,>).MakeGenericType(objectType, typeof(Error)); var objContract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(documentRootType); var dataProp = objContract.Properties.GetClosestMatchProperty("data"); var root = serializer.Deserialize(reader, documentRootType); obj = dataProp.ValueProvider.GetValue(root); return(true); }
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) { var errors = DocumentRootConverter.ResolveAsRootError(reader, objectType, serializer); //not sure if this is the correct thing to do. We are deserializing a single //error but json:api always gives us a list of errors. We just return the first //error return(errors.First()); } var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType); var error = (IError)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator(); foreach (var propName in ReaderUtil.IterateProperties(reader)) { ReaderUtil.TryPopulateProperty(serializer, error, contract.Properties.GetClosestMatchProperty(propName), reader); } return(error); }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var serializationData = SerializationData.GetSerializationData(writer); serializationData.HasProcessedDocumentRoot = true; var contractResolver = (JsonApiContractResolver)serializer.ContractResolver; var contract = (JsonObjectContract)contractResolver.ResolveContract(value.GetType()); writer.WriteStartObject(); var propertiesOutput = new HashSet <string>(); foreach (var prop in contract.Properties) { //we will do includes last, so we we can ensure all the references have been added if (prop.PropertyName == PropertyNames.Included) { continue; } //respect the serializers null handling value 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) { case PropertyNames.Data when ListUtil.IsList(propType, out var elementType): writer.WritePropertyName(prop.PropertyName); propertiesOutput.Add(prop.PropertyName); if (propValue == null) { //Resource linkage MUST be represented by an empty array ([]) for empty to-many relationships writer.WriteStartArray(); writer.WriteEndArray(); break; } contractResolver.ResourceObjectListConverter.WriteJson(writer, propValue, serializer); break; case PropertyNames.Data: writer.WritePropertyName(prop.PropertyName); propertiesOutput.Add(prop.PropertyName); if (propValue == null) { writer.WriteNull(); break; } //because we are in a relationship we want to force this list to be treated as a resource object contractResolver.ResourceObjectConverter.WriteJson(writer, propValue, serializer); break; default: //A document MAY contain any of these top-level members: jsonapi, links, included //We are also allowing everything else they happen to have on the root document writer.WritePropertyName(prop.PropertyName); serializer.Serialize(writer, propValue); propertiesOutput.Add(prop.PropertyName); break; } } //A document MUST contain one of the following (data, errors, meta) //so if we do not have one of them we will output a null data if (!propertiesOutput.Contains(PropertyNames.Data) && !propertiesOutput.Contains(PropertyNames.Errors) && !propertiesOutput.Contains(PropertyNames.Meta)) { propertiesOutput.Add(PropertyNames.Data); writer.WritePropertyName(PropertyNames.Data); writer.WriteNull(); } //If a document does not contain a top-level data key, the included member MUST NOT be present if (propertiesOutput.Contains(PropertyNames.Data)) { //output the included. If we have a specified included field we will out everything in there //and we will also output all the references defined in our reference resolver var referencesToInclude = serializationData.Included .Where(x => !serializationData.RenderedIncluded.Contains(x.Key)); //dont output values we have already output //if any other included have been explicitly mentioned we will output them as well var includedProperty = contract.Properties.GetClosestMatchProperty(PropertyNames.Included); var includedValues = includedProperty?.ValueProvider?.GetValue(value) as IEnumerable <object> ?? Enumerable.Empty <object>(); if (referencesToInclude.Any() || includedValues.Any()) { writer.WritePropertyName(PropertyNames.Included); writer.WriteStartArray(); foreach (var includedValue in includedValues) { serializer.Serialize(writer, includedValue); } //I know we can alter the OrderedDictionary while enumerating it, otherwise this would error foreach (var includedReference in referencesToInclude) { serializer.Serialize(writer, includedReference.Value); } writer.WriteEndArray(); } } writer.WriteEndObject(); for (var i = 0; i < serializationData.PostProcessingActions.Count; i++) { serializationData.PostProcessingActions[i](); } serializationData.PostProcessingActions.Clear(); }
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 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; })); }
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(); }
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); }
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); }
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; } }