// 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 YamlDotNet.Core;
using YamlDotNet.Serialization.Converters;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.ObjectGraphTraversalStrategies;
using YamlDotNet.Serialization.ObjectGraphVisitors;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
namespace YamlDotNet.Serialization
{
///
/// Creates and configures instances of .
/// This class is used to customize the behavior of . Use the relevant methods
/// to apply customizations, then call to create an instance of the serializer
/// with the desired customizations.
///
public sealed class SerializerBuilder : BuilderSkeleton
{
private ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory;
private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories;
private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories;
private readonly LazyComponentRegistrationList eventEmitterFactories;
private readonly IDictionary tagMappings = new Dictionary();
private int maximumRecursion = 50;
private EmitterSettings emitterSettings = EmitterSettings.Default;
private DefaultValuesHandling defaultValuesHandlingConfiguration = DefaultValuesHandling.Preserve;
public SerializerBuilder()
: base(new DynamicTypeResolver())
{
typeInspectorFactories.Add(typeof(CachedTypeInspector), inner => new CachedTypeInspector(inner));
typeInspectorFactories.Add(typeof(NamingConventionTypeInspector), inner => namingConvention is NullNamingConvention ? inner : new NamingConventionTypeInspector(inner, namingConvention));
typeInspectorFactories.Add(typeof(YamlAttributesTypeInspector), inner => new YamlAttributesTypeInspector(inner));
typeInspectorFactories.Add(typeof(YamlAttributeOverridesInspector), inner => overrides != null ? new YamlAttributeOverridesInspector(inner, overrides.Clone()) : inner);
preProcessingPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList, IObjectGraphVisitor>
{
{ typeof(AnchorAssigner), typeConverters => new AnchorAssigner(typeConverters) }
};
emissionPhaseObjectGraphVisitorFactories = new LazyComponentRegistrationList>
{
{
typeof(CustomSerializationObjectGraphVisitor),
args => new CustomSerializationObjectGraphVisitor(args.InnerVisitor, args.TypeConverters, args.NestedObjectSerializer)
},
{
typeof(AnchorAssigningObjectGraphVisitor),
args => new AnchorAssigningObjectGraphVisitor(args.InnerVisitor, args.EventEmitter, args.GetPreProcessingPhaseObjectGraphVisitor())
},
{
typeof(DefaultValuesObjectGraphVisitor),
args => new DefaultValuesObjectGraphVisitor(defaultValuesHandlingConfiguration, args.InnerVisitor)
},
{
typeof(CommentsObjectGraphVisitor),
args => new CommentsObjectGraphVisitor(args.InnerVisitor)
}
};
eventEmitterFactories = new LazyComponentRegistrationList
{
{ typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, false, tagMappings) }
};
objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters, maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention);
}
protected override SerializerBuilder Self { get { return this; } }
///
/// Sets the maximum recursion that is allowed while traversing the object graph. The default value is 50.
///
public SerializerBuilder WithMaximumRecursion(int maximumRecursion)
{
if (maximumRecursion <= 0)
{
throw new ArgumentOutOfRangeException(nameof(maximumRecursion), $"The maximum recursion specified ({maximumRecursion}) is invalid. It should be a positive integer.");
}
this.maximumRecursion = maximumRecursion;
return this;
}
///
/// Registers an additional to be used by the serializer.
///
/// A function that instantiates the event emitter.
public SerializerBuilder WithEventEmitter(Func eventEmitterFactory)
where TEventEmitter : IEventEmitter
{
return WithEventEmitter(eventEmitterFactory, w => w.OnTop());
}
///
/// Registers an additional to be used by the serializer.
///
/// A function that instantiates the event emitter.
/// Configures the location where to insert the
public SerializerBuilder WithEventEmitter(
Func eventEmitterFactory,
Action> where
)
where TEventEmitter : IEventEmitter
{
if (eventEmitterFactory == null)
{
throw new ArgumentNullException(nameof(eventEmitterFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(eventEmitterFactories.CreateRegistrationLocationSelector(typeof(TEventEmitter), inner => eventEmitterFactory(inner)));
return Self;
}
///
/// Registers an additional to be used by the serializer.
///
/// A function that instantiates the event emitter based on a previously registered .
/// Configures the location where to insert the
public SerializerBuilder WithEventEmitter(
WrapperFactory eventEmitterFactory,
Action> where
)
where TEventEmitter : IEventEmitter
{
if (eventEmitterFactory == null)
{
throw new ArgumentNullException(nameof(eventEmitterFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(eventEmitterFactories.CreateTrackingRegistrationLocationSelector(typeof(TEventEmitter), (wrapped, inner) => eventEmitterFactory(wrapped, inner)));
return Self;
}
///
/// Unregisters an existing of type .
///
public SerializerBuilder WithoutEventEmitter()
where TEventEmitter : IEventEmitter
{
return WithoutEventEmitter(typeof(TEventEmitter));
}
///
/// Unregisters an existing of type .
///
public SerializerBuilder WithoutEventEmitter(Type eventEmitterType)
{
if (eventEmitterType == null)
{
throw new ArgumentNullException(nameof(eventEmitterType));
}
eventEmitterFactories.Remove(eventEmitterType);
return this;
}
///
/// Registers a tag mapping.
///
public override SerializerBuilder WithTagMapping(TagName tag, Type type)
{
if (tag.IsEmpty)
{
throw new ArgumentException("Non-specific tags cannot be maped");
}
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (tagMappings.TryGetValue(type, out var alreadyRegisteredTag))
{
throw new ArgumentException($"Type already has a registered tag '{alreadyRegisteredTag}' for type '{type.FullName}'", nameof(type));
}
tagMappings.Add(type, tag);
return this;
}
///
/// Unregisters an existing tag mapping.
///
public SerializerBuilder WithoutTagMapping(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (!tagMappings.Remove(type))
{
throw new KeyNotFoundException($"Tag for type '{type.FullName}' is not registered");
}
return this;
}
///
/// Ensures that it will be possible to deserialize the serialized objects.
/// This option will force the emission of tags and emit only properties with setters.
///
public SerializerBuilder EnsureRoundtrip()
{
objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters, maximumRecursion) => new RoundtripObjectGraphTraversalStrategy(
typeConverters,
typeInspector,
typeResolver,
maximumRecursion,
namingConvention
);
WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, true, tagMappings), loc => loc.InsteadOf());
return WithTypeInspector(inner => new ReadableAndWritablePropertiesTypeInspector(inner), loc => loc.OnBottom());
}
///
/// Specifies that, if the same object appears more than once in the
/// serialization graph, it will be serialized each time instead of just once.
///
///
/// If the serialization graph contains circular references and this flag is set,
/// a StackOverflowException will be thrown.
/// If this flag is not set, there is a performance penalty because the entire
/// object graph must be walked twice.
///
public SerializerBuilder DisableAliases()
{
preProcessingPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigner));
emissionPhaseObjectGraphVisitorFactories.Remove(typeof(AnchorAssigningObjectGraphVisitor));
return this;
}
///
/// Forces every value to be serialized, even if it is the default value for that type.
///
[Obsolete("The default behavior is now to always emit default values, thefore calling this method has no effect. This behavior is now controlled by ConfigureDefaultValuesHandling.", error: true)]
public SerializerBuilder EmitDefaults() => ConfigureDefaultValuesHandling(DefaultValuesHandling.Preserve);
///
/// Configures how properties with default and null values should be handled. The default value is DefaultValuesHandling.Preserve
///
///
/// If more control is needed, create a class that extends from ChainedObjectGraphVisitor and override its EnterMapping methods.
/// Then register it as follows:
/// WithEmissionPhaseObjectGraphVisitor(args => new MyDefaultHandlingStrategy(args.InnerVisitor));
///
public SerializerBuilder ConfigureDefaultValuesHandling(DefaultValuesHandling configuration)
{
this.defaultValuesHandlingConfiguration = configuration;
return this;
}
///
/// Ensures that the result of the serialization is valid JSON.
///
public SerializerBuilder JsonCompatible()
{
this.emitterSettings = this.emitterSettings
.WithMaxSimpleKeyLength(int.MaxValue)
.WithoutAnchorName();
return this
.WithTypeConverter(new GuidConverter(true), w => w.InsteadOf())
.WithEventEmitter(inner => new JsonEventEmitter(inner), loc => loc.InsteadOf());
}
///
/// Registers an additional to be used by the serializer
/// before emitting an object graph.
///
///
/// Registering a visitor in the pre-processing phase enables to traverse the object graph once
/// before actually emitting it. This allows a visitor to collect information about the graph that
/// can be used later by another visitor registered in the emission phase.
///
/// The type inspector.
public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(TObjectGraphVisitor objectGraphVisitor)
where TObjectGraphVisitor : IObjectGraphVisitor
{
return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitor, w => w.OnTop());
}
///
/// Registers an additional to be used by the serializer
/// before emitting an object graph.
///
///
/// Registering a visitor in the pre-processing phase enables to traverse the object graph once
/// before actually emitting it. This allows a visitor to collect information about the graph that
/// can be used later by another visitor registered in the emission phase.
///
/// The type inspector.
/// Configures the location where to insert the
public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(
TObjectGraphVisitor objectGraphVisitor,
Action>> where
)
where TObjectGraphVisitor : IObjectGraphVisitor
{
if (objectGraphVisitor == null)
{
throw new ArgumentNullException(nameof(objectGraphVisitor));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(preProcessingPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), _ => objectGraphVisitor));
return this;
}
///
/// Registers an additional to be used by the serializer
/// before emitting an object graph.
///
///
/// Registering a visitor in the pre-processing phase enables to traverse the object graph once
/// before actually emitting it. This allows a visitor to collect information about the graph that
/// can be used later by another visitor registered in the emission phase.
///
/// A factory that creates the based on a previously registered .
/// Configures the location where to insert the
public SerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(
WrapperFactory, TObjectGraphVisitor> objectGraphVisitorFactory,
Action>> where
)
where TObjectGraphVisitor : IObjectGraphVisitor
{
if (objectGraphVisitorFactory == null)
{
throw new ArgumentNullException(nameof(objectGraphVisitorFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(preProcessingPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (wrapped, _) => objectGraphVisitorFactory(wrapped)));
return this;
}
///
/// Unregisters an existing of type .
///
public SerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor()
where TObjectGraphVisitor : IObjectGraphVisitor
{
return WithoutPreProcessingPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor));
}
///
/// Unregisters an existing of type .
///
public SerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor(Type objectGraphVisitorType)
{
if (objectGraphVisitorType == null)
{
throw new ArgumentNullException(nameof(objectGraphVisitorType));
}
preProcessingPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType);
return this;
}
///
/// Registers an to be used by the serializer
/// while traversing the object graph.
///
/// A function that instantiates the traversal strategy.
public SerializerBuilder WithObjectGraphTraversalStrategyFactory(ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory)
{
this.objectGraphTraversalStrategyFactory = objectGraphTraversalStrategyFactory;
return this;
}
///
/// Registers an additional to be used by the serializer
/// while emitting an object graph.
///
/// A function that instantiates the type inspector.
public SerializerBuilder WithEmissionPhaseObjectGraphVisitor(Func objectGraphVisitorFactory)
where TObjectGraphVisitor : IObjectGraphVisitor
{
return WithEmissionPhaseObjectGraphVisitor(objectGraphVisitorFactory, w => w.OnTop());
}
///
/// Registers an additional to be used by the serializer
/// while emitting an object graph.
///
/// A function that instantiates the type inspector.
/// Configures the location where to insert the
public SerializerBuilder WithEmissionPhaseObjectGraphVisitor(
Func objectGraphVisitorFactory,
Action>> where
)
where TObjectGraphVisitor : IObjectGraphVisitor
{
if (objectGraphVisitorFactory == null)
{
throw new ArgumentNullException(nameof(objectGraphVisitorFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(emissionPhaseObjectGraphVisitorFactories.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), args => objectGraphVisitorFactory(args)));
return this;
}
///
/// Registers an additional to be used by the serializer
/// while emitting an object graph.
///
/// A function that instantiates the type inspector based on a previously registered .
/// Configures the location where to insert the
public SerializerBuilder WithEmissionPhaseObjectGraphVisitor(
WrapperFactory, TObjectGraphVisitor> objectGraphVisitorFactory,
Action>> where
)
where TObjectGraphVisitor : IObjectGraphVisitor
{
if (objectGraphVisitorFactory == null)
{
throw new ArgumentNullException(nameof(objectGraphVisitorFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(emissionPhaseObjectGraphVisitorFactories.CreateTrackingRegistrationLocationSelector(typeof(TObjectGraphVisitor), (wrapped, args) => objectGraphVisitorFactory(wrapped, args)));
return this;
}
///
/// Unregisters an existing of type .
///
public SerializerBuilder WithoutEmissionPhaseObjectGraphVisitor()
where TObjectGraphVisitor : IObjectGraphVisitor
{
return WithoutEmissionPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor));
}
///
/// Unregisters an existing of type .
///
public SerializerBuilder WithoutEmissionPhaseObjectGraphVisitor(Type objectGraphVisitorType)
{
if (objectGraphVisitorType == null)
{
throw new ArgumentNullException(nameof(objectGraphVisitorType));
}
emissionPhaseObjectGraphVisitorFactories.Remove(objectGraphVisitorType);
return this;
}
///
/// Creates sequences with extra indentation
///
///
/// list:
/// - item
/// - item
///
///
public SerializerBuilder WithIndentedSequences()
{
emitterSettings = emitterSettings.WithIndentedSequences();
return this;
}
///
/// Creates a new according to the current configuration.
///
public ISerializer Build()
{
return Serializer.FromValueSerializer(BuildValueSerializer(), emitterSettings);
}
///
/// Creates a new that implements the current configuration.
/// This method is available for advanced scenarios. The preferred way to customize the behavior of the
/// deserializer is to use the method.
///
public IValueSerializer BuildValueSerializer()
{
var typeConverters = BuildTypeConverters();
var typeInspector = BuildTypeInspector();
var traversalStrategy = objectGraphTraversalStrategyFactory(typeInspector, typeResolver, typeConverters, maximumRecursion);
var eventEmitter = eventEmitterFactories.BuildComponentChain(new WriterEventEmitter());
return new ValueSerializer(
traversalStrategy,
eventEmitter,
typeConverters,
preProcessingPhaseObjectGraphVisitorFactories.Clone(),
emissionPhaseObjectGraphVisitorFactories.Clone()
);
}
private class ValueSerializer : IValueSerializer
{
private readonly IObjectGraphTraversalStrategy traversalStrategy;
private readonly IEventEmitter eventEmitter;
private readonly IEnumerable typeConverters;
private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories;
private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories;
public ValueSerializer(
IObjectGraphTraversalStrategy traversalStrategy,
IEventEmitter eventEmitter,
IEnumerable typeConverters,
LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories,
LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories
)
{
this.traversalStrategy = traversalStrategy;
this.eventEmitter = eventEmitter;
this.typeConverters = typeConverters;
this.preProcessingPhaseObjectGraphVisitorFactories = preProcessingPhaseObjectGraphVisitorFactories;
this.emissionPhaseObjectGraphVisitorFactories = emissionPhaseObjectGraphVisitorFactories;
}
public void SerializeValue(IEmitter emitter, object? value, Type? type)
{
var actualType = type ?? (value != null ? value.GetType() : typeof(object));
var staticType = type ?? typeof(object);
var graph = new ObjectDescriptor(value, actualType, staticType);
var preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters);
foreach (var visitor in preProcessingPhaseObjectGraphVisitors)
{
traversalStrategy.Traverse(graph, visitor, default);
}
void NestedObjectSerializer(object? v, Type? t) => SerializeValue(emitter, v, t);
var emittingVisitor = emissionPhaseObjectGraphVisitorFactories.BuildComponentChain(
new EmittingObjectGraphVisitor(eventEmitter),
inner => new EmissionPhaseObjectGraphVisitorArgs(inner, eventEmitter, preProcessingPhaseObjectGraphVisitors, typeConverters, NestedObjectSerializer)
);
traversalStrategy.Traverse(graph, emittingVisitor, emitter);
}
}
}
}