Skip to content

Commit

Permalink
Fix issue #218: Objects with custom type converters are traversed
Browse files Browse the repository at this point in the history
  • Loading branch information
aaubry committed Nov 23, 2016
1 parent e2a7fa6 commit 42a9f8e
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 46 deletions.
91 changes: 90 additions & 1 deletion YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Xunit;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.RepresentationModel;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.TypeInspectors;

namespace YamlDotNet.Test.Serialization
{
Expand Down Expand Up @@ -1369,5 +1370,93 @@ public void DeserializationOfInt64Succeeds(long value)
long parsed = new Deserializer().Deserialize<long>(yaml);
Assert.Equal(value, parsed);
}

[Fact]
public void SerializeExceptionWithStackTrace()
{
var ex = GetExceptionWithStackTrace();
var serializer = new SerializerBuilder()
.WithTypeConverter(new MethodInfoConverter())
.Build();
string yaml = serializer.Serialize(ex);
Assert.Contains("GetExceptionWithStackTrace", yaml);
}

private class MethodInfoConverter : IYamlTypeConverter
{
public bool Accepts(Type type)
{
return typeof(MethodInfo).IsAssignableFrom(type);
}

public object ReadYaml(IParser parser, Type type)
{
throw new NotImplementedException();
}

public void WriteYaml(IEmitter emitter, object value, Type type)
{
var method = (MethodInfo)value;
emitter.Emit(new Scalar(string.Format("{0}.{1}", method.DeclaringType.FullName, method.Name)));
}
}

static Exception GetExceptionWithStackTrace()
{
try
{
throw new ArgumentNullException("foo");
}
catch (Exception ex)
{
return ex;
}
}

[Fact]
public void RegisteringATypeConverterPreventsTheTypeFromBeingVisited()
{
var serializer = new SerializerBuilder()
.WithTypeConverter(new SystemTypeTypeConverter())
.Build();

var yaml = serializer.Serialize(new TypeContainer
{
Type = typeof(string),
});

var deserializer = new DeserializerBuilder()
.WithTypeConverter(new SystemTypeTypeConverter())
.Build();

var result = deserializer.Deserialize<TypeContainer>(yaml);

Assert.Equal(typeof(string), result.Type);
}

public class TypeContainer
{
public Type Type { get; set; }
}

public class SystemTypeTypeConverter : IYamlTypeConverter
{
public bool Accepts(Type type)
{
return typeof(Type).IsAssignableFrom(type);
}

public object ReadYaml(IParser parser, Type type)
{
var scalar = parser.Expect<Scalar>();
return Type.GetType(scalar.Value);
}

public void WriteYaml(IEmitter emitter, object value, Type type)
{
var typeName = ((Type)value).AssemblyQualifiedName;
emitter.Emit(new Scalar(typeName));
}
}
}
}
10 changes: 1 addition & 9 deletions YamlDotNet/Serialization/BuilderSkeleton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ Action<IRegistrationLocationSelectionSyntax<IYamlTypeConverter>> where
throw new ArgumentNullException("where");
}

typeConvertersCache = null;

where(typeConverterFactories.CreateRegistrationLocationSelector(typeConverter.GetType(), _ => typeConverter));
return Self;
}
Expand Down Expand Up @@ -177,15 +175,9 @@ Action<IRegistrationLocationSelectionSyntax<ITypeInspector>> where
return Self;
}

private IEnumerable<IYamlTypeConverter> typeConvertersCache;

protected IEnumerable<IYamlTypeConverter> BuildTypeConverters()
{
if (typeConvertersCache == null)
{
typeConvertersCache = typeConverterFactories.BuildComponentList();
}
return typeConvertersCache;
return typeConverterFactories.BuildComponentList();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,12 @@ public static List<TComponent> BuildComponentList<TComponent>(this LazyComponent
.Select(factory => factory(null))
.ToList();
}

public static List<TComponent> BuildComponentList<TArgument, TComponent>(this LazyComponentRegistrationList<TArgument, TComponent> registrations, TArgument argument)
{
return registrations
.Select(factory => factory(argument))
.ToList();
}
}
}
23 changes: 14 additions & 9 deletions YamlDotNet/Serialization/ObjectGraphVisitors/AnchorAssigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

namespace YamlDotNet.Serialization.ObjectGraphVisitors
{
public sealed class AnchorAssigner : IObjectGraphVisitor<Nothing>, IAliasProvider
public sealed class AnchorAssigner : PreProcessingPhaseObjectGraphVisitorSkeleton, IAliasProvider
{
private class AnchorAssignment
{
Expand All @@ -35,7 +35,12 @@ private class AnchorAssignment
private readonly IDictionary<object, AnchorAssignment> assignments = new Dictionary<object, AnchorAssignment>();
private uint nextId;

bool IObjectGraphVisitor<Nothing>.Enter(IObjectDescriptor value, Nothing context)
public AnchorAssigner(IEnumerable<IYamlTypeConverter> typeConverters)
: base(typeConverters)
{
}

protected override bool Enter(IObjectDescriptor value)
{
AnchorAssignment assignment;
if (value.Value != null && assignments.TryGetValue(value.Value, out assignment))
Expand All @@ -51,34 +56,34 @@ bool IObjectGraphVisitor<Nothing>.Enter(IObjectDescriptor value, Nothing context
return true;
}

bool IObjectGraphVisitor<Nothing>.EnterMapping(IObjectDescriptor key, IObjectDescriptor value, Nothing context)
protected override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value)
{
return true;
}

bool IObjectGraphVisitor<Nothing>.EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, Nothing context)
protected override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value)
{
return true;
}

void IObjectGraphVisitor<Nothing>.VisitScalar(IObjectDescriptor scalar, Nothing context)
protected override void VisitScalar(IObjectDescriptor scalar)
{
// Do not assign anchors to scalars
}

void IObjectGraphVisitor<Nothing>.VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, Nothing context)
protected override void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType)
{
VisitObject(mapping);
}

void IObjectGraphVisitor<Nothing>.VisitMappingEnd(IObjectDescriptor mapping, Nothing context) { }
protected override void VisitMappingEnd(IObjectDescriptor mapping) { }

void IObjectGraphVisitor<Nothing>.VisitSequenceStart(IObjectDescriptor sequence, Type elementType, Nothing context)
protected override void VisitSequenceStart(IObjectDescriptor sequence, Type elementType)
{
VisitObject(sequence);
}

void IObjectGraphVisitor<Nothing>.VisitSequenceEnd(IObjectDescriptor sequence, Nothing context) { }
protected override void VisitSequenceEnd(IObjectDescriptor sequence) { }

private void VisitObject(IObjectDescriptor value)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// This file is part of YamlDotNet - A .NET library for YAML.
// Copyright (c) Antoine Aubry and contributors

// 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 System.Collections.Generic;
using System.Linq;

namespace YamlDotNet.Serialization.ObjectGraphVisitors
{
/// <summary>
/// A base class that simplifies the correct implementation of <see cref="IObjectGraphVisitor{Nothing}" />.
/// </summary>
public abstract class PreProcessingPhaseObjectGraphVisitorSkeleton : IObjectGraphVisitor<Nothing>
{
protected readonly IEnumerable<IYamlTypeConverter> typeConverters;

public PreProcessingPhaseObjectGraphVisitorSkeleton(IEnumerable<IYamlTypeConverter> typeConverters)
{
this.typeConverters = typeConverters != null
? typeConverters.ToList()
: Enumerable.Empty<IYamlTypeConverter>();
}

bool IObjectGraphVisitor<Nothing>.Enter(IObjectDescriptor value, Nothing context)
{
var typeConverter = typeConverters.FirstOrDefault(t => t.Accepts(value.Type));
if (typeConverter != null)
{
return false;
}

var convertible = value.Value as IYamlConvertible;
if (convertible != null)
{
return false;
}

#pragma warning disable 0618 // IYamlSerializable is obsolete
var serializable = value.Value as IYamlSerializable;
if (serializable != null)
{
return false;
}
#pragma warning restore

return Enter(value);
}

bool IObjectGraphVisitor<Nothing>.EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, Nothing context)
{
return EnterMapping(key, value);
}

bool IObjectGraphVisitor<Nothing>.EnterMapping(IObjectDescriptor key, IObjectDescriptor value, Nothing context)
{
return EnterMapping(key, value);
}

void IObjectGraphVisitor<Nothing>.VisitMappingEnd(IObjectDescriptor mapping, Nothing context)
{
VisitMappingEnd(mapping);
}

void IObjectGraphVisitor<Nothing>.VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType, Nothing context)
{
VisitMappingStart(mapping, keyType, valueType);
}

void IObjectGraphVisitor<Nothing>.VisitScalar(IObjectDescriptor scalar, Nothing context)
{
VisitScalar(scalar);
}

void IObjectGraphVisitor<Nothing>.VisitSequenceEnd(IObjectDescriptor sequence, Nothing context)
{
VisitSequenceEnd(sequence);
}

void IObjectGraphVisitor<Nothing>.VisitSequenceStart(IObjectDescriptor sequence, Type elementType, Nothing context)
{
VisitSequenceStart(sequence, elementType);
}

protected abstract bool Enter(IObjectDescriptor value);
protected abstract bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value);
protected abstract bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value);
protected abstract void VisitMappingEnd(IObjectDescriptor mapping);
protected abstract void VisitMappingStart(IObjectDescriptor mapping, Type keyType, Type valueType);
protected abstract void VisitScalar(IObjectDescriptor scalar);
protected abstract void VisitSequenceEnd(IObjectDescriptor sequence);
protected abstract void VisitSequenceStart(IObjectDescriptor sequence, Type elementType);
}
}
2 changes: 1 addition & 1 deletion YamlDotNet/Serialization/Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ private IObjectGraphVisitor<IEmitter> CreateEmittingVisitor(IEmitter emitter, IO

if (!IsOptionSet(SerializationOptions.DisableAliases))
{
var anchorAssigner = new AnchorAssigner();
var anchorAssigner = new AnchorAssigner(Converters);
traversalStrategy.Traverse<Nothing>(graph, anchorAssigner, null);

emittingVisitor = new AnchorAssigningObjectGraphVisitor(emittingVisitor, eventEmitter, anchorAssigner);
Expand Down
Loading

0 comments on commit 42a9f8e

Please sign in to comment.