Skip to content

Commit fd2bd26

Browse files
author
Chris Nussbaum
committed
Make duplicate key checking optional
1 parent afdd1e2 commit fd2bd26

File tree

4 files changed

+37
-5
lines changed

4 files changed

+37
-5
lines changed

YamlDotNet.Test/Serialization/DeserializerTest.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public void DeserializeScalarEdgeCases(IConvertible value, Type type)
284284
}
285285

286286
[Fact]
287-
public void Deserialize_YamlWithDuplicateKeys_ThrowsYamlException()
287+
public void DeserializeWithDuplicateKeyChecking_YamlWithDuplicateKeys_ThrowsYamlException()
288288
{
289289
var yaml = @"
290290
name: Jack
@@ -294,12 +294,30 @@ public void Deserialize_YamlWithDuplicateKeys_ThrowsYamlException()
294294

295295
var sut = new DeserializerBuilder()
296296
.WithNamingConvention(CamelCaseNamingConvention.Instance)
297+
.WithDuplicateKeyChecking()
297298
.Build();
298299

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

304+
[Fact]
305+
public void DeserializeWithoutDuplicateKeyChecking_YamlWithDuplicateKeys_DoesNotThrowYamlException()
306+
{
307+
var yaml = @"
308+
name: Jack
309+
momentOfBirth: 1983-04-21T20:21:03.0041599Z
310+
name: Jake
311+
";
312+
313+
var sut = new DeserializerBuilder()
314+
.WithNamingConvention(CamelCaseNamingConvention.Instance)
315+
.Build();
316+
317+
Action act = () => sut.Deserialize<Person>(yaml);
318+
act.ShouldNotThrow<YamlException>("Because duplicate key checking is not enabled");
319+
}
320+
303321
public class Test
304322
{
305323
public string Value { get; set; }

YamlDotNet.Test/Serialization/SerializationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,6 +1312,7 @@ public void IgnoreExtraPropertiesIfWantedNamingScheme()
13121312
"scratch: 'scratcher'",
13131313
"deleteScratch: false",
13141314
"notScratch: 9443",
1315+
"notScratch: 192.168.1.30",
13151316
"mappedScratch:",
13161317
"- '/work/'"
13171318
);

YamlDotNet/Serialization/DeserializerBuilder.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public sealed class DeserializerBuilder : BuilderSkeleton<DeserializerBuilder>
4747
private readonly Dictionary<TagName, Type> tagMappings;
4848
private readonly Dictionary<Type, Type> typeMappings;
4949
private bool ignoreUnmatched;
50+
private bool duplicateKeyChecking;
5051
private bool attemptUnknownTypeDeserialization;
5152

5253
/// <summary>
@@ -85,7 +86,7 @@ public DeserializerBuilder()
8586
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value) },
8687
{ typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value) },
8788
{ typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() },
88-
{ typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched) }
89+
{ typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking) }
8990
};
9091

9192
nodeTypeResolverFactories = new LazyComponentRegistrationList<Nothing, INodeTypeResolver>
@@ -388,6 +389,16 @@ public DeserializerBuilder IgnoreUnmatchedProperties()
388389
return this;
389390
}
390391

392+
/// <summary>
393+
/// Instructs the deserializer to check for duplicate keys and throw an exception if duplicate keys are found.
394+
/// </summary>
395+
/// <returns></returns>
396+
public DeserializerBuilder WithDuplicateKeyChecking()
397+
{
398+
duplicateKeyChecking = true;
399+
return this;
400+
}
401+
391402
/// <summary>
392403
/// Creates a new <see cref="Deserializer" /> according to the current configuration.
393404
/// </summary>

YamlDotNet/Serialization/NodeDeserializers/ObjectNodeDeserializer.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ public sealed class ObjectNodeDeserializer : INodeDeserializer
3333
private readonly IObjectFactory objectFactory;
3434
private readonly ITypeInspector typeDescriptor;
3535
private readonly bool ignoreUnmatched;
36+
private readonly bool duplicateKeyChecking;
3637

37-
public ObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, bool ignoreUnmatched)
38+
public ObjectNodeDeserializer(IObjectFactory objectFactory, ITypeInspector typeDescriptor, bool ignoreUnmatched, bool duplicateKeyChecking)
3839
{
3940
this.objectFactory = objectFactory ?? throw new ArgumentNullException(nameof(objectFactory));
4041
this.typeDescriptor = typeDescriptor ?? throw new ArgumentNullException(nameof(typeDescriptor));
4142
this.ignoreUnmatched = ignoreUnmatched;
43+
this.duplicateKeyChecking = duplicateKeyChecking;
4244
}
4345

4446
bool INodeDeserializer.Deserialize(IParser parser, Type expectedType, Func<IParser, Type, object?> nestedObjectDeserializer, out object? value)
@@ -57,9 +59,9 @@ bool INodeDeserializer.Deserialize(IParser parser, Type expectedType, Func<IPars
5759
while (!parser.TryConsume<MappingEnd>(out var _))
5860
{
5961
var propertyName = parser.Consume<Scalar>();
60-
if (consumedProperties.Contains(propertyName.Value))
62+
if (duplicateKeyChecking && consumedProperties.Contains(propertyName.Value))
6163
{
62-
throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate property {propertyName.Value}");
64+
throw new YamlException(propertyName.Start, propertyName.End, $"Encountered duplicate key {propertyName.Value}");
6365
}
6466
try
6567
{

0 commit comments

Comments
 (0)