Skip to content

Commit

Permalink
Drop support for specifying arbitrary type names in tags
Browse files Browse the repository at this point in the history
  • Loading branch information
aaubry committed Jun 21, 2018
1 parent c8df563 commit 0ce8ac1
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 56 deletions.
75 changes: 65 additions & 10 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ public void DeserializeExplicitType()
{
var text = Yaml.StreamFrom("explicit-type.template").TemplatedOn<Simple>();

var result = Deserializer.Deserialize<Simple>(UsingReaderFor(text));
var result = new DeserializerBuilder()
.WithTagMapping("!Simple", typeof(Simple))
.Build()
.Deserialize<Simple>(UsingReaderFor(text));

result.aaa.Should().Be("bbb");
}
Expand All @@ -220,7 +223,10 @@ public void DeserializeConvertible()
{
var text = Yaml.StreamFrom("convertible.template").TemplatedOn<Convertible>();

var result = Deserializer.Deserialize<Simple>(UsingReaderFor(text));
var result = new DeserializerBuilder()
.WithTagMapping("!Convertible", typeof(Convertible))
.Build()
.Deserialize<Simple>(UsingReaderFor(text));

result.aaa.Should().Be("[hello, world]");
}
Expand Down Expand Up @@ -255,7 +261,17 @@ public void RoundtripObject()
{
var obj = new Example();

var result = DoRoundtripFromObjectTo<Example>(obj, SerializerBuilder.EnsureRoundtrip().Build());
var result = DoRoundtripFromObjectTo<Example>(
obj,
new SerializerBuilder()
.WithTagMapping("!Example", typeof(Example))
.EnsureRoundtrip()
.EmitDefaults()
.Build(),
new DeserializerBuilder()
.WithTagMapping("!Example", typeof(Example))
.Build()
);

result.ShouldBeEquivalentTo(obj);
}
Expand All @@ -265,7 +281,17 @@ public void RoundtripObjectWithDefaults()
{
var obj = new Example();

var result = DoRoundtripFromObjectTo<Example>(obj, SerializerBuilder.EnsureRoundtrip().EmitDefaults().Build());
var result = DoRoundtripFromObjectTo<Example>(
obj,
new SerializerBuilder()
.WithTagMapping("!Example", typeof(Example))
.EnsureRoundtrip()
.EmitDefaults()
.Build(),
new DeserializerBuilder()
.WithTagMapping("!Example", typeof(Example))
.Build()
);

result.ShouldBeEquivalentTo(obj);
}
Expand Down Expand Up @@ -351,7 +377,18 @@ public void RoundtripDerivedClass()
RegularBase = new Derived { BaseProperty = "foo", DerivedProperty = "bar" }
};

var result = DoRoundtripFromObjectTo<InheritanceExample>(obj, SerializerBuilder.EnsureRoundtrip().Build());
var result = DoRoundtripFromObjectTo<InheritanceExample>(
obj,
new SerializerBuilder()
.WithTagMapping("!InheritanceExample", typeof(InheritanceExample))
.WithTagMapping("!Derived", typeof(Derived))
.EnsureRoundtrip()
.Build(),
new DeserializerBuilder()
.WithTagMapping("!InheritanceExample", typeof(InheritanceExample))
.WithTagMapping("!Derived", typeof(Derived))
.Build()
);

result.SomeScalar.Should().Be("Hello");
result.RegularBase.Should().BeOfType<Derived>().And
Expand All @@ -367,7 +404,16 @@ public void RoundtripDerivedClassWithSerializeAs()
BaseWithSerializeAs = new Derived { BaseProperty = "foo", DerivedProperty = "bar" }
};

var result = DoRoundtripFromObjectTo<InheritanceExample>(obj, SerializerBuilder.EnsureRoundtrip().Build());
var result = DoRoundtripFromObjectTo<InheritanceExample>(
obj,
new SerializerBuilder()
.WithTagMapping("!InheritanceExample", typeof(InheritanceExample))
.EnsureRoundtrip()
.Build(),
new DeserializerBuilder()
.WithTagMapping("!InheritanceExample", typeof(InheritanceExample))
.Build()
);

result.BaseWithSerializeAs.Should().BeOfType<Base>().And
.Subject.As<Base>().ShouldBeEquivalentTo(new { ParentProp = "foo" }, o => o.ExcludingMissingMembers());
Expand Down Expand Up @@ -450,7 +496,10 @@ public void DeserializeExplicitList()
{
var stream = Yaml.StreamFrom("list-explicit.yaml");

var result = Deserializer.Deserialize(stream);
var result = new DeserializerBuilder()
.WithTagMapping("!List", typeof(List<int>))
.Build()
.Deserialize(stream);

result.Should().BeAssignableTo<IList<int>>().And
.Subject.As<IList<int>>().Should().Equal(3, 4, 5);
Expand Down Expand Up @@ -531,7 +580,10 @@ public void DeserializeExplicitDictionary()
{
var stream = Yaml.StreamFrom("dictionary-explicit.yaml");

var result = Deserializer.Deserialize(stream);
var result = new DeserializerBuilder()
.WithTagMapping("!Dictionary", typeof(Dictionary<string, int>))
.Build()
.Deserialize(stream);

result.Should().BeAssignableTo<IDictionary<string, int>>().And.Subject
.As<IDictionary<string, int>>().Should().Equal(new Dictionary<string, int> {
Expand Down Expand Up @@ -947,12 +999,15 @@ public void DeserializationUtilizeNamingConventions()
public void TypeConverterIsUsedOnListItems()
{
var text = Lines(
"- !<!{type}>",
"- !{type}",
" Left: hello",
" Right: world")
.TemplatedOn<Convertible>();

var list = Deserializer.Deserialize<List<string>>(UsingReaderFor(text));
var list = new DeserializerBuilder()
.WithTagMapping("!Convertible", typeof(Convertible))
.Build()
.Deserialize<List<string>>(UsingReaderFor(text));

list
.Should().NotBeNull()
Expand Down
2 changes: 1 addition & 1 deletion YamlDotNet.Test/Yaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static string TemplatedOn<T>(this TextReader reader)
public static string TemplatedOn<T>(this string text)
{
return Regex.Replace(text, @"{type}", match =>
Uri.EscapeDataString(String.Format("{0}, {1}", typeof(T).FullName, typeof(T).GetTypeInfo().Assembly.FullName)));
Uri.EscapeDataString(typeof(T).Name));
}

public static IParser ParserForEmptyContent()
Expand Down
2 changes: 1 addition & 1 deletion YamlDotNet.Test/files/convertible.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aaa:
!<!{type}>
!{type}
Left: hello
Right: world
2 changes: 1 addition & 1 deletion YamlDotNet.Test/files/dictionary-explicit.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
!<!System.Collections.Generic.Dictionary%602%5BSystem.String%2CSystem.Int32%5D,mscorlib> {
!Dictionary {
key1: 1,
key2: 2
}
2 changes: 1 addition & 1 deletion YamlDotNet.Test/files/explicit-type.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
!<!{type}> {
!{type} {
aaa: bbb
}
2 changes: 1 addition & 1 deletion YamlDotNet.Test/files/list-explicit.yaml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
!<!System.Collections.Generic.List%601%5BSystem.Int32%5D,mscorlib> [ 3, 4, 5 ]
!List [ 3, 4, 5 ]
2 changes: 1 addition & 1 deletion YamlDotNet/Serialization/DeserializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public DeserializerBuilder()
{ typeof(YamlConvertibleTypeResolver), _ => new YamlConvertibleTypeResolver() },
{ typeof(YamlSerializableTypeResolver), _ => new YamlSerializableTypeResolver() },
{ typeof(TagNodeTypeResolver), _ => new TagNodeTypeResolver(tagMappings) },
{ typeof(TypeNameInTagNodeTypeResolver), _ => new TypeNameInTagNodeTypeResolver() },
{ typeof(PreventUnknownTagsNodeTypeResolver), _ => new PreventUnknownTagsNodeTypeResolver() },
{ typeof(DefaultContainersNodeTypeResolver), _ => new DefaultContainersNodeTypeResolver() }
};

Expand Down
26 changes: 0 additions & 26 deletions YamlDotNet/Serialization/EventEmitters/CustomTagEventEmitter.cs

This file was deleted.

32 changes: 21 additions & 11 deletions YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@
// SOFTWARE.

using System;
using System.Collections.Generic;
using System.Globalization;
using YamlDotNet.Core;

namespace YamlDotNet.Serialization.EventEmitters
{
public sealed class TypeAssigningEventEmitter : ChainedEventEmitter
{
private readonly bool _assignTypeWhenDifferent;
private readonly bool requireTagWhenStaticAndActualTypesAreDifferent;
private IDictionary<Type, string> tagMappings;

public TypeAssigningEventEmitter(IEventEmitter nextEmitter, bool assignTypeWhenDifferent)
public TypeAssigningEventEmitter(IEventEmitter nextEmitter, bool requireTagWhenStaticAndActualTypesAreDifferent, IDictionary<Type, string> tagMappings)
: base(nextEmitter)
{
_assignTypeWhenDifferent = assignTypeWhenDifferent;
this.requireTagWhenStaticAndActualTypesAreDifferent = requireTagWhenStaticAndActualTypesAreDifferent;
this.tagMappings = tagMappings;
}

public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)
Expand Down Expand Up @@ -115,24 +118,31 @@ public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter)

public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter)
{
AssignTypeIfDifferent(eventInfo);
AssignTypeIfNeeded(eventInfo);
base.Emit(eventInfo, emitter);
}

public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter)
{
AssignTypeIfDifferent(eventInfo);
AssignTypeIfNeeded(eventInfo);
base.Emit(eventInfo, emitter);
}

private void AssignTypeIfDifferent(ObjectEventInfo eventInfo)
private void AssignTypeIfNeeded(ObjectEventInfo eventInfo)
{
if (_assignTypeWhenDifferent && eventInfo.Source.Value != null)
if (tagMappings.TryGetValue(eventInfo.Source.Type, out string tag))
{
if (eventInfo.Source.Type != eventInfo.Source.StaticType)
{
eventInfo.Tag = "!" + eventInfo.Source.Type.AssemblyQualifiedName;
}
eventInfo.Tag = tag;
}
else if (requireTagWhenStaticAndActualTypesAreDifferent && eventInfo.Source.Value != null && eventInfo.Source.Type != eventInfo.Source.StaticType)
{
throw new YamlException(
$"Cannot serialize type '{eventInfo.Source.Type.FullName}' where a '{eventInfo.Source.StaticType.FullName}' was expected " +
$"because no tag mapping has been registered for '{eventInfo.Source.Type.FullName}', " +
$"which means that it won't be possible to deserialize the document.\n" +
$"Register a tag mapping using the SerializerBuilder.WithTagMapping method.\n\n" +
$"E.g: builder.WithTagMapping(\"!{eventInfo.Source.Type.Name}\", typeof({eventInfo.Source.Type.FullName}));"
);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// This file is part of YamlDotNet - A .NET library for YAML.
// Copyright (c) Antoine Aubry
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;

namespace YamlDotNet.Serialization.NodeTypeResolvers
{
public class PreventUnknownTagsNodeTypeResolver : INodeTypeResolver
{
bool INodeTypeResolver.Resolve(NodeEvent nodeEvent, ref Type currentType)
{
if (!string.IsNullOrEmpty(nodeEvent.Tag))
{
throw new YamlException(nodeEvent.Start, nodeEvent.End, $"Encountered an unresolved tag '{nodeEvent.Tag}'");
}
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

namespace YamlDotNet.Serialization.NodeTypeResolvers
{
[Obsolete("The mechanism that this class uses to specify type names is non-standard. Register the tags explicitly instead of using this convention.")]
public sealed class TypeNameInTagNodeTypeResolver : INodeTypeResolver
{
bool INodeTypeResolver.Resolve(NodeEvent nodeEvent, ref Type currentType)
Expand Down
5 changes: 2 additions & 3 deletions YamlDotNet/Serialization/SerializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,11 @@ public SerializerBuilder()
args => new DefaultExclusiveObjectGraphVisitor(args.InnerVisitor));

eventEmitterFactories = new LazyComponentRegistrationList<IEventEmitter, IEventEmitter>();
eventEmitterFactories.Add(typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, false));
eventEmitterFactories.Add(typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, false, tagMappings));

objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, 50, namingConvention ?? new NullNamingConvention());

WithTypeResolver(new DynamicTypeResolver());
WithEventEmitter(inner => new CustomTagEventEmitter(inner, tagMappings));
}

protected override SerializerBuilder Self { get { return this; } }
Expand Down Expand Up @@ -214,7 +213,7 @@ public SerializerBuilder EnsureRoundtrip()
typeResolver,
50
);
WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, true), loc => loc.InsteadOf<TypeAssigningEventEmitter>());
WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, true, tagMappings), loc => loc.InsteadOf<TypeAssigningEventEmitter>());
return WithTypeInspector(inner => new ReadableAndWritablePropertiesTypeInspector(inner), loc => loc.OnBottom());
}

Expand Down

0 comments on commit 0ce8ac1

Please sign in to comment.