Skip to content

Commit 65c2aa9

Browse files
authored
Merge pull request #771 from stan-sz/WithDuplicateKeyChecking
Follow up on duplicate key checking
2 parents d079a3c + 96fe6c5 commit 65c2aa9

File tree

5 files changed

+38
-14
lines changed

5 files changed

+38
-14
lines changed

YamlDotNet.Test/Serialization/DeserializerTest.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,13 @@ public void DeserializeWithDuplicateKeyChecking_YamlWithDuplicateKeys_ThrowsYaml
299299

300300
Action act = () => sut.Deserialize<Person>(yaml);
301301
act.ShouldThrow<YamlException>("Because there are duplicate name keys");
302+
act = () => sut.Deserialize<IDictionary<object, object>>(yaml);
303+
act.ShouldThrow<YamlException>("Because there are duplicate name keys");
304+
305+
var stream = Yaml.ReaderFrom("backreference.yaml");
306+
var parser = new MergingParser(new Parser(stream));
307+
act = () => sut.Deserialize<Dictionary<string, Dictionary<string, string>>>(parser);
308+
act.ShouldThrow<YamlException>("Because there are duplicate name keys");
302309
}
303310

304311
[Fact]
@@ -316,6 +323,13 @@ public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNot
316323

317324
Action act = () => sut.Deserialize<Person>(yaml);
318325
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");
326+
act = () => sut.Deserialize<IDictionary<object, object>>(yaml);
327+
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");
328+
329+
var stream = Yaml.ReaderFrom("backreference.yaml");
330+
var parser = new MergingParser(new Parser(stream));
331+
act = () => sut.Deserialize<Dictionary<string, Dictionary<string, string>>>(parser);
332+
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");
319333
}
320334

321335
public class Test

YamlDotNet/Serialization/DeserializerBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public DeserializerBuilder()
9191
{ typeof(NullNodeDeserializer), _ => new NullNodeDeserializer() },
9292
{ typeof(ScalarNodeDeserializer), _ => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization) },
9393
{ typeof(ArrayNodeDeserializer), _ => new ArrayNodeDeserializer() },
94-
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value) },
94+
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value, duplicateKeyChecking) },
9595
{ typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value) },
9696
{ typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() },
9797
{ typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking) }
@@ -151,7 +151,7 @@ public DeserializerBuilder WithStaticContext(StaticContext context)
151151
_baseTypeInspector = context.GetTypeInspector();
152152

153153
WithoutNodeDeserializer<DictionaryNodeDeserializer>();
154-
WithNodeDeserializer(new StaticDictionaryNodeDeserializer(context.GetFactory()));
154+
WithNodeDeserializer(new StaticDictionaryNodeDeserializer(context.GetFactory(), duplicateKeyChecking));
155155

156156
return Self;
157157
}

YamlDotNet/Serialization/NodeDeserializers/DictionaryNodeDeserializer.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ namespace YamlDotNet.Serialization.NodeDeserializers
3232
public class DictionaryNodeDeserializer : INodeDeserializer
3333
{
3434
private readonly IObjectFactory objectFactory;
35+
private readonly bool duplicateKeyChecking;
3536

36-
public DictionaryNodeDeserializer(IObjectFactory objectFactory)
37+
public DictionaryNodeDeserializer(IObjectFactory objectFactory, bool duplicateKeyChecking)
3738
{
3839
this.objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
40+
this.duplicateKeyChecking = duplicateKeyChecking;
3941
}
4042

4143
public virtual bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
@@ -77,9 +79,18 @@ public virtual bool Deserialize(IParser parser, Type expectedType, Func<IParser,
7779
return true;
7880
}
7981

82+
private void TryAssign(IDictionary result, object key, object value, MappingStart propertyName)
83+
{
84+
if (duplicateKeyChecking && result.Contains(key))
85+
{
86+
throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate key {key}");
87+
}
88+
result[key] = value!;
89+
}
90+
8091
protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser, Type, object?> nestedObjectDeserializer, IDictionary result)
8192
{
82-
parser.Consume<MappingStart>();
93+
var property = parser.Consume<MappingStart>();
8394
while (!parser.TryConsume<MappingEnd>(out var _))
8495
{
8596
var key = nestedObjectDeserializer(parser, tKey);
@@ -91,7 +102,7 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
91102
if (valuePromise == null)
92103
{
93104
// Key is pending, value is known
94-
keyPromise.ValueAvailable += v => result[v!] = value!;
105+
keyPromise.ValueAvailable += v => TryAssign(result, v!, value!, property);
95106
}
96107
else
97108
{
@@ -102,7 +113,7 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
102113
{
103114
if (hasFirstPart)
104115
{
105-
result[v!] = value!;
116+
TryAssign(result, v!, value!, property);
106117
}
107118
else
108119
{
@@ -115,7 +126,7 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
115126
{
116127
if (hasFirstPart)
117128
{
118-
result[key] = v!;
129+
TryAssign(result, key, v!, property);
119130
}
120131
else
121132
{
@@ -135,12 +146,12 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
135146
if (valuePromise == null)
136147
{
137148
// Happy path: both key and value are known
138-
result[key] = value!;
149+
TryAssign(result, key, value!, property);
139150
}
140151
else
141152
{
142153
// Key is known, value is pending
143-
valuePromise.ValueAvailable += v => result[key!] = v!;
154+
valuePromise.ValueAvailable += v => TryAssign(result, key!, v!, property);
144155
}
145156
}
146157
}

YamlDotNet/Serialization/NodeDeserializers/ObjectNodeDeserializer.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,16 @@ public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, o
5555
var implementationType = Nullable.GetUnderlyingType(expectedType) ?? expectedType;
5656

5757
value = objectFactory.Create(implementationType);
58-
var consumedProperties = new List<string>();
58+
var consumedProperties = new HashSet<string>(StringComparer.Ordinal);
5959
while (!parser.TryConsume<MappingEnd>(out var _))
6060
{
6161
var propertyName = parser.Consume<Scalar>();
62-
if (duplicateKeyChecking && consumedProperties.Contains(propertyName.Value))
62+
if (duplicateKeyChecking && !consumedProperties.Add(propertyName.Value))
6363
{
6464
throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate key {propertyName.Value}");
6565
}
6666
try
6767
{
68-
consumedProperties.Add(propertyName.Value);
6968
var property = typeDescriptor.GetProperty(implementationType, null, propertyName.Value, ignoreUnmatched);
7069
if (property == null)
7170
{

YamlDotNet/Serialization/NodeDeserializers/StaticDictionaryNodeDeserializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public class StaticDictionaryNodeDeserializer : DictionaryNodeDeserializer
2929
{
3030
private readonly ObjectFactories.StaticObjectFactory _objectFactory;
3131

32-
public StaticDictionaryNodeDeserializer(ObjectFactories.StaticObjectFactory objectFactory)
33-
: base(objectFactory)
32+
public StaticDictionaryNodeDeserializer(ObjectFactories.StaticObjectFactory objectFactory, bool duplicateKeyChecking)
33+
: base(objectFactory, duplicateKeyChecking)
3434
{
3535
_objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
3636
}

0 commit comments

Comments
 (0)