// 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); } } } }