// 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.Runtime.CompilerServices; #if NET7_0_OR_GREATER using System.Diagnostics.CodeAnalysis; #endif using YamlDotNet.Core; 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. /// public sealed class StaticDeserializerBuilder : StaticBuilderSkeleton { private readonly StaticContext context; private readonly StaticObjectFactory factory; private readonly LazyComponentRegistrationList nodeDeserializerFactories; private readonly LazyComponentRegistrationList nodeTypeResolverFactories; private readonly Dictionary tagMappings; private readonly ITypeConverter typeConverter; private readonly Dictionary typeMappings; private bool ignoreUnmatched; private bool duplicateKeyChecking; private bool attemptUnknownTypeDeserialization; private bool enforceNullability; private bool caseInsensitivePropertyMatching; /// /// Initializes a new using the default component registrations. /// public StaticDeserializerBuilder(StaticContext context) : base(context.GetTypeResolver()) { this.context = context; factory = context.GetFactory(); typeMappings = new (); 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)); typeConverter = new NullTypeConverter(); nodeDeserializerFactories = new LazyComponentRegistrationList { { typeof(YamlConvertibleNodeDeserializer), _ => new YamlConvertibleNodeDeserializer(factory) }, { typeof(YamlSerializableNodeDeserializer), _ => new YamlSerializableNodeDeserializer(factory) }, { typeof(TypeConverterNodeDeserializer), _ => new TypeConverterNodeDeserializer(BuildTypeConverters()) }, { typeof(NullNodeDeserializer), _ => new NullNodeDeserializer() }, { typeof(ScalarNodeDeserializer), _ => new ScalarNodeDeserializer(attemptUnknownTypeDeserialization, typeConverter, BuildTypeInspector(), yamlFormatter, enumNamingConvention) }, { typeof(StaticArrayNodeDeserializer), _ => new StaticArrayNodeDeserializer(factory) }, { typeof(StaticDictionaryNodeDeserializer), _ => new StaticDictionaryNodeDeserializer(factory, duplicateKeyChecking) }, { typeof(StaticCollectionNodeDeserializer), _ => new StaticCollectionNodeDeserializer(factory) }, { typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(factory, BuildTypeInspector(), ignoreUnmatched, duplicateKeyChecking, typeConverter, enumNamingConvention, enforceNullability, caseInsensitivePropertyMatching, false, // the static builder doesn't support required attributes BuildTypeConverters()) }, }; 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 StaticDeserializerBuilder 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 = context.GetTypeInspector(); 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 StaticDeserializerBuilder WithAttemptingUnquotedStringTypeDeserialization() { attemptUnknownTypeDeserialization = true; return this; } /// /// Registers an additional to be used by the deserializer. /// public StaticDeserializerBuilder 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 StaticDeserializerBuilder 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 StaticDeserializerBuilder 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; } /// /// Ignore case when matching property names. /// /// public StaticDeserializerBuilder WithCaseInsensitivePropertyMatching() { caseInsensitivePropertyMatching = true; return this; } /// /// Enforce whether null values can be set on non-nullable properties and fields. /// /// This static deserializer builder. public StaticDeserializerBuilder WithEnforceNullability() { enforceNullability = true; return this; } /// /// Unregisters an existing of type . /// public StaticDeserializerBuilder WithoutNodeDeserializer() where TNodeDeserializer : INodeDeserializer { return WithoutNodeDeserializer(typeof(TNodeDeserializer)); } /// /// Unregisters an existing of type . /// public StaticDeserializerBuilder 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 StaticDeserializerBuilder 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 StaticDeserializerBuilder 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 StaticDeserializerBuilder 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 StaticDeserializerBuilder 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; } /// /// Unregisters an existing of type . /// public StaticDeserializerBuilder WithoutNodeTypeResolver() where TNodeTypeResolver : INodeTypeResolver { return WithoutNodeTypeResolver(typeof(TNodeTypeResolver)); } /// /// Unregisters an existing of type . /// public StaticDeserializerBuilder WithoutNodeTypeResolver(Type nodeTypeResolverType) { if (nodeTypeResolverType == null) { throw new ArgumentNullException(nameof(nodeTypeResolverType)); } nodeTypeResolverFactories.Remove(nodeTypeResolverType); return this; } /// /// Registers a tag mapping. /// public override StaticDeserializerBuilder 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 StaticDeserializerBuilder 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}'."); } typeMappings[interfaceType] = concreteType; return this; } /// /// Unregisters an existing tag mapping. /// public StaticDeserializerBuilder 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 StaticDeserializerBuilder IgnoreUnmatchedProperties() { ignoreUnmatched = true; return this; } /// /// Instructs the deserializer to check for duplicate keys and throw an exception if duplicate keys are found. /// /// public StaticDeserializerBuilder 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() ) ); } } }