From bb33c6ccf0375221f24c7954d4dd19cdc8fe902b Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 3 Feb 2015 11:50:27 +0100 Subject: [PATCH 1/2] Internal refactor of MetadataManager, to better handle more types of metadata going forward. --- JSONAPI/Core/MetadataManager.cs | 69 ++++++++++++++++++++++---------- JSONAPI/Json/JsonApiFormatter.cs | 4 +- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/JSONAPI/Core/MetadataManager.cs b/JSONAPI/Core/MetadataManager.cs index d1541d52..3b3141a9 100644 --- a/JSONAPI/Core/MetadataManager.cs +++ b/JSONAPI/Core/MetadataManager.cs @@ -10,6 +10,23 @@ namespace JSONAPI.Core { public sealed class MetadataManager { + private class PropertyMetadata + { + public bool PresentInJson { get; set; } // only meaningful for incoming/deserialized models! + public Lazy> AttributeOverrides + = new Lazy>( + () => new List() + ); + } + + private class ModelMetadata + { + public Lazy> PropertyMetadata + = new Lazy>( + () => new Dictionary() + ); + } + #region Singleton pattern private static readonly MetadataManager instance = new MetadataManager(); @@ -26,8 +43,8 @@ public static MetadataManager Instance #endregion - private readonly ConditionalWeakTable> cwt - = new ConditionalWeakTable>(); + private readonly ConditionalWeakTable cwt + = new ConditionalWeakTable(); /* internal void SetDeserializationMetadata(object deserialized, Dictionary meta) @@ -36,32 +53,40 @@ internal void SetDeserializationMetadata(object deserialized, Dictionary meta; - if (!cwt.TryGetValue(deserialized, out meta)) + ModelMetadata meta; + lock(cwt) { - meta = new Dictionary(); - cwt.Add(deserialized, meta); + if (!cwt.TryGetValue(model, out meta)) + { + meta = new ModelMetadata(); + cwt.Add(model, meta); + } } - if (!meta.ContainsKey(prop.Name)) // Temporary fix for non-standard Id reprecussions...this internal implementation will change soon anyway. - meta.Add(prop.Name, value); - + return meta; } - - internal Dictionary DeserializationMetadata(object deserialized) + private PropertyMetadata GetMetadataForProperty(object model, PropertyInfo prop) { - Dictionary retval; - if (cwt.TryGetValue(deserialized, out retval)) + ModelMetadata mmeta = GetMetadataForModel(model); + IDictionary pmetadict = mmeta.PropertyMetadata.Value; + PropertyMetadata pmeta; + lock (pmetadict) { - return retval; - } - else - { - //TODO: Throw an exception here? If you asked for metadata for an object and it's not found, something has probably gone pretty badly wrong! - return null; + if (!pmetadict.TryGetValue(prop, out pmeta)) + { + pmeta = new PropertyMetadata(); + pmetadict.Add(prop, pmeta); + } } + return pmeta; + } + + internal void SetPropertyWasPresent(object deserialized, PropertyInfo prop, bool value) + { + PropertyMetadata pmeta = GetMetadataForProperty(deserialized, prop); + pmeta.PresentInJson = value; } /// @@ -75,8 +100,8 @@ internal Dictionary DeserializationMetadata(object deserialized) [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool PropertyWasPresent(object deserialized, PropertyInfo prop) { - object throwaway; - return this.DeserializationMetadata(deserialized).TryGetValue(prop.Name, out throwaway); + return this.GetMetadataForProperty(deserialized, prop).PresentInJson; } + } } diff --git a/JSONAPI/Json/JsonApiFormatter.cs b/JSONAPI/Json/JsonApiFormatter.cs index a87d9674..e0f7a126 100644 --- a/JSONAPI/Json/JsonApiFormatter.cs +++ b/JSONAPI/Json/JsonApiFormatter.cs @@ -656,7 +656,7 @@ public object Deserialize(Type objectType, Stream readStream, JsonReader reader, prop.SetValue(retval, propVal, null); // Tell the MetadataManager that we deserialized this property - MetadataManager.Instance.SetMetaForProperty(retval, prop, true); + MetadataManager.Instance.SetPropertyWasPresent(retval, prop, true); // pop the value off the reader, so we catch the EndObject token below!. reader.Read(); @@ -805,7 +805,7 @@ private void DeserializeLinkedResources(object obj, Stream readStream, JsonReade } // Tell the MetadataManager that we deserialized this property - MetadataManager.Instance.SetMetaForProperty(obj, prop, true); + MetadataManager.Instance.SetPropertyWasPresent(obj, prop, true); } else reader.Skip(); From 83907d79ed81b07b8cbf0486a730afa95c47becc Mon Sep 17 00:00:00 2001 From: S'pht'Kr Date: Tue, 3 Feb 2015 13:08:31 +0100 Subject: [PATCH 2/2] Implemented serialization attribute overrides. Incomplete--you can set an override of JsonIgnore but you can't un-ignore a property, for instance. --- .../OverrideSerializationAttributesTest.json | 17 +++++++ JSONAPI.Tests/JSONAPI.Tests.csproj | 3 ++ JSONAPI.Tests/Json/LinkTemplateTests.cs | 28 ++++++++++- JSONAPI/Core/MetadataManager.cs | 47 +++++++++++++++++-- JSONAPI/Json/JsonApiFormatter.cs | 2 + 5 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 JSONAPI.Tests/Data/OverrideSerializationAttributesTest.json diff --git a/JSONAPI.Tests/Data/OverrideSerializationAttributesTest.json b/JSONAPI.Tests/Data/OverrideSerializationAttributesTest.json new file mode 100644 index 00000000..074bf20a --- /dev/null +++ b/JSONAPI.Tests/Data/OverrideSerializationAttributesTest.json @@ -0,0 +1,17 @@ +{ + "posts": { + "id": "2", + "title": "How to fry an egg", + "links": { + "author": "5" + } + }, + "linked": { + "users": [ + { + "id": "5", + "name": "Bob" + } + ] + } +} \ No newline at end of file diff --git a/JSONAPI.Tests/JSONAPI.Tests.csproj b/JSONAPI.Tests/JSONAPI.Tests.csproj index d61bedf1..e6ebb4ba 100644 --- a/JSONAPI.Tests/JSONAPI.Tests.csproj +++ b/JSONAPI.Tests/JSONAPI.Tests.csproj @@ -109,6 +109,9 @@ Always + + Always + Always diff --git a/JSONAPI.Tests/Json/LinkTemplateTests.cs b/JSONAPI.Tests/Json/LinkTemplateTests.cs index e5eb0d83..3dff8f47 100644 --- a/JSONAPI.Tests/Json/LinkTemplateTests.cs +++ b/JSONAPI.Tests/Json/LinkTemplateTests.cs @@ -61,7 +61,33 @@ public void GetResourceWithLinkTemplateRelationship() var expected = JsonHelpers.MinifyJson(File.ReadAllText("LinkTemplateTest.json")); var output = Encoding.ASCII.GetString(stream.ToArray()); Trace.WriteLine(output); - Assert.AreEqual(output.Trim(), expected); + Assert.AreEqual(expected,output.Trim()); + } + + [TestMethod] + [DeploymentItem(@"Data\OverrideSerializationAttributesTest.json")] + public void OverrideSerializationAttributesTest() + { + // Arrange + var formatter = new JsonApiFormatter + ( + new JSONAPI.Core.PluralizationService() + ); + var stream = new MemoryStream(); + + // Act + JSONAPI.Core.MetadataManager.Instance.SetPropertyAttributeOverrides( + ThePost, typeof(Post).GetProperty("Author"), + new SerializeAs(SerializeAsOptions.Ids), + new IncludeInPayload(true) + ); + formatter.WriteToStreamAsync(typeof(Post), ThePost, stream, null, null); + + // Assert + var expected = JsonHelpers.MinifyJson(File.ReadAllText("OverrideSerializationAttributesTest.json")); + var output = Encoding.ASCII.GetString(stream.ToArray()); + Trace.WriteLine(output); + Assert.AreEqual(expected, output.Trim()); } } } diff --git a/JSONAPI/Core/MetadataManager.cs b/JSONAPI/Core/MetadataManager.cs index 3b3141a9..2dbe5bd4 100644 --- a/JSONAPI/Core/MetadataManager.cs +++ b/JSONAPI/Core/MetadataManager.cs @@ -13,9 +13,9 @@ public sealed class MetadataManager private class PropertyMetadata { public bool PresentInJson { get; set; } // only meaningful for incoming/deserialized models! - public Lazy> AttributeOverrides - = new Lazy>( - () => new List() + public Lazy> AttributeOverrides + = new Lazy>( + () => new HashSet() ); } @@ -103,5 +103,46 @@ public bool PropertyWasPresent(object deserialized, PropertyInfo prop) return this.GetMetadataForProperty(deserialized, prop).PresentInJson; } + /// + /// Set different serialization attributes at runtime than those that were declared on + /// a property at compile time. E.g., if you declared a relationship property with + /// [SerializeAs(SerializeAsOptions.Link)] but you want to change that to + /// SerializeAsOptions.Ids when you are transmitting only one object, you can do: + /// + /// MetadataManager.SetPropertyAttributeOverrides( + /// theModelInstance, theProperty, + /// new SerializeAsAttribute(SerializeAsOptions.Ids) + /// ); + /// + /// Further, if you want to also include the related objects in the serialized document: + /// + /// MetadataManager.SetPropertyAttributeOverrides( + /// theModelInstance, theProperty, + /// new SerializeAs(SerializeAsOptions.Ids), + /// new IncludeInPayload(true) + /// ); + /// + /// Calling this function resets all overrides, so calling it twice will result in only + /// the second set of overrides being applied. At present, the order of the attributes + /// is not meaningful. + /// + /// The model object that is to be serialized, for which you want to change serialization behavior. + /// The property for which to override attributes. + /// One or more attribute instances that will override the declared behavior. + public void SetPropertyAttributeOverrides(object model, PropertyInfo prop, params System.Attribute[] attrs) + { + var aoverrides = this.GetMetadataForProperty(model, prop).AttributeOverrides.Value; + lock (aoverrides) + { + aoverrides.Clear(); + aoverrides.UnionWith(attrs); + } + } + + internal IEnumerable GetPropertyAttributeOverrides(object model, PropertyInfo prop) + { + return this.GetMetadataForProperty(model, prop).AttributeOverrides.Value; + } + } } diff --git a/JSONAPI/Json/JsonApiFormatter.cs b/JSONAPI/Json/JsonApiFormatter.cs index e0f7a126..40721aa0 100644 --- a/JSONAPI/Json/JsonApiFormatter.cs +++ b/JSONAPI/Json/JsonApiFormatter.cs @@ -252,6 +252,8 @@ protected void Serialize(object value, Stream writeStream, JsonWriter writer, Js SerializeAsOptions sa = SerializeAsOptions.Ids; object[] attrs = prop.GetCustomAttributes(true); + // aha...this way the overrides will be applied last! + attrs = attrs.Concat(MetadataManager.Instance.GetPropertyAttributeOverrides(value, prop)).ToArray(); foreach (object attr in attrs) {