Skip to content

Commit 1403f5d

Browse files
authored
Fix serializing nullable struct dictionaries (#2452)
1 parent 60be32f commit 1403f5d

File tree

2 files changed

+109
-12
lines changed

2 files changed

+109
-12
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#region License
2+
// Copyright (c) 2007 James Newton-King
3+
//
4+
// Permission is hereby granted, free of charge, to any person
5+
// obtaining a copy of this software and associated documentation
6+
// files (the "Software"), to deal in the Software without
7+
// restriction, including without limitation the rights to use,
8+
// copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the
10+
// Software is furnished to do so, subject to the following
11+
// conditions:
12+
//
13+
// The above copyright notice and this permission notice shall be
14+
// included in all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23+
// OTHER DEALINGS IN THE SOFTWARE.
24+
#endregion
25+
26+
#if (NET45 || NET50)
27+
#if DNXCORE50
28+
using Xunit;
29+
using Test = Xunit.FactAttribute;
30+
using Assert = Newtonsoft.Json.Tests.XUnitAssert;
31+
#else
32+
using NUnit.Framework;
33+
#endif
34+
using System.Collections.Generic;
35+
using Newtonsoft.Json.Serialization;
36+
using Newtonsoft.Json.Converters;
37+
using System.Collections;
38+
using System;
39+
40+
namespace Newtonsoft.Json.Tests.Issues
41+
{
42+
[TestFixture]
43+
public class Issue2450
44+
{
45+
[Test]
46+
public void Test()
47+
{
48+
var resolver = new DefaultContractResolver();
49+
JsonContract contract;
50+
51+
contract = resolver.ResolveContract(typeof(Dict));
52+
Assert.IsTrue(contract is JsonDictionaryContract);
53+
54+
contract = resolver.ResolveContract(typeof(Dict?));
55+
Assert.IsTrue(contract is JsonDictionaryContract);
56+
}
57+
58+
[Test]
59+
public void Test_Serialize()
60+
{
61+
Dict d = new Dict(new Dictionary<string, object>
62+
{
63+
["prop1"] = 1,
64+
["prop2"] = 2
65+
});
66+
67+
string json = JsonConvert.SerializeObject(d);
68+
Assert.AreEqual(@"{""prop1"":1,""prop2"":2}", json);
69+
}
70+
71+
[Test]
72+
public void Test_Deserialize()
73+
{
74+
string json = @"{""prop1"":1,""prop2"":2}";
75+
76+
var d = JsonConvert.DeserializeObject<Dict?>(json);
77+
Assert.AreEqual(1, d.Value["prop1"]);
78+
Assert.AreEqual(2, d.Value["prop2"]);
79+
}
80+
81+
public struct Dict : IReadOnlyDictionary<string, object>
82+
{
83+
private readonly IDictionary<string, object> _dict;
84+
public Dict(IDictionary<string, object> dict) => _dict = dict;
85+
86+
public object this[string key] => _dict[key];
87+
public IEnumerable<string> Keys => _dict.Keys;
88+
public IEnumerable<object> Values => _dict.Values;
89+
public int Count => _dict.Count;
90+
public bool ContainsKey(string key) => _dict.ContainsKey(key);
91+
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() => _dict.GetEnumerator();
92+
public bool TryGetValue(string key, out object value) => _dict.TryGetValue(key, out value);
93+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
94+
}
95+
}
96+
}
97+
#endif

Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,39 +118,39 @@ public JsonDictionaryContract(Type underlyingType)
118118
Type? keyType;
119119
Type? valueType;
120120

121-
if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(IDictionary<,>), out _genericCollectionDefinitionType))
121+
if (ReflectionUtils.ImplementsGenericDefinition(NonNullableUnderlyingType, typeof(IDictionary<,>), out _genericCollectionDefinitionType))
122122
{
123123
keyType = _genericCollectionDefinitionType.GetGenericArguments()[0];
124124
valueType = _genericCollectionDefinitionType.GetGenericArguments()[1];
125125

126-
if (ReflectionUtils.IsGenericDefinition(UnderlyingType, typeof(IDictionary<,>)))
126+
if (ReflectionUtils.IsGenericDefinition(NonNullableUnderlyingType, typeof(IDictionary<,>)))
127127
{
128128
CreatedType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
129129
}
130-
else if (underlyingType.IsGenericType())
130+
else if (NonNullableUnderlyingType.IsGenericType())
131131
{
132132
// ConcurrentDictionary<,> + IDictionary setter + null value = error
133133
// wrap to use generic setter
134134
// https://github.com/JamesNK/Newtonsoft.Json/issues/1582
135-
Type typeDefinition = underlyingType.GetGenericTypeDefinition();
135+
Type typeDefinition = NonNullableUnderlyingType.GetGenericTypeDefinition();
136136
if (typeDefinition.FullName == JsonTypeReflector.ConcurrentDictionaryTypeName)
137137
{
138138
ShouldCreateWrapper = true;
139139
}
140140
}
141141

142142
#if HAVE_READ_ONLY_COLLECTIONS
143-
IsReadOnlyOrFixedSize = ReflectionUtils.InheritsGenericDefinition(underlyingType, typeof(ReadOnlyDictionary<,>));
143+
IsReadOnlyOrFixedSize = ReflectionUtils.InheritsGenericDefinition(NonNullableUnderlyingType, typeof(ReadOnlyDictionary<,>));
144144
#endif
145145

146146
}
147147
#if HAVE_READ_ONLY_COLLECTIONS
148-
else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(IReadOnlyDictionary<,>), out _genericCollectionDefinitionType))
148+
else if (ReflectionUtils.ImplementsGenericDefinition(NonNullableUnderlyingType, typeof(IReadOnlyDictionary<,>), out _genericCollectionDefinitionType))
149149
{
150150
keyType = _genericCollectionDefinitionType.GetGenericArguments()[0];
151151
valueType = _genericCollectionDefinitionType.GetGenericArguments()[1];
152152

153-
if (ReflectionUtils.IsGenericDefinition(UnderlyingType, typeof(IReadOnlyDictionary<,>)))
153+
if (ReflectionUtils.IsGenericDefinition(NonNullableUnderlyingType, typeof(IReadOnlyDictionary<,>)))
154154
{
155155
CreatedType = typeof(ReadOnlyDictionary<,>).MakeGenericType(keyType, valueType);
156156
}
@@ -160,9 +160,9 @@ public JsonDictionaryContract(Type underlyingType)
160160
#endif
161161
else
162162
{
163-
ReflectionUtils.GetDictionaryKeyValueTypes(UnderlyingType, out keyType, out valueType);
163+
ReflectionUtils.GetDictionaryKeyValueTypes(NonNullableUnderlyingType, out keyType, out valueType);
164164

165-
if (UnderlyingType == typeof(IDictionary))
165+
if (NonNullableUnderlyingType == typeof(IDictionary))
166166
{
167167
CreatedType = typeof(Dictionary<object, object>);
168168
}
@@ -176,9 +176,9 @@ public JsonDictionaryContract(Type underlyingType)
176176
typeof(IDictionary<,>).MakeGenericType(keyType, valueType));
177177

178178
#if HAVE_FSHARP_TYPES
179-
if (!HasParameterizedCreatorInternal && underlyingType.Name == FSharpUtils.FSharpMapTypeName)
179+
if (!HasParameterizedCreatorInternal && NonNullableUnderlyingType.Name == FSharpUtils.FSharpMapTypeName)
180180
{
181-
FSharpUtils.EnsureInitialized(underlyingType.Assembly());
181+
FSharpUtils.EnsureInitialized(NonNullableUnderlyingType.Assembly());
182182
_parameterizedCreator = FSharpUtils.Instance.CreateMap(keyType, valueType);
183183
}
184184
#endif
@@ -207,7 +207,7 @@ public JsonDictionaryContract(Type underlyingType)
207207
if (DictionaryKeyType != null &&
208208
DictionaryValueType != null &&
209209
ImmutableCollectionsUtils.TryBuildImmutableForDictionaryContract(
210-
underlyingType,
210+
NonNullableUnderlyingType,
211211
DictionaryKeyType,
212212
DictionaryValueType,
213213
out Type? immutableCreatedType,

0 commit comments

Comments
 (0)