// 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.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.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 DeserializerBuilder : BuilderSkeleton { private Lazy objectFactory; private readonly LazyComponentRegistrationList nodeDeserializerFactories; private readonly LazyComponentRegistrationList nodeTypeResolverFactories; private readonly Dictionary tagMappings; private readonly Dictionary typeMappings; private bool ignoreUnmatched; /// /// Initializes a new using the default component registrations. /// public DeserializerBuilder() : base(new StaticTypeResolver()) { typeMappings = new Dictionary(); objectFactory = new Lazy(() => new DefaultObjectFactory(typeMappings), 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)); 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() }, { typeof(ArrayNodeDeserializer), _ => new ArrayNodeDeserializer() }, { typeof(DictionaryNodeDeserializer), _ => new DictionaryNodeDeserializer(objectFactory.Value) }, { typeof(CollectionNodeDeserializer), _ => new CollectionNodeDeserializer(objectFactory.Value) }, { typeof(EnumerableNodeDeserializer), _ => new EnumerableNodeDeserializer() }, { typeof(ObjectNodeDeserializer), _ => new ObjectNodeDeserializer(objectFactory.Value, BuildTypeInspector(), ignoreUnmatched) } }; 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; } } /// /// 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 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; } /// /// 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.ContainsKey(interfaceType)) { typeMappings[interfaceType] = concreteType; } else { typeMappings.Add(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; } /// /// 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() ) ); } } }