Skip to content

Commit

Permalink
Merge pull request #771 from stan-sz/WithDuplicateKeyChecking
Browse files Browse the repository at this point in the history
Follow up on duplicate key checking
  • Loading branch information
EdwardCooke authored Jan 28, 2023
2 parents d079a3c + 96fe6c5 commit 65c2aa9
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 14 deletions.
14 changes: 14 additions & 0 deletions YamlDotNet.Test/Serialization/DeserializerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,13 @@ public void DeserializeWithDuplicateKeyChecking_YamlWithDuplicateKeys_ThrowsYaml

Action act = () => sut.Deserialize<Person>(yaml);
act.ShouldThrow<YamlException>("Because there are duplicate name keys");
act = () => sut.Deserialize<IDictionary<object, object>>(yaml);
act.ShouldThrow<YamlException>("Because there are duplicate name keys");

var stream = Yaml.ReaderFrom("backreference.yaml");
var parser = new MergingParser(new Parser(stream));
act = () => sut.Deserialize<Dictionary<string, Dictionary<string, string>>>(parser);
act.ShouldThrow<YamlException>("Because there are duplicate name keys");
}

[Fact]
Expand All @@ -316,6 +323,13 @@ public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNot

Action act = () => sut.Deserialize<Person>(yaml);
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");
act = () => sut.Deserialize<IDictionary<object, object>>(yaml);
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");

var stream = Yaml.ReaderFrom("backreference.yaml");
var parser = new MergingParser(new Parser(stream));
act = () => sut.Deserialize<Dictionary<string, Dictionary<string, string>>>(parser);
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");
}

public class Test
Expand Down
4 changes: 2 additions & 2 deletions YamlDotNet/Serialization/DeserializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public DeserializerBuilder()
{ typeof(NullNodeDeserializer), _ => new NullNodeDeserializer() },
{ typeof(ScalarNodeDeserializer), _ => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization) },
{ typeof(ArrayNodeDeserializer), _ => new ArrayNodeDeserializer() },
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value) },
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value, duplicateKeyChecking) },
{ typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value) },
{ typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() },
{ typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking) }
Expand Down Expand Up @@ -151,7 +151,7 @@ public DeserializerBuilder WithStaticContext(StaticContext context)
_baseTypeInspector = context.GetTypeInspector();

WithoutNodeDeserializer<DictionaryNodeDeserializer>();
WithNodeDeserializer(new StaticDictionaryNodeDeserializer(context.GetFactory()));
WithNodeDeserializer(new StaticDictionaryNodeDeserializer(context.GetFactory(), duplicateKeyChecking));

return Self;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ namespace YamlDotNet.Serialization.NodeDeserializers
public class DictionaryNodeDeserializer : INodeDeserializer
{
private readonly IObjectFactory objectFactory;
private readonly bool duplicateKeyChecking;

public DictionaryNodeDeserializer(IObjectFactory objectFactory)
public DictionaryNodeDeserializer(IObjectFactory objectFactory, bool duplicateKeyChecking)
{
this.objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
this.duplicateKeyChecking = duplicateKeyChecking;
}

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

private void TryAssign(IDictionary result, object key, object value, MappingStart propertyName)
{
if (duplicateKeyChecking && result.Contains(key))
{
throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate key {key}");
}
result[key] = value!;
}

protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser, Type, object?> nestedObjectDeserializer, IDictionary result)
{
parser.Consume<MappingStart>();
var property = parser.Consume<MappingStart>();
while (!parser.TryConsume<MappingEnd>(out var _))
{
var key = nestedObjectDeserializer(parser, tKey);
Expand All @@ -91,7 +102,7 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
if (valuePromise == null)
{
// Key is pending, value is known
keyPromise.ValueAvailable += v => result[v!] = value!;
keyPromise.ValueAvailable += v => TryAssign(result, v!, value!, property);
}
else
{
Expand All @@ -102,7 +113,7 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
{
if (hasFirstPart)
{
result[v!] = value!;
TryAssign(result, v!, value!, property);
}
else
{
Expand All @@ -115,7 +126,7 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
{
if (hasFirstPart)
{
result[key] = v!;
TryAssign(result, key, v!, property);
}
else
{
Expand All @@ -135,12 +146,12 @@ protected void Deserialize(Type tKey, Type tValue, IParser parser, Func<IParser,
if (valuePromise == null)
{
// Happy path: both key and value are known
result[key] = value!;
TryAssign(result, key, value!, property);
}
else
{
// Key is known, value is pending
valuePromise.ValueAvailable += v => result[key!] = v!;
valuePromise.ValueAvailable += v => TryAssign(result, key!, v!, property);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,16 @@ public bool Deserialize(IParser parser, Type expectedType, Func<IParser, Type, o
var implementationType = Nullable.GetUnderlyingType(expectedType) ?? expectedType;

value = objectFactory.Create(implementationType);
var consumedProperties = new List<string>();
var consumedProperties = new HashSet<string>(StringComparer.Ordinal);
while (!parser.TryConsume<MappingEnd>(out var _))
{
var propertyName = parser.Consume<Scalar>();
if (duplicateKeyChecking && consumedProperties.Contains(propertyName.Value))
if (duplicateKeyChecking && !consumedProperties.Add(propertyName.Value))
{
throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate key {propertyName.Value}");
}
try
{
consumedProperties.Add(propertyName.Value);
var property = typeDescriptor.GetProperty(implementationType, null, propertyName.Value, ignoreUnmatched);
if (property == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public class StaticDictionaryNodeDeserializer : DictionaryNodeDeserializer
{
private readonly ObjectFactories.StaticObjectFactory _objectFactory;

public StaticDictionaryNodeDeserializer(ObjectFactories.StaticObjectFactory objectFactory)
: base(objectFactory)
public StaticDictionaryNodeDeserializer(ObjectFactories.StaticObjectFactory objectFactory, bool duplicateKeyChecking)
: base(objectFactory, duplicateKeyChecking)
{
_objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
}
Expand Down

0 comments on commit 65c2aa9

Please sign in to comment.