// 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.BufferedDeserialization;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.NodeDeserializers;
using YamlDotNet.Serialization.NodeTypeResolvers;
using YamlDotNet.Serialization.ObjectFactories;
using YamlDotNet.Serialization.Schemas;
using YamlDotNet.Serialization.TypeInspectors;
using YamlDotNet.Serialization.TypeResolvers;
using YamlDotNet.Serialization.Utilities;
using YamlDotNet.Serialization.ValueDeserializers;
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 deserializer
/// with the desired customizations.
///
#if NET7_0_OR_GREATER
[RequiresDynamicCode("This builder configures the deserializer to use reflection which is not compatible with ahead-of-time compilation or assembly trimming." +
" You need to use the code generator/analyzer to generate static code and use the 'StaticDeserializerBuilder' object instead of this one.")]
#endif
public sealed class DeserializerBuilder : BuilderSkeleton
{
private Lazy objectFactory;
private readonly LazyComponentRegistrationList nodeDeserializerFactories;
private readonly LazyComponentRegistrationList nodeTypeResolverFactories;
private readonly Dictionary tagMappings;
private readonly Dictionary typeMappings;
private readonly ITypeConverter typeConverter;
private bool ignoreUnmatched;
private bool duplicateKeyChecking;
private bool attemptUnknownTypeDeserialization;
private bool enforceNullability;
private bool caseInsensitivePropertyMatching;
private bool enforceRequiredProperties;
///
/// Initializes a new using the default component registrations.
///
public DeserializerBuilder()
: base(new StaticTypeResolver())
{
typeMappings = new ();
objectFactory = new Lazy(() => new DefaultObjectFactory(typeMappings, settings), true);
tagMappings = new Dictionary
{
{ FailsafeSchema.Tags.Map, typeof(Dictionary) },
{ FailsafeSchema.Tags.Str, typeof(string) },
{ JsonSchema.Tags.Bool, typeof(bool) },
{ JsonSchema.Tags.Float, typeof(double) },
{ JsonSchema.Tags.Int, typeof(int) },
{ DefaultSchema.Tags.Timestamp, typeof(DateTime) }
};
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);
typeInspectorFactories.Add(typeof(ReadableAndWritablePropertiesTypeInspector), inner => new ReadableAndWritablePropertiesTypeInspector(inner));
typeConverter = new ReflectionTypeConverter();
nodeDeserializerFactories = new LazyComponentRegistrationList
{
{ typeof(YamlConvertibleNodeDeserializer), _ => new YamlConvertibleNodeDeserializer(objectFactory.Value) },
{ typeof(YamlSerializableNodeDeserializer), _ => new YamlSerializableNodeDeserializer(objectFactory.Value) },
{ typeof(TypeConverterNodeDeserializer), _ => new TypeConverterNodeDeserializer(BuildTypeConverters()) },
{ typeof(NullNodeDeserializer), _ => new NullNodeDeserializer() },
{ typeof(ScalarNodeDeserializer), _ => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization, typeConverter, BuildTypeInspector(), yamlFormatter, enumNamingConvention) },
{ typeof(ArrayNodeDeserializer), _ => new ArrayNodeDeserializer(enumNamingConvention, BuildTypeInspector()) },
{ typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value, duplicateKeyChecking) },
{ typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value, enumNamingConvention, BuildTypeInspector()) },
{ typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() },
{
typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value,
BuildTypeInspector(),
ignoreUnmatched,
duplicateKeyChecking,
typeConverter,
enumNamingConvention,
enforceNullability,
caseInsensitivePropertyMatching,
enforceRequiredProperties,
BuildTypeConverters())
},
{ typeof(FsharpListNodeDeserializer), _ => new FsharpListNodeDeserializer(BuildTypeInspector(), enumNamingConvention) },
};
nodeTypeResolverFactories = new LazyComponentRegistrationList
{
{ typeof(MappingNodeTypeResolver), _ => new MappingNodeTypeResolver(typeMappings) },
{ typeof(YamlConvertibleTypeResolver), _ => new YamlConvertibleTypeResolver() },
{ typeof(YamlSerializableTypeResolver), _ => new YamlSerializableTypeResolver() },
{ typeof(TagNodeTypeResolver), _ => new TagNodeTypeResolver(tagMappings) },
{ typeof(PreventUnknownTagsNodeTypeResolver), _ => new PreventUnknownTagsNodeTypeResolver() },
{ typeof(DefaultContainersNodeTypeResolver), _ => new DefaultContainersNodeTypeResolver() }
};
}
protected override DeserializerBuilder Self { get { return this; } }
///
/// Builds the type inspector used by various classes to get information about types and their members.
///
///
public ITypeInspector BuildTypeInspector()
{
ITypeInspector innerInspector = new WritablePropertiesTypeInspector(typeResolver, includeNonPublicProperties);
if (!ignoreFields)
{
innerInspector = new CompositeTypeInspector(
new ReadableFieldsTypeInspector(typeResolver),
innerInspector
);
}
return typeInspectorFactories.BuildComponentChain(innerInspector);
}
///
/// When deserializing it will attempt to convert unquoted strings to their correct datatype. If conversion is not sucessful, it will leave it as a string.
/// This option is only applicable when not specifying a type or specifying the object type during deserialization.
///
public DeserializerBuilder WithAttemptingUnquotedStringTypeDeserialization()
{
attemptUnknownTypeDeserialization = true;
return this;
}
///
/// Sets the that will be used by the deserializer.
///
public DeserializerBuilder WithObjectFactory(IObjectFactory objectFactory)
{
if (objectFactory == null)
{
throw new ArgumentNullException(nameof(objectFactory));
}
this.objectFactory = new Lazy(() => objectFactory, true);
return this;
}
///
/// Sets the that will be used by the deserializer.
///
public DeserializerBuilder WithObjectFactory(Func objectFactory)
{
if (objectFactory == null)
{
throw new ArgumentNullException(nameof(objectFactory));
}
return WithObjectFactory(new LambdaObjectFactory(objectFactory));
}
///
/// Registers an additional to be used by the deserializer.
///
public DeserializerBuilder WithNodeDeserializer(INodeDeserializer nodeDeserializer)
{
return WithNodeDeserializer(nodeDeserializer, w => w.OnTop());
}
///
/// Registers an additional to be used by the deserializer.
///
///
/// Configures the location where to insert the
public DeserializerBuilder WithNodeDeserializer(
INodeDeserializer nodeDeserializer,
Action> where
)
{
if (nodeDeserializer == null)
{
throw new ArgumentNullException(nameof(nodeDeserializer));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(nodeDeserializerFactories.CreateRegistrationLocationSelector(nodeDeserializer.GetType(), _ => nodeDeserializer));
return this;
}
///
/// Registers an additional to be used by the deserializer.
///
/// A factory that creates the based on a previously registered .
/// Configures the location where to insert the
public DeserializerBuilder WithNodeDeserializer(
WrapperFactory nodeDeserializerFactory,
Action> where
)
where TNodeDeserializer : INodeDeserializer
{
if (nodeDeserializerFactory == null)
{
throw new ArgumentNullException(nameof(nodeDeserializerFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(nodeDeserializerFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeDeserializer), (wrapped, _) => nodeDeserializerFactory(wrapped)));
return this;
}
///
/// Unregisters an existing of type .
///
public DeserializerBuilder WithoutNodeDeserializer()
where TNodeDeserializer : INodeDeserializer
{
return WithoutNodeDeserializer(typeof(TNodeDeserializer));
}
///
/// Unregisters an existing of type .
///
public DeserializerBuilder WithoutNodeDeserializer(Type nodeDeserializerType)
{
if (nodeDeserializerType == null)
{
throw new ArgumentNullException(nameof(nodeDeserializerType));
}
nodeDeserializerFactories.Remove(nodeDeserializerType);
return this;
}
///
/// Registers a to be used by the deserializer. This internally registers
/// all existing as inner deserializers available to the .
/// Usually you will want to call this after any other changes to the s used by the deserializer.
///
/// An action that can configure the .
/// Configures the max depth of yaml nodes that will be buffered. A value of -1 (the default) means yaml nodes of any depth will be buffered.
/// Configures the max number of yaml nodes that will be buffered. A value of -1 (the default) means there is no limit on the number of yaml nodes buffered.
public DeserializerBuilder WithTypeDiscriminatingNodeDeserializer(
Action configureTypeDiscriminatingNodeDeserializerOptions, int maxDepth = -1, int maxLength = -1)
{
var options = new TypeDiscriminatingNodeDeserializerOptions();
configureTypeDiscriminatingNodeDeserializerOptions(options);
// We use all current NodeDeserializers as the inner deserializers for the TypeDiscriminatingNodeDeserializer,
// so that it can successfully deserialize anything our root deserializer can.
var typeDiscriminatingNodeDeserializer = new TypeDiscriminatingNodeDeserializer(nodeDeserializerFactories.BuildComponentList(), options.discriminators, maxDepth, maxLength);
// We register this before the DictionaryNodeDeserializer, as otherwise it will take precedence
// and cases where BaseType = object will not reach the TypeDiscriminatingNodeDeserializer
return WithNodeDeserializer(typeDiscriminatingNodeDeserializer, s => s.Before());
}
///
/// Registers an additional to be used by the deserializer.
///
public DeserializerBuilder WithNodeTypeResolver(INodeTypeResolver nodeTypeResolver)
{
return WithNodeTypeResolver(nodeTypeResolver, w => w.OnTop());
}
///
/// Registers an additional to be used by the deserializer.
///
///
/// Configures the location where to insert the
public DeserializerBuilder WithNodeTypeResolver(
INodeTypeResolver nodeTypeResolver,
Action> where
)
{
if (nodeTypeResolver == null)
{
throw new ArgumentNullException(nameof(nodeTypeResolver));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(nodeTypeResolverFactories.CreateRegistrationLocationSelector(nodeTypeResolver.GetType(), _ => nodeTypeResolver));
return this;
}
///
/// Registers an additional to be used by the deserializer.
///
/// A factory that creates the based on a previously registered .
/// Configures the location where to insert the
public DeserializerBuilder WithNodeTypeResolver(
WrapperFactory nodeTypeResolverFactory,
Action> where
)
where TNodeTypeResolver : INodeTypeResolver
{
if (nodeTypeResolverFactory == null)
{
throw new ArgumentNullException(nameof(nodeTypeResolverFactory));
}
if (where == null)
{
throw new ArgumentNullException(nameof(where));
}
where(nodeTypeResolverFactories.CreateTrackingRegistrationLocationSelector(typeof(TNodeTypeResolver), (wrapped, _) => nodeTypeResolverFactory(wrapped)));
return this;
}
///
/// Ignore case when matching property names.
///
///
public DeserializerBuilder WithCaseInsensitivePropertyMatching()
{
caseInsensitivePropertyMatching = true;
return this;
}
///
/// Enforce whether null values can be set on non-nullable properties and fields.
///
/// This deserializer builder.
public DeserializerBuilder WithEnforceNullability()
{
enforceNullability = true;
return this;
}
///
/// Require that all members with the 'required' keyword be set by YAML.
///
///
public DeserializerBuilder WithEnforceRequiredMembers()
{
enforceRequiredProperties = true;
return this;
}
///
/// Unregisters an existing of type .
///
public DeserializerBuilder WithoutNodeTypeResolver()
where TNodeTypeResolver : INodeTypeResolver
{
return WithoutNodeTypeResolver(typeof(TNodeTypeResolver));
}
///
/// Unregisters an existing of type .
///
public DeserializerBuilder WithoutNodeTypeResolver(Type nodeTypeResolverType)
{
if (nodeTypeResolverType == null)
{
throw new ArgumentNullException(nameof(nodeTypeResolverType));
}
nodeTypeResolverFactories.Remove(nodeTypeResolverType);
return this;
}
///
/// Registers a tag mapping.
///
public override DeserializerBuilder 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(tag, out var alreadyRegisteredType))
{
throw new ArgumentException($"Type already has a registered type '{alreadyRegisteredType.FullName}' for tag '{tag}'", nameof(tag));
}
tagMappings.Add(tag, type);
return this;
}
///
/// Registers a type mapping using the default object factory.
///
public DeserializerBuilder WithTypeMapping()
where TConcrete : TInterface
{
var interfaceType = typeof(TInterface);
var concreteType = typeof(TConcrete);
if (!interfaceType.IsAssignableFrom(concreteType))
{
throw new InvalidOperationException($"The type '{concreteType.Name}' does not implement interface '{interfaceType.Name}'.");
}
if (!typeMappings.TryAdd(interfaceType, concreteType))
{
typeMappings[interfaceType] = concreteType;
}
return this;
}
///
/// Unregisters an existing tag mapping.
///
public DeserializerBuilder WithoutTagMapping(TagName tag)
{
if (tag.IsEmpty)
{
throw new ArgumentException("Non-specific tags cannot be maped");
}
if (!tagMappings.Remove(tag))
{
throw new KeyNotFoundException($"Tag '{tag}' is not registered");
}
return this;
}
///
/// Instructs the deserializer to ignore unmatched properties instead of throwing an exception.
///
public DeserializerBuilder IgnoreUnmatchedProperties()
{
ignoreUnmatched = true;
return this;
}
///
/// Instructs the deserializer to check for duplicate keys and throw an exception if duplicate keys are found.
///
///
public DeserializerBuilder WithDuplicateKeyChecking()
{
duplicateKeyChecking = true;
return this;
}
///
/// Creates a new according to the current configuration.
///
public IDeserializer Build()
{
return Deserializer.FromValueDeserializer(BuildValueDeserializer());
}
///
/// 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 IValueDeserializer BuildValueDeserializer()
{
return new AliasValueDeserializer(
new NodeValueDeserializer(
nodeDeserializerFactories.BuildComponentList(),
nodeTypeResolverFactories.BuildComponentList(),
typeConverter,
enumNamingConvention,
BuildTypeInspector()
)
);
}
}
}