From 29567c5b5d4e48bcc4095a76eb78bc22d0f6489b Mon Sep 17 00:00:00 2001 From: Lowell Smith Date: Fri, 5 Feb 2016 15:05:42 -0500 Subject: [PATCH 1/2] Fixed an issue where iterating extensionDataGetter's enumerable multiple times or using LINQ statement on it caused inconsistant data to be returned. --- .../Serialization/ContractResolverTests.cs | 27 +++++++++++++ .../Serialization/DefaultContractResolver.cs | 38 ++++--------------- 2 files changed, 35 insertions(+), 30 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs index 6b71dd21..4f49e979 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs @@ -614,5 +614,32 @@ namespace Newtonsoft.Json.Tests.Serialization } #pragma warning restore 618 #endif + + public class ClassWithExtensionData + { + [JsonExtensionData] + public IDictionary Data { get; set; } + } + + [Test] + public void ExtensionDataGetterCanBeIteratedMultipleTimes() + { + var resolver = new DefaultContractResolver(); + var contract = (JsonObjectContract)resolver.ResolveContract(typeof(ClassWithExtensionData)); + + var myClass = new ClassWithExtensionData + { + Data = new Dictionary + { + { "SomeField", "Field" }, + } + }; + + var getter = contract.ExtensionDataGetter; + + var extensionData = getter(myClass); + Assert.IsTrue(extensionData.Any()); + Assert.IsTrue(extensionData.Any()); // second test fails if the enumerator returned isn't reset + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index d65a4b4d..9c5ef398 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -576,49 +576,27 @@ namespace Newtonsoft.Json.Serialization contract.ExtensionDataValueType = valueType; } - internal struct DictionaryEnumerator : IEnumerable>, IEnumerator> + internal struct DictionaryEnumerator : IEnumerable> { - private readonly IEnumerator> _e; + private readonly IEnumerable> _e; public DictionaryEnumerator(IEnumerable> e) { ValidationUtils.ArgumentNotNull(e, nameof(e)); - _e = e.GetEnumerator(); - } - - public bool MoveNext() - { - return _e.MoveNext(); - } - - public void Reset() - { - _e.Reset(); - } - - public KeyValuePair Current - { - get { return new KeyValuePair(_e.Current.Key, _e.Current.Value); } - } - - public void Dispose() - { - _e.Dispose(); - } - - object IEnumerator.Current - { - get { return Current; } + _e = e; } public IEnumerator> GetEnumerator() { - return this; + foreach (var item in _e) + { + yield return new KeyValuePair(item.Key, item.Value); + } } IEnumerator IEnumerable.GetEnumerator() { - return this; + return GetEnumerator(); } } From 196d6a274b1ee6da59446855ab1946a5e5062ef5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 6 Feb 2016 10:47:44 +1300 Subject: [PATCH 2/2] -Fixed iterating over extension data multiple times --- .../Serialization/ContractResolverTests.cs | 14 +++++++++----- .../Serialization/DefaultContractResolver.cs | 10 ++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs index 4f49e979..85b3f3ea 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs @@ -624,10 +624,10 @@ namespace Newtonsoft.Json.Tests.Serialization [Test] public void ExtensionDataGetterCanBeIteratedMultipleTimes() { - var resolver = new DefaultContractResolver(); - var contract = (JsonObjectContract)resolver.ResolveContract(typeof(ClassWithExtensionData)); + DefaultContractResolver resolver = new DefaultContractResolver(); + JsonObjectContract contract = (JsonObjectContract)resolver.ResolveContract(typeof(ClassWithExtensionData)); - var myClass = new ClassWithExtensionData + ClassWithExtensionData myClass = new ClassWithExtensionData { Data = new Dictionary { @@ -635,9 +635,13 @@ namespace Newtonsoft.Json.Tests.Serialization } }; - var getter = contract.ExtensionDataGetter; + ExtensionDataGetter getter = contract.ExtensionDataGetter; - var extensionData = getter(myClass); + IEnumerable> dictionaryData = getter(myClass).ToDictionary(kv => kv.Key, kv => kv.Value); + Assert.IsTrue(dictionaryData.Any()); + Assert.IsTrue(dictionaryData.Any()); + + IEnumerable> extensionData = getter(myClass); Assert.IsTrue(extensionData.Any()); Assert.IsTrue(extensionData.Any()); // second test fails if the enumerator returned isn't reset } diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 9c5ef398..bf970338 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -555,7 +555,7 @@ namespace Newtonsoft.Json.Serialization if (extensionDataAttribute.WriteData) { - Type enumerableWrapper = typeof(DictionaryEnumerator<,>).MakeGenericType(keyType, valueType); + Type enumerableWrapper = typeof(EnumerableDictionaryWrapper<,>).MakeGenericType(keyType, valueType); ConstructorInfo constructors = enumerableWrapper.GetConstructors().First(); ObjectConstructor createEnumerableWrapper = JsonTypeReflector.ReflectionDelegateFactory.CreateParameterizedConstructor(constructors); @@ -576,11 +576,13 @@ namespace Newtonsoft.Json.Serialization contract.ExtensionDataValueType = valueType; } - internal struct DictionaryEnumerator : IEnumerable> + // leave as class instead of struct + // will be always return as an interface and boxed + internal class EnumerableDictionaryWrapper : IEnumerable> { private readonly IEnumerable> _e; - public DictionaryEnumerator(IEnumerable> e) + public EnumerableDictionaryWrapper(IEnumerable> e) { ValidationUtils.ArgumentNotNull(e, nameof(e)); _e = e; @@ -588,7 +590,7 @@ namespace Newtonsoft.Json.Serialization public IEnumerator> GetEnumerator() { - foreach (var item in _e) + foreach (KeyValuePair item in _e) { yield return new KeyValuePair(item.Key, item.Value); }