Skip to content

Commit 9557b99

Browse files
committed
Merge branch 'fix/5.x-dictionary-serialization' into 5.x
2 parents e83d528 + 85c074a commit 9557b99

File tree

5 files changed

+391
-31
lines changed

5 files changed

+391
-31
lines changed

src/Nest/CommonAbstractions/SerializationBehavior/ElasticContractResolver.cs

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ public class ElasticContractResolver : DefaultContractResolver
1818
public static JsonSerializer Empty { get; } = new JsonSerializer();
1919

2020
/// <summary>
21-
/// ConnectionSettings can be requested by JsonConverter's.
21+
/// ConnectionSettings can be requested by JsonConverters.
2222
/// </summary>
23-
public IConnectionSettingsValues ConnectionSettings { get; private set; }
23+
public IConnectionSettingsValues ConnectionSettings { get; }
2424

2525
/// <summary>
2626
/// Signals to custom converter that it can get serialization state from one of the converters. Ugly but massive performance gain
@@ -40,21 +40,16 @@ protected override JsonContract CreateContract(Type objectType)
4040
{
4141
var contract = base.CreateContract(o);
4242

43-
if (typeof(IDictionary).IsAssignableFrom(o) && !typeof(IIsADictionary).IsAssignableFrom(o))
43+
if ((typeof(IDictionary).IsAssignableFrom(o) || o.IsGenericDictionary()) && !typeof(IIsADictionary).IsAssignableFrom(o))
4444
{
45-
if (!o.IsGeneric())
46-
contract.Converter = new VerbatimDictionaryKeysJsonConverter<object, object>();
45+
Type[] genericArguments;
46+
if (!o.TryGetGenericDictionaryArguments(out genericArguments))
47+
contract.Converter = new VerbatimDictionaryKeysJsonConverter();
4748
else
48-
{
49-
var genericArguments = o.GetGenericArguments();
50-
if (genericArguments.Length != 2)
51-
contract.Converter = new VerbatimDictionaryKeysJsonConverter<object, object>();
52-
else
53-
contract.Converter =
54-
(JsonConverter)typeof(VerbatimDictionaryKeysJsonConverter<,>).CreateGenericInstance(genericArguments);
55-
}
49+
contract.Converter =
50+
(JsonConverter)typeof(VerbatimDictionaryKeysJsonConverter<,>).CreateGenericInstance(genericArguments);
5651
}
57-
if (typeof(IEnumerable<QueryContainer>).IsAssignableFrom(o))
52+
else if (typeof(IEnumerable<QueryContainer>).IsAssignableFrom(o))
5853
contract.Converter = new QueryContainerCollectionJsonConverter();
5954
else if (o == typeof(ServerError))
6055
contract.Converter = new ServerErrorJsonConverter();

src/Nest/CommonAbstractions/SerializationBehavior/GenericJsonConverters/VerbatimDictionaryKeysConverter.cs

Lines changed: 111 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,105 @@ namespace Nest
1212
/// JSON converter for IDictionary that ignores the contract resolver (e.g. CamelCaseFieldNamesContractResolver)
1313
/// when converting dictionary keys to property names.
1414
/// </summary>
15+
internal class VerbatimDictionaryKeysJsonConverter : JsonConverter
16+
{
17+
public override bool CanConvert(Type t) => typeof(IDictionary).IsAssignableFrom(t);
18+
19+
public override bool CanRead => false;
20+
21+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
22+
{
23+
throw new NotSupportedException();
24+
}
25+
26+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
27+
{
28+
var dictionary = (IDictionary)value;
29+
if (dictionary == null)
30+
{
31+
writer.WriteNull();
32+
return;
33+
}
34+
35+
var settings = serializer.GetConnectionSettings();
36+
var seenEntries = new Dictionary<string, object>(dictionary.Count);
37+
38+
Field fieldName;
39+
PropertyName propertyName;
40+
IndexName indexName;
41+
TypeName typeName;
42+
43+
foreach (DictionaryEntry entry in dictionary)
44+
{
45+
if (entry.Value == null && serializer.NullValueHandling == NullValueHandling.Ignore)
46+
continue;
47+
string key;
48+
if (settings == null)
49+
key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
50+
else if (AsType(entry.Key, out fieldName))
51+
{
52+
key = settings.Inferrer.Field(fieldName);
53+
}
54+
else if (AsType(entry.Key, out propertyName))
55+
{
56+
if (propertyName?.Property != null)
57+
{
58+
IPropertyMapping mapping;
59+
if (settings.PropertyMappings.TryGetValue(propertyName.Property, out mapping) && mapping.Ignore)
60+
{
61+
continue;
62+
}
63+
}
64+
65+
key = settings.Inferrer.PropertyName(propertyName);
66+
}
67+
else if (AsType(entry.Key, out indexName))
68+
{
69+
key = settings.Inferrer.IndexName(indexName);
70+
}
71+
else if (AsType(entry.Key, out typeName))
72+
{
73+
key = settings.Inferrer.TypeName(typeName);
74+
}
75+
else
76+
key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
77+
78+
if (key != null)
79+
seenEntries[key] = entry.Value;
80+
}
81+
82+
writer.WriteStartObject();
83+
foreach (var entry in seenEntries)
84+
{
85+
writer.WritePropertyName(entry.Key);
86+
serializer.Serialize(writer, entry.Value);
87+
}
88+
writer.WriteEndObject();
89+
}
90+
91+
private static bool AsType<T>(object value, out T convertedValue) where T : class
92+
{
93+
convertedValue = value as T;
94+
return convertedValue != null;
95+
}
96+
}
97+
98+
/// <summary>
99+
/// JSON converter for IDictionary&lt;TKey,TValue&gt; and IReadOnlyDictionary&lt;TKey,TValue&gt;
100+
/// that ignores the contract resolver (e.g. CamelCaseFieldNamesContractResolver)
101+
/// when converting dictionary keys to property names.
102+
/// </summary>
15103
internal class VerbatimDictionaryKeysJsonConverter<TKey, TValue> : JsonConverter
16104
{
17-
public override bool CanConvert(Type t) => typeof (IDictionary<TKey, TValue>).IsAssignableFrom(t);
105+
private readonly bool _keyIsString = typeof(TKey) == typeof(string);
106+
private readonly bool _keyIsField = typeof(TKey) == typeof(Field);
107+
private readonly bool _keyIsPropertyName = typeof(TKey) == typeof(PropertyName);
108+
private readonly bool _keyIsIndexName = typeof(TKey) == typeof(IndexName);
109+
private readonly bool _keyIsTypeName = typeof(TKey) == typeof(TypeName);
110+
111+
public override bool CanConvert(Type t) =>
112+
typeof(IDictionary<TKey, TValue>).IsAssignableFrom(t) ||
113+
typeof(IReadOnlyDictionary<TKey, TValue>).IsAssignableFrom(t);
18114

19115
public override bool CanRead => false;
20116

@@ -25,32 +121,31 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist
25121

26122
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
27123
{
28-
var dictionary = value as IDictionary<TKey, TValue>;
29-
if (dictionary == null) return;
124+
var enumerable = (IEnumerable<KeyValuePair<TKey, TValue>>)value;
125+
if (enumerable == null)
126+
{
127+
writer.WriteNull();
128+
return;
129+
}
30130

31131
var settings = serializer.GetConnectionSettings();
32-
var seenEntries = new Dictionary<string, TValue>(dictionary.Count);
33-
var keyIsString = typeof(TKey) == typeof(string);
34-
var keyIsField = typeof(TKey) == typeof(Field);
35-
var keyIsPropertyName = typeof(TKey) == typeof(PropertyName);
36-
var keyIsIndexName = typeof(TKey) == typeof(IndexName);
37-
var keyIsTypeName = typeof(TKey) == typeof(TypeName);
38-
39-
foreach (var entry in dictionary)
132+
var seenEntries = new Dictionary<string, TValue>(enumerable.Count());
133+
134+
foreach (var entry in enumerable)
40135
{
41136
if (entry.Value == null && serializer.NullValueHandling == NullValueHandling.Ignore)
42137
continue;
43138
string key;
44-
if (keyIsString)
139+
if (_keyIsString)
45140
key = entry.Key?.ToString();
46141
else if (settings == null)
47142
key = Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
48-
else if (keyIsField)
143+
else if (_keyIsField)
49144
{
50145
var fieldName = entry.Key as Field;
51146
key = settings.Inferrer.Field(fieldName);
52147
}
53-
else if (keyIsPropertyName)
148+
else if (_keyIsPropertyName)
54149
{
55150
var propertyName = entry.Key as PropertyName;
56151
if (propertyName?.Property != null)
@@ -64,12 +159,12 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
64159

65160
key = settings.Inferrer.PropertyName(propertyName);
66161
}
67-
else if (keyIsIndexName)
162+
else if (_keyIsIndexName)
68163
{
69164
var indexName = entry.Key as IndexName;
70165
key = settings.Inferrer.IndexName(indexName);
71166
}
72-
else if (keyIsTypeName)
167+
else if (_keyIsTypeName)
73168
{
74169
var typeName = entry.Key as TypeName;
75170
key = settings.Inferrer.TypeName(typeName);

src/Nest/CrossPlatform/TypeExtensions.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,35 @@ internal static bool IsGeneric(this Type type)
1313
#if DOTNETCORE
1414
return type.GetTypeInfo().IsGenericType;
1515
#else
16-
return type.IsGenericType;
16+
return type.IsGenericType;
1717
#endif
1818
}
1919

20+
internal static bool IsGenericDictionary(this Type type)
21+
{
22+
return type.GetInterfaces().Any(t =>
23+
t.IsGeneric() && (
24+
t.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
25+
t.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
26+
}
27+
28+
internal static bool TryGetGenericDictionaryArguments(this Type type, out Type[] genericArguments)
29+
{
30+
var genericDictionary = type.GetInterfaces().FirstOrDefault(t =>
31+
t.IsGeneric() && (
32+
t.GetGenericTypeDefinition() == typeof(IDictionary<,>) ||
33+
t.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)));
34+
35+
if (genericDictionary == null)
36+
{
37+
genericArguments = new Type[0];
38+
return false;
39+
}
40+
41+
genericArguments = genericDictionary.GetGenericArguments();
42+
return true;
43+
}
44+
2045
internal static bool IsValue(this Type type)
2146
{
2247
#if DOTNETCORE

0 commit comments

Comments
 (0)