コード例 #1
0
        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();
        }
コード例 #2
0
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            object list;

            if (DocumentRootConverter.TryResolveAsRootData(reader, objectType, serializer, out list))
            {
                return(list);
            }

            //read into the 'Data' path
            var preDataPath = ReaderUtil.ReadUntilStart(reader, DataPathRegex);

            //we should be dealing with list types, but we also want the element type
            Type elementType;

            if (!ListUtil.IsList(objectType, out elementType))
            {
                throw new ArgumentException($"{typeof(ResourceObjectListConverter)} can only read json lists", nameof(objectType));
            }

            var itemsIterator = ReaderUtil.IterateList(reader).Select(x => serializer.Deserialize(reader, elementType));

            list = ListUtil.CreateList(objectType, itemsIterator);

            //read out of the 'Data' path
            ReaderUtil.ReadUntilEnd(reader, preDataPath);

            return(list);
        }
コード例 #3
0
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (DocumentRootConverter.TryResolveAsRootData(writer, value, serializer))
            {
                return;
            }

            //if they have custom convertors registered, we will respect them
            var customConvertor = serializer.Converters.FirstOrDefault(x => x.CanWrite && x.CanConvert(value.GetType()));

            if (customConvertor != null && customConvertor != this)
            {
                customConvertor.WriteJson(writer, value, serializer);
                return;
            }

            var isRelationship = RelationshipPathRegex.IsMatch(writer.Path);

            if (isRelationship)
            {
                WriteReferenceObjectJson(writer, value, serializer);
            }
            else
            {
                WriteFullObjectJson(writer, value, serializer);
            }
        }
コード例 #4
0
        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();
        }
コード例 #5
0
        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;
            }));
        }
コード例 #6
0
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (DocumentRootConverter.TryResolveAsRootError(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();
        }
コード例 #7
0
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (DocumentRootConverter.TryResolveAsRootData(writer, value, serializer))
            {
                return;
            }

            var isRelationship = RelationshipPathRegex.IsMatch(writer.Path);

            if (isRelationship)
            {
                WriteReferenceObjectJson(writer, value, serializer);
            }
            else
            {
                WriteFullObjectJson(writer, value, serializer);
            }
        }
コード例 #8
0
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (DocumentRootConverter.TryResolveAsRootData(writer, value, serializer))
            {
                return;
            }

            WriterUtil.WriteIntoElement(writer, DataPathRegex, PropertyNames.Data, () =>
            {
                var enumerable = value as IEnumerable <object> ?? Enumerable.Empty <object>();
                writer.WriteStartArray();
                foreach (var valueElement in enumerable)
                {
                    serializer.Serialize(writer, valueElement);
                }
                writer.WriteEndArray();
            });
        }
コード例 #9
0
        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
            IEnumerable <IError> errors;

            if (DocumentRootConverter.TryResolveAsRootError(reader, objectType, serializer, out errors))
            {
                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))));
        }
コード例 #10
0
        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();
        }
コード例 #11
0
        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))));
        }
コード例 #12
0
        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);
        }
コード例 #13
0
        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
            IEnumerable <IError> errors;

            if (DocumentRootConverter.TryResolveAsRootError(reader, objectType, serializer, out errors))
            {
                //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);
        }
コード例 #14
0
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (DocumentRootConverter.TryResolveAsRootData(writer, value, serializer))
            {
                return;
            }

            WriterUtil.WriteIntoElement(writer, DataWritePathRegex, PropertyNames.Data, () =>
            {
                var probe = writer as AttributeOrRelationshipProbe;
                if (probe != null)
                {
                    //if someone is sending a probe its because we are in a relationship property.
                    //let the probe know we are in a relationship and write the reference element
                    probe.PropertyType = AttributeOrRelationshipProbe.Type.Relationship;
                    WriteReferenceObjectJson(writer, value, serializer);
                }
                else
                {
                    WriteFullObjectJson(writer, value, serializer);
                }
            });
        }
コード例 #15
0
        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);
        }
コード例 #16
0
        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();
        }
コード例 #17
0
        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;
            }));
        }
コード例 #18
0
        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);
        }