示例#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)
        {
            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);
        }
示例#3
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);
        }
示例#4
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();
        }
示例#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
            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);
        }