// 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; #if NET7_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif using YamlDotNet.Core; using YamlDotNet.Helpers; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.ObjectFactories; 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 StaticSerializerBuilder : StaticBuilderSkeleton { private readonly StaticContext context; private readonly StaticObjectFactory factory; private ObjectGraphTraversalStrategyFactory objectGraphTraversalStrategyFactory; private readonly LazyComponentRegistrationList, IObjectGraphVisitor> preProcessingPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList> emissionPhaseObjectGraphVisitorFactories; private readonly LazyComponentRegistrationList eventEmitterFactories; private readonly Dictionary tagMappings = new (); private int maximumRecursion = 50; private EmitterSettings emitterSettings = EmitterSettings.Default; private DefaultValuesHandling defaultValuesHandlingConfiguration = DefaultValuesHandling.Preserve; private bool quoteNecessaryStrings; private bool quoteYaml1_1Strings; private ScalarStyle defaultScalarStyle; public StaticSerializerBuilder(StaticContext context) : base(new DynamicTypeResolver()) { this.context = context; factory = context.GetFactory(); 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)); 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, factory) }, { typeof(CommentsObjectGraphVisitor), args => new CommentsObjectGraphVisitor(args.InnerVisitor) } }; eventEmitterFactories = new LazyComponentRegistrationList { { typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, defaultScalarStyle, yamlFormatter, enumNamingConvention, BuildTypeInspector()) } }; objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters, maximumRecursion) => new FullObjectGraphTraversalStrategy(typeInspector, typeResolver, maximumRecursion, namingConvention, factory); } protected override StaticSerializerBuilder Self { get { return this; } } /// /// Put double quotes around strings that need it, for example Null, True, False, a number. This should be called before any other "With" methods if you want this feature enabled. /// /// Also quote strings that are valid scalars in the YAML 1.1 specification (which includes boolean Yes/No/On/Off, base 60 numbers and more) public StaticSerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = false) { quoteNecessaryStrings = true; this.quoteYaml1_1Strings = quoteYaml1_1Strings; return this; } /// /// Put double quotes around strings that need it, for example Null, True, False, a number. This should be called before any other "With" methods if you want this feature enabled. /// public StaticSerializerBuilder WithQuotingNecessaryStrings() { quoteNecessaryStrings = true; return this; } /// /// Sets the default quoting style for scalar values. The default value is /// public StaticSerializerBuilder WithDefaultScalarStyle(ScalarStyle style) { this.defaultScalarStyle = style; return this; } /// /// Sets the maximum recursion that is allowed while traversing the object graph. The default value is 50. /// public StaticSerializerBuilder 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 StaticSerializerBuilder WithEventEmitter(Func eventEmitterFactory) where TEventEmitter : IEventEmitter => WithEventEmitter(eventEmitterFactory, w => w.OnTop()); /// /// Registers an additional to be used by the serializer. /// /// A function that instantiates the event emitter. public StaticSerializerBuilder WithEventEmitter(Func eventEmitterFactory) where TEventEmitter : IEventEmitter => 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 StaticSerializerBuilder WithEventEmitter( Func eventEmitterFactory, Action> where ) where TEventEmitter : IEventEmitter => WithEventEmitter((IEventEmitter e, ITypeInspector _) => eventEmitterFactory(e), where); /// /// Registers an additional to be used by the serializer. /// /// A function that instantiates the event emitter. /// Configures the location where to insert the public StaticSerializerBuilder 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, BuildTypeInspector()))); 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 StaticSerializerBuilder 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 StaticSerializerBuilder WithoutEventEmitter() where TEventEmitter : IEventEmitter { return WithoutEventEmitter(typeof(TEventEmitter)); } /// /// Unregisters an existing of type . /// public StaticSerializerBuilder WithoutEventEmitter(Type eventEmitterType) { if (eventEmitterType == null) { throw new ArgumentNullException(nameof(eventEmitterType)); } eventEmitterFactories.Remove(eventEmitterType); return this; } /// /// Registers a tag mapping. /// public override StaticSerializerBuilder 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 StaticSerializerBuilder 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 StaticSerializerBuilder EnsureRoundtrip() { objectGraphTraversalStrategyFactory = (typeInspector, typeResolver, typeConverters, maximumRecursion) => new RoundtripObjectGraphTraversalStrategy( typeConverters, typeInspector, typeResolver, maximumRecursion, namingConvention, settings, factory ); WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, tagMappings, quoteNecessaryStrings, false, ScalarStyle.Plain, YamlFormatter.Default, enumNamingConvention, BuildTypeInspector() ), 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 StaticSerializerBuilder 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 StaticSerializerBuilder 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 StaticSerializerBuilder ConfigureDefaultValuesHandling(DefaultValuesHandling configuration) { this.defaultValuesHandlingConfiguration = configuration; return this; } /// /// Ensures that the result of the serialization is valid JSON. /// public StaticSerializerBuilder JsonCompatible() { this.emitterSettings = this.emitterSettings .WithMaxSimpleKeyLength(int.MaxValue) .WithoutAnchorName(); return this .WithTypeConverter(new GuidConverter(true), w => w.InsteadOf()) .WithTypeConverter(new DateTime8601Converter(ScalarStyle.DoubleQuoted)) #if NET6_0_OR_GREATER .WithTypeConverter(new DateOnlyConverter(doubleQuotes: true)) .WithTypeConverter(new TimeOnlyConverter(doubleQuotes: true)) #endif .WithEventEmitter(inner => new JsonEventEmitter(inner, yamlFormatter, enumNamingConvention, BuildTypeInspector()), loc => loc.InsteadOf()); } /// /// Allows you to override the new line character to use when serializing to YAML. /// /// NewLine character(s) to use when serializing to YAML. public StaticSerializerBuilder WithNewLine(string newLine) { this.emitterSettings = this.emitterSettings.WithNewLine(newLine); 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. /// /// The type inspector. public StaticSerializerBuilder 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. /// /// A function that instantiates the type inspector. public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor(Func, TObjectGraphVisitor> objectGraphVisitorFactory) where TObjectGraphVisitor : IObjectGraphVisitor { return WithPreProcessingPhaseObjectGraphVisitor(objectGraphVisitorFactory, 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 StaticSerializerBuilder 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 function that instantiates the type inspector. /// Configures the location where to insert the public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor( Func, 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.CreateRegistrationLocationSelector(typeof(TObjectGraphVisitor), typeConverters => objectGraphVisitorFactory(typeConverters))); 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 function that instantiates the type inspector based on a previously registered . /// Configures the location where to insert the public StaticSerializerBuilder 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; } /// /// 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 function that instantiates the type inspector based on a previously registered . /// Configures the location where to insert the public StaticSerializerBuilder WithPreProcessingPhaseObjectGraphVisitor( WrapperFactory, IObjectGraphVisitor, 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, typeConverters) => objectGraphVisitorFactory(wrapped, typeConverters))); return this; } /// /// Unregisters an existing of type . /// public StaticSerializerBuilder WithoutPreProcessingPhaseObjectGraphVisitor() where TObjectGraphVisitor : IObjectGraphVisitor { return WithoutPreProcessingPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } /// /// Unregisters an existing of type . /// public StaticSerializerBuilder 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 StaticSerializerBuilder 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 StaticSerializerBuilder 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 StaticSerializerBuilder 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 StaticSerializerBuilder 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 StaticSerializerBuilder WithoutEmissionPhaseObjectGraphVisitor() where TObjectGraphVisitor : IObjectGraphVisitor { return WithoutEmissionPhaseObjectGraphVisitor(typeof(TObjectGraphVisitor)); } /// /// Unregisters an existing of type . /// public StaticSerializerBuilder 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 StaticSerializerBuilder 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() ); } /// /// Builds the type inspector used by various classes to get information about types and their members. /// /// public ITypeInspector BuildTypeInspector() { var typeInspector = context.GetTypeInspector(); return typeInspectorFactories.BuildComponentChain(typeInspector); } 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); void NestedObjectSerializer(object? v, Type? t) { SerializeValue(emitter, v, t); } var preProcessingPhaseObjectGraphVisitors = preProcessingPhaseObjectGraphVisitorFactories.BuildComponentList(typeConverters); foreach (var visitor in preProcessingPhaseObjectGraphVisitors) { traversalStrategy.Traverse(graph, visitor, default, NestedObjectSerializer); } var emittingVisitor = emissionPhaseObjectGraphVisitorFactories.BuildComponentChain( new EmittingObjectGraphVisitor(eventEmitter), inner => new EmissionPhaseObjectGraphVisitorArgs(inner, eventEmitter, preProcessingPhaseObjectGraphVisitors, typeConverters, NestedObjectSerializer) ); traversalStrategy.Traverse(graph, emittingVisitor, emitter, NestedObjectSerializer); } } } }