Skip to content

Commit

Permalink
Merge pull request #750 from nuttytree/f/throw-exception-for-non-uniq…
Browse files Browse the repository at this point in the history
…ue-keys

Throw an exception on duplicate keys

+semver:fix
  • Loading branch information
EdwardCooke committed Dec 14, 2022
2 parents 1e99937 + fd2bd26 commit 6bf5b44
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 3 deletions.
37 changes: 36 additions & 1 deletion YamlDotNet.Test/Serialization/DeserializerTest.cs
Expand Up @@ -24,6 +24,7 @@
using System.Linq;
using FluentAssertions;
using Xunit;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

Expand Down Expand Up @@ -216,7 +217,6 @@ public void NewLinesInKeys()
Assert.Equal($"value\na\nb", dictionary.First().Value);
}


[Theory]
[InlineData(".nan", System.Single.NaN)]
[InlineData(".NaN", System.Single.NaN)]
Expand Down Expand Up @@ -283,6 +283,41 @@ public void DeserializeScalarEdgeCases(IConvertible value, Type type)
result.Should().Be(value);
}

[Fact]
public void DeserializeWithDuplicateKeyChecking_YamlWithDuplicateKeys_ThrowsYamlException()
{
var yaml = @"
name: Jack
momentOfBirth: 1983-04-21T20:21:03.0041599Z
name: Jake
";

var sut = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.WithDuplicateKeyChecking()
.Build();

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

[Fact]
public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNotThrowYamlException()
{
var yaml = @"
name: Jack
momentOfBirth: 1983-04-21T20:21:03.0041599Z
name: Jake
";

var sut = new DeserializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();

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

public class Test
{
public string Value { get; set; }
Expand Down
13 changes: 12 additions & 1 deletion YamlDotNet/Serialization/DeserializerBuilder.cs
Expand Up @@ -47,6 +47,7 @@ public sealed class DeserializerBuilder : BuilderSkeleton<DeserializerBuilder>
private readonly Dictionary<TagName, Type> tagMappings;
private readonly Dictionary<Type, Type> typeMappings;
private bool ignoreUnmatched;
private bool duplicateKeyChecking;
private bool attemptUnknownTypeDeserialization;

/// <summary>
Expand Down Expand Up @@ -85,7 +86,7 @@ public DeserializerBuilder()
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value) },
{ typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value) },
{ typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() },
{ typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched) }
{ typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking) }
};

nodeTypeResolverFactories = new LazyComponentRegistrationList<Nothing, INodeTypeResolver>
Expand Down Expand Up @@ -388,6 +389,16 @@ public DeserializerBuilder IgnoreUnmatchedProperties()
return this;
}

/// <summary>
/// Instructs the deserializer to check for duplicate keys and throw an exception if duplicate keys are found.
/// </summary>
/// <returns></returns>
public DeserializerBuilder WithDuplicateKeyChecking()
{
duplicateKeyChecking = true;
return this;
}

/// <summary>
/// Creates a new <see cref="Deserializer" /> according to the current configuration.
/// </summary>
Expand Down
Expand Up @@ -20,6 +20,7 @@
// SOFTWARE.

using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
Expand All @@ -32,12 +33,14 @@ public sealed class ObjectNodeDeserializer : INodeDeserializer
private readonly IObjectFactory objectFactory;
private readonly ITypeInspector typeDescriptor;
private readonly bool ignoreUnmatched;
private readonly bool duplicateKeyChecking;

public ObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, bool ignoreUnmatched)
public ObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, bool ignoreUnmatched, bool duplicateKeyChecking)
{
this.objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
this.typeDescriptor = typeDescriptor ?? throw new ArgumentNullException(nameof(typeDescriptor));
this.ignoreUnmatched = ignoreUnmatched;
this.duplicateKeyChecking = duplicateKeyChecking;
}

bool INodeDeserializer.Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
Expand All @@ -52,11 +55,17 @@ bool INodeDeserializer.Deserialize(IParser parser, Type expectedType, Func<IPars
var implementationType = Nullable.GetUnderlyingType(expectedType) ?? expectedType;

value = objectFactory.Create(implementationType);
var consumedProperties = new List<string>();
while (!parser.TryConsume<MappingEnd>(out var _))
{
var propertyName = parser.Consume<Scalar>();
if (duplicateKeyChecking && consumedProperties.Contains(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

0 comments on commit 6bf5b44

Please sign in to comment.