예제 #1
0
        protected void SerializeLinkedResources(Stream writeStream, JsonWriter writer, JsonSerializer serializer, RelationAggregator aggregator)
        {
            /* This is a bit messy, because we may add items of a given type to the
             * set we are currently processing. Not only is this an issue because you
             * can't modify a set while you're enumerating it (hence why we make a
             * copy first), but we need to catch the newly added objects and process
             * them as well. So, we have to keep making passes until we detect that
             * we haven't added any new objects to any of the appendices.
             */
            Dictionary <Type, ISet <object> >
            processed         = new Dictionary <Type, ISet <object> >(),
                toBeProcessed = new Dictionary <Type, ISet <object> >(); // is this actually necessary?

            /* On top of that, we need a new JsonWriter for each appendix--because we
             * may write objects of type A, then while processing type B find that
             * we need to write more objects of type A! So we can't keep appending
             * to the same writer.
             */
            /* Oh, and we have to keep a reference to the TextWriter of the JsonWriter
             * because there's no member to get it back out again. ?!?
             * */
            Dictionary <Type, KeyValuePair <JsonWriter, StringWriter> > writers = new Dictionary <Type, KeyValuePair <JsonWriter, StringWriter> >();

            int numAdditions;

            do
            {
                numAdditions = 0;
                Dictionary <Type, ISet <object> > appxs = new Dictionary <Type, ISet <object> >(aggregator.Appendices); // shallow clone, in case we add a new type during enumeration!
                foreach (KeyValuePair <Type, ISet <object> > apair in appxs)
                {
                    Type          type     = apair.Key;
                    ISet <object> appendix = apair.Value;
                    JsonWriter    jw;
                    if (writers.ContainsKey(type))
                    {
                        jw = writers[type].Key;
                    }
                    else
                    {
                        // Setup and start the writer for this type...
                        StringWriter sw = new StringWriter();
                        jw            = new JsonTextWriter(sw);
                        writers[type] = new KeyValuePair <JsonWriter, StringWriter>(jw, sw);
                        jw.WriteStartArray();
                    }

                    HashSet <object> tbp;
                    if (processed.ContainsKey(type))
                    {
                        toBeProcessed[type] = tbp = new HashSet <object>(appendix.Except(processed[type]));
                    }
                    else
                    {
                        toBeProcessed[type] = tbp = new HashSet <object>(appendix);
                        processed[type]     = new HashSet <object>();
                    }

                    if (tbp.Count > 0)
                    {
                        numAdditions += tbp.Count;
                        foreach (object obj in tbp)
                        {
                            Serialize(obj, writeStream, jw, serializer, aggregator); // Note, not writer, but jw--we write each type to its own JsonWriter and combine them later.
                        }
                        processed[type].UnionWith(tbp);
                    }

                    //TODO: Add traversal depth limit??
                }
            } while (numAdditions > 0);

            if (aggregator.Appendices.Count > 0)
            {
                writer.WritePropertyName("linked");
                writer.WriteStartObject();

                // Okay, we should have captured everything now. Now combine the type writers into the main writer...
                foreach (KeyValuePair <Type, KeyValuePair <JsonWriter, StringWriter> > apair in writers)
                {
                    apair.Value.Key.WriteEnd();                         // close off the array
                    writer.WritePropertyName(_modelManager.GetJsonKeyForType(apair.Key));
                    writer.WriteRawValue(apair.Value.Value.ToString()); // write the contents of the type JsonWriter's StringWriter to the main JsonWriter
                }

                writer.WriteEndObject();
            }
        }
예제 #2
0
 protected void SerializeMany(object value, Stream writeStream, JsonWriter writer, JsonSerializer serializer, RelationAggregator aggregator)
 {
     writer.WriteStartArray();
     foreach (object singleVal in (IEnumerable)value)
     {
         this.Serialize(singleVal, writeStream, writer, serializer, aggregator);
     }
     writer.WriteEndArray();
 }
예제 #3
0
        protected void Serialize(object value, Stream writeStream, JsonWriter writer, JsonSerializer serializer, RelationAggregator aggregator)
        {
            writer.WriteStartObject();

            // Do the Id now...
            writer.WritePropertyName("id");
            var idProp = _modelManager.GetIdProperty(value.GetType());

            writer.WriteValue(GetValueForIdProperty(idProp, value));

            // Leverage the cached map to avoid another costly call to System.Type.GetProperties()
            PropertyInfo[] props = _modelManager.GetProperties(value.GetType());

            // Do non-model properties first, everything else goes in "links"
            //TODO: Unless embedded???
            IList <PropertyInfo> modelProps = new List <PropertyInfo>();

            foreach (PropertyInfo prop in props)
            {
                if (prop == idProp)
                {
                    continue;
                }

                if (this.CanWriteTypeAsPrimitive(prop.PropertyType))
                {
                    if (prop.GetCustomAttributes().Any(attr => attr is JsonIgnoreAttribute))
                    {
                        continue;
                    }

                    // numbers, strings, dates...
                    writer.WritePropertyName(_modelManager.GetJsonKeyForProperty(prop));

                    var propertyValue = prop.GetValue(value, null);

                    if (prop.PropertyType == typeof(string) &&
                        prop.GetCustomAttributes().Any(attr => attr is SerializeStringAsRawJsonAttribute))
                    {
                        if (propertyValue == null)
                        {
                            writer.WriteNull();
                        }
                        else
                        {
                            var minifiedValue = JsonHelpers.MinifyJson((string)propertyValue);
                            writer.WriteRawValue(minifiedValue);
                        }
                    }
                    else
                    {
                        serializer.Serialize(writer, propertyValue);
                    }
                }
                else
                {
                    modelProps.Add(prop);
                    continue;
                }
            }

            // Now do other stuff
            if (modelProps.Count() > 0)
            {
                writer.WritePropertyName("links");
                writer.WriteStartObject();
            }
            foreach (PropertyInfo prop in modelProps)
            {
                bool               skip = false, iip = false;
                string             lt = null;
                SerializeAsOptions sa = SerializeAsOptions.Ids;

                object[] attrs = prop.GetCustomAttributes(true);

                foreach (object attr in attrs)
                {
                    Type attrType = attr.GetType();
                    if (typeof(JsonIgnoreAttribute).IsAssignableFrom(attrType))
                    {
                        skip = true;
                        continue;
                    }
                    if (typeof(IncludeInPayload).IsAssignableFrom(attrType))
                    {
                        iip = ((IncludeInPayload)attr).Include;
                    }
                    if (typeof(SerializeAs).IsAssignableFrom(attrType))
                    {
                        sa = ((SerializeAs)attr).How;
                    }
                    if (typeof(LinkTemplate).IsAssignableFrom(attrType))
                    {
                        lt = ((LinkTemplate)attr).TemplateString;
                    }
                }
                if (skip)
                {
                    continue;
                }

                writer.WritePropertyName(_modelManager.GetJsonKeyForProperty(prop));

                // Look out! If we want to SerializeAs a link, computing the property is probably
                // expensive...so don't force it just to check for null early!
                if (sa != SerializeAsOptions.Link && prop.GetValue(value, null) == null)
                {
                    writer.WriteNull();
                    continue;
                }

                // Now look for enumerable-ness:
                if (typeof(IEnumerable <Object>).IsAssignableFrom(prop.PropertyType))
                {
                    switch (sa)
                    {
                    case SerializeAsOptions.Ids:
                        //writer.WritePropertyName(ContractResolver._modelManager.GetJsonKeyForProperty(prop));
                        IEnumerable <object> items = (IEnumerable <object>)prop.GetValue(value, null);
                        if (items == null)
                        {
                            writer.WriteValue((IEnumerable <object>)null); //TODO: Is it okay with the spec and Ember Data to return null for an empty array?
                            break;                                         // LOOK OUT! Ending this case block early here!!!
                        }
                        this.WriteIdsArrayJson(writer, items, serializer);
                        if (iip)
                        {
                            Type itemType;
                            if (prop.PropertyType.IsGenericType)
                            {
                                itemType = prop.PropertyType.GetGenericArguments()[0];
                            }
                            else
                            {
                                // Must be an array at this point, right??
                                itemType = prop.PropertyType.GetElementType();
                            }
                            if (aggregator != null)
                            {
                                aggregator.Add(itemType, items);                         // should call the IEnumerable one...right?
                            }
                        }
                        break;

                    case SerializeAsOptions.Link:
                        if (lt == null)
                        {
                            throw new JsonSerializationException("A property was decorated with SerializeAs(SerializeAsOptions.Link) but no LinkTemplate attribute was provided.");
                        }
                        //TODO: Check for "{0}" in linkTemplate and (only) if it's there, get the Ids of all objects and "implode" them.
                        string href = String.Format(lt, null, GetIdFor(value));
                        //writer.WritePropertyName(ContractResolver._modelManager.GetJsonKeyForProperty(prop));
                        //TODO: Support ids and type properties in "link" object
                        writer.WriteStartObject();
                        writer.WritePropertyName("href");
                        writer.WriteValue(href);
                        writer.WriteEndObject();
                        break;

                    case SerializeAsOptions.Embedded:
                        // Not really supported by Ember Data yet, incidentally...but easy to implement here.
                        //writer.WritePropertyName(ContractResolver._modelManager.GetJsonKeyForProperty(prop));
                        //serializer.Serialize(writer, prop.GetValue(value, null));
                        this.Serialize(prop.GetValue(value, null), writeStream, writer, serializer, aggregator);
                        break;
                    }
                }
                else
                {
                    var propertyValue = prop.GetValue(value, null);
                    if (propertyValue == null)
                    {
                        writer.WriteNull();
                    }
                    else
                    {
                        string objId = GetIdFor(propertyValue);

                        switch (sa)
                        {
                        case SerializeAsOptions.Ids:
                            //writer.WritePropertyName(ContractResolver._modelManager.GetJsonKeyForProperty(prop));
                            serializer.Serialize(writer, objId);
                            if (iip)
                            {
                                if (aggregator != null)
                                {
                                    aggregator.Add(prop.PropertyType, prop.GetValue(value, null));
                                }
                            }
                            break;

                        case SerializeAsOptions.Link:
                            if (lt == null)
                            {
                                throw new JsonSerializationException(
                                          "A property was decorated with SerializeAs(SerializeAsOptions.Link) but no LinkTemplate attribute was provided.");
                            }
                            string link = String.Format(lt, objId,
                                                        GetIdFor(value)); //value.GetType().GetProperty("Id").GetValue(value, null));

                            //writer.WritePropertyName(ContractResolver._modelManager.GetJsonKeyForProperty(prop));
                            writer.WriteStartObject();
                            writer.WritePropertyName("href");
                            writer.WriteValue(link);
                            writer.WriteEndObject();
                            break;

                        case SerializeAsOptions.Embedded:
                            // Not really supported by Ember Data yet, incidentally...but easy to implement here.
                            //writer.WritePropertyName(ContractResolver._modelManager.GetJsonKeyForProperty(prop));
                            //serializer.Serialize(writer, prop.GetValue(value, null));
                            this.Serialize(prop.GetValue(value, null), writeStream, writer, serializer, aggregator);
                            break;
                        }
                    }
                }
            }
            if (modelProps.Count() > 0)
            {
                writer.WriteEndObject();
            }

            writer.WriteEndObject();
        }
예제 #4
0
        public override Task WriteToStreamAsync(System.Type type, object value, Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
        {
            RelationAggregator aggregator;

            lock (this.RelationAggregators)
            {
                if (this.RelationAggregators.ContainsKey(writeStream))
                {
                    aggregator = this.RelationAggregators[writeStream];
                }
                else
                {
                    aggregator = new RelationAggregator();
                    this.RelationAggregators[writeStream] = aggregator;
                }
            }

            var            contentHeaders    = content == null ? null : content.Headers;
            var            effectiveEncoding = SelectCharacterEncoding(contentHeaders);
            JsonWriter     writer            = this.CreateJsonWriter(typeof(object), writeStream, effectiveEncoding);
            JsonSerializer serializer        = this.CreateJsonSerializer();

            if (_errorSerializer.CanSerialize(type))
            {
                // `value` is an error
                _errorSerializer.SerializeError(value, writeStream, writer, serializer);
            }
            else
            {
                Type valtype = GetSingleType(value.GetType());
                if (_modelManager.IsSerializedAsMany(value.GetType()))
                {
                    aggregator.AddPrimary(valtype, (IEnumerable <object>)value);
                }
                else
                {
                    aggregator.AddPrimary(valtype, value);
                }

                //writer.Formatting = Formatting.Indented;

                var root = _modelManager.GetJsonKeyForType(type);

                writer.WriteStartObject();
                writer.WritePropertyName(root);
                if (_modelManager.IsSerializedAsMany(value.GetType()))
                {
                    this.SerializeMany(value, writeStream, writer, serializer, aggregator);
                }
                else
                {
                    this.Serialize(value, writeStream, writer, serializer, aggregator);
                }

                // Include links from aggregator
                SerializeLinkedResources(writeStream, writer, serializer, aggregator);

                writer.WriteEndObject();
            }
            writer.Flush();

            lock (this.RelationAggregators)
            {
                this.RelationAggregators.Remove(writeStream);
            }

            //return base.WriteToStreamAsync(type, obj as object, writeStream, content, transportContext);

            //TODO: For now we won't worry about optimizing this down into smaller async parts, we'll just do it all synchronous. So...
            // Just return a completed task... from http://stackoverflow.com/a/17527551/489116
            var tcs = new TaskCompletionSource <object>();

            tcs.SetResult(null);
            return(tcs.Task);
        }