diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerCollectionsTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerCollectionsTests.cs index 45d0eed5..e983917d 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerCollectionsTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerCollectionsTests.cs @@ -61,6 +61,20 @@ namespace Newtonsoft.Json.Tests.Serialization [TestFixture] public class JsonSerializerCollectionsTests : TestFixtureBase { +#if !(NET35 || NET20 || PORTABLE || PORTABLE40) || NETSTANDARD2_0 + [Test] + public void DeserializeConcurrentDictionaryWithNullValue() + { + const string key = "id"; + + var jsonValue = $"{{\"{key}\":null}}"; + + var deserializedObject = JsonConvert.DeserializeObject>(jsonValue); + + Assert.IsNull(deserializedObject[key]); + } +#endif + #if !(NET20 || NET35) [Test] public void SerializeConcurrentQueue() diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index f37c079b..eebca012 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -900,7 +900,7 @@ namespace Newtonsoft.Json.Serialization case "System.Collections.Concurrent.ConcurrentQueue`1": case "System.Collections.Concurrent.ConcurrentStack`1": case "System.Collections.Concurrent.ConcurrentBag`1": - case "System.Collections.Concurrent.ConcurrentDictionary`2": + case JsonTypeReflector.ConcurrentDictionaryTypeName: case "System.Collections.ObjectModel.ObservableCollection`1": return true; } diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index 486b0e0c..c486be2b 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -127,10 +127,22 @@ namespace Newtonsoft.Json.Serialization { CreatedType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); } + else if (underlyingType.IsGenericType()) + { + // ConcurrentDictionary<,> + IDictionary setter + null value = error + // wrap to use generic setter + // https://github.com/JamesNK/Newtonsoft.Json/issues/1582 + Type typeDefinition = underlyingType.GetGenericTypeDefinition(); + if (typeDefinition.FullName == JsonTypeReflector.ConcurrentDictionaryTypeName) + { + ShouldCreateWrapper = true; + } + } #if HAVE_READ_ONLY_COLLECTIONS IsReadOnlyOrFixedSize = ReflectionUtils.InheritsGenericDefinition(underlyingType, typeof(ReadOnlyDictionary<,>)); #endif + } #if HAVE_READ_ONLY_COLLECTIONS else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(IReadOnlyDictionary<,>), out _genericCollectionDefinitionType)) @@ -172,7 +184,10 @@ namespace Newtonsoft.Json.Serialization #endif } - ShouldCreateWrapper = !typeof(IDictionary).IsAssignableFrom(CreatedType); + if (!typeof(IDictionary).IsAssignableFrom(CreatedType)) + { + ShouldCreateWrapper = true; + } DictionaryKeyType = keyType; DictionaryValueType = valueType; diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index a16c534a..a98ea366 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -56,6 +56,8 @@ namespace Newtonsoft.Json.Serialization public const string ShouldSerializePrefix = "ShouldSerialize"; public const string SpecifiedPostfix = "Specified"; + public const string ConcurrentDictionaryTypeName = "System.Collections.Concurrent.ConcurrentDictionary`2"; + private static readonly ThreadSafeStore> CreatorCache = new ThreadSafeStore>(GetCreator);