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. ?!? * */ int numAdditions; if (aggregator.Appendices.Count > 0) { writer.WritePropertyName(IncludedKeyName); writer.WriteStartArray(); 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; 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, writer, serializer, aggregator); // Note, not writer, but jw--we write each type to its own JsonWriter and combine them later. } processed[type].UnionWith(tbp); } } } while (numAdditions > 0); writer.WriteEndArray(); } }
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(); }
protected void Serialize(object value, Stream writeStream, JsonWriter writer, JsonSerializer serializer, RelationAggregator aggregator) { writer.WriteStartObject(); var resourceType = value.GetType(); var jsonTypeKey = _modelManager.GetResourceTypeNameForType(resourceType); var idProp = _modelManager.GetIdProperty(resourceType); // Write the type and id WriteTypeAndId(writer, resourceType, value); // Leverage the cached map to avoid another costly call to System.Type.GetProperties() var props = _modelManager.GetProperties(value.GetType()); // Do non-model properties first, everything else goes in "related" //TODO: Unless embedded??? var relationshipModelProperties = new List<RelationshipModelProperty>(); // Write attributes WriteAttributes(props, writer, idProp, value, serializer, relationshipModelProperties); // Now do other stuff if (relationshipModelProperties.Count() > 0) { writer.WritePropertyName(RelationshipsKeyName); writer.WriteStartObject(); } foreach (var relationshipModelProperty in relationshipModelProperties) { bool skip = false, iip = false; string lt = null; SerializeAsOptions sa = SerializeAsOptions.NoLinks; var prop = relationshipModelProperty.Property; 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; // Write the relationship's type writer.WritePropertyName(relationshipModelProperty.JsonKey); // Now look for enumerable-ness: if (typeof(IEnumerable<Object>).IsAssignableFrom(prop.PropertyType)) { writer.WriteStartObject(); // Write the data element writer.WritePropertyName(PrimaryDataKeyName); // 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.NoLinks && prop.GetValue(value, null) == null) { writer.WriteStartArray(); writer.WriteEndArray(); writer.WriteEndObject(); continue; } // Always write the data attribute of a relationship IEnumerable<object> items = (IEnumerable<object>)prop.GetValue(value, null); if (items == null) { // Return an empty array when there are no items writer.WriteStartArray(); writer.WriteEndArray(); } else { // Write the array with data 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? } } // in case there is also a link defined, add it to the relationship if (sa == SerializeAsOptions.RelatedLink) { 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.WritePropertyName(LinksKeyName); writer.WriteStartObject(); writer.WritePropertyName(RelatedKeyName); writer.WriteValue(href); writer.WriteEndObject(); } writer.WriteEndObject(); } else { var propertyValue = prop.GetValue(value, null); writer.WriteStartObject(); // Write the data element writer.WritePropertyName(PrimaryDataKeyName); // 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.NoLinks && propertyValue == null) { writer.WriteNull(); writer.WriteEndObject(); continue; } Lazy<string> objId = new Lazy<String>(() => GetIdFor(propertyValue)); // Write the resource identifier object WriteResourceIdentifierObject(writer, prop.PropertyType, propertyValue); if (iip) if (aggregator != null) aggregator.Add(prop.PropertyType, propertyValue); // If there are links write them next to the data object if (sa == SerializeAsOptions.RelatedLink) { if (lt == null) throw new JsonSerializationException( "A property was decorated with SerializeAs(SerializeAsOptions.Link) but no LinkTemplate attribute was provided."); var relatedObjectId = lt.Contains("{0}") ? objId.Value : null; string link = String.Format(lt, relatedObjectId, GetIdFor(value)); writer.WritePropertyName(LinksKeyName); writer.WriteStartObject(); writer.WritePropertyName(RelatedKeyName); writer.WriteValue(link); writer.WriteEndObject(); } writer.WriteEndObject(); } } if (relationshipModelProperties.Count() > 0) { writer.WriteEndObject(); } writer.WriteEndObject(); }
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 { var payload = value as IPayload; if (payload != null) { value = payload.PrimaryData; } writer.WriteStartObject(); writer.WritePropertyName(PrimaryDataKeyName); if (value == null) { if (_modelManager.IsSerializedAsMany(type)) { writer.WriteStartArray(); writer.WriteEndArray(); } else { writer.WriteNull(); } } 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; 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); } if (payload != null && payload.Metadata != null) { writer.WritePropertyName("meta"); serializer.Serialize(writer, payload.Metadata); } 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; }