Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Follow up on duplicate key checking #771

Merged
merged 1 commit into from
Jan 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
stan-sz marked this conversation as resolved.
Show resolved Hide resolved
{
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)
EdwardCooke marked this conversation as resolved.
Show resolved Hide resolved
: base(objectFactory)
public StaticDictionaryNodeDeserializer(ObjectFactories.StaticObjectFactory objectFactory, bool duplicateKeyChecking)
: base(objectFactory, duplicateKeyChecking)
{
_objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
}
Expand Down