コード例 #1
0
        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);
        }
コード例 #2
0
        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");
            }
        }
コード例 #3
0
        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));
            }
        }
コード例 #4
0
        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;
        }
コード例 #5
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();
        }
コード例 #6
0
        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);
        }
コード例 #7
0
        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);
        }
コード例 #8
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();
        }
コード例 #9
0
        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;
            }
        }
コード例 #10
0
        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);
        }
コード例 #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
            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;
            }));
        }
コード例 #12
0
        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);
        }
コード例 #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
            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))));
        }
コード例 #14
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();
        }
コード例 #15
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);
        }
コード例 #16
0
        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);
        }
コード例 #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
            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);
        }
コード例 #18
0
        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();
        }
コード例 #19
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);
        }
コード例 #20
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;
            }));
        }
コード例 #21
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();
        }
コード例 #22
0
        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;
            }
        }
コード例 #23
0
        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();
        }
コード例 #24
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);
        }
コード例 #25
0
        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);
        }
コード例 #26
0
        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;
            }
        }