diff --git a/YamlDotNet.Test/Serialization/BufferedDeserialization/KeyValueTypeDiscriminatorTests.cs b/YamlDotNet.Test/Serialization/BufferedDeserialization/KeyValueTypeDiscriminatorTests.cs new file mode 100644 index 00000000..cf7f14f6 --- /dev/null +++ b/YamlDotNet.Test/Serialization/BufferedDeserialization/KeyValueTypeDiscriminatorTests.cs @@ -0,0 +1,265 @@ +// 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.Linq; +using FluentAssertions; +using Xunit; +using YamlDotNet.Core; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace YamlDotNet.Test.Serialization.BufferedDeserialization +{ + public class KeyValueTypeDiscriminatorTests + { + [Fact] + public void KeyValueTypeDiscriminator_WithParentBaseType_Single() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 40) + .Build(); + + var service = bufferedDeserializer.Deserialize(KubernetesServiceYaml); + service.Should().BeOfType(); + } + + [Fact] + public void KeyValueTypeDiscriminator_WithParentBaseType_List() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 40) + .Build(); + + var resources = bufferedDeserializer.Deserialize>(ListOfKubernetesYaml); + resources[0].Should().BeOfType(); + resources[1].Should().BeOfType(); + } + + [Fact] + public void KeyValueTypeDiscriminator_WithObjectBaseType_Single() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 40) + .Build(); + + var service = bufferedDeserializer.Deserialize(KubernetesServiceYaml); + service.Should().BeOfType(); + } + + [Fact] + public void KeyValueTypeDiscriminator_WithObjectBaseType_List() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 30) + .Build(); + + var resources = bufferedDeserializer.Deserialize>(ListOfKubernetesYaml); + resources[0].Should().BeOfType(); + resources[1].Should().BeOfType(); + } + + [Fact] + public void KeyValueTypeDiscriminator_WithInterfaceBaseType_Single() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 40) + .Build(); + + var service = bufferedDeserializer.Deserialize(KubernetesServiceYaml); + service.Should().BeOfType(); + } + + [Fact] + public void KeyValueTypeDiscriminator_WithInterfaceBaseType_List() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 30) + .Build(); + + var resources = bufferedDeserializer.Deserialize>(ListOfKubernetesYaml); + resources[0].Should().BeOfType(); + resources[1].Should().BeOfType(); + } + + [Fact] + public void KeyValueTypeDiscriminator_MultipleWithSameKey() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Namespace", typeof(KubernetesNamespace) }, + }); + options.AddKeyValueTypeDiscriminator( + "kind", + new Dictionary() + { + { "Service", typeof(KubernetesService) } + }); + }, + maxDepth: 3, + maxLength: 40) + .Build(); + + var resources = bufferedDeserializer.Deserialize>(ListOfKubernetesYaml); + resources[0].Should().BeOfType(); + resources[1].Should().BeOfType(); + } + + public const string ListOfKubernetesYaml = @" +- apiVersion: v1 + kind: Namespace + metadata: + name: test-namespace +- apiVersion: v1 + kind: Service + metadata: + name: my-service + spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - protocol: TCP + port: 80 + targetPort: 9376 +"; + + public const string KubernetesServiceYaml = @" +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - protocol: TCP + port: 80 + targetPort: 9376 +"; + + public interface IKubernetesResource { } + + public class KubernetesResource : IKubernetesResource + { + public string ApiVersion { get; set; } + public string Kind { get; set; } + public KubernetesMetadata Metadata { get; set; } + + public class KubernetesMetadata + { + public string Name { get; set; } + } + } + + public class KubernetesService : KubernetesResource + { + public KubernetesServiceSpec Spec { get; set; } + public class KubernetesServiceSpec + { + public Dictionary Selector { get; set; } + public List Ports { get; set; } + public class KubernetesServicePort + { + public string Protocol { get; set; } + public int Port { get; set; } + public int TargetPort { get; set; } + } + } + } + + public class KubernetesNamespace : KubernetesResource + { + + } + } +} diff --git a/YamlDotNet.Test/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializerTests.cs b/YamlDotNet.Test/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializerTests.cs new file mode 100644 index 00000000..34ec355f --- /dev/null +++ b/YamlDotNet.Test/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializerTests.cs @@ -0,0 +1,89 @@ +// 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.Linq; +using FluentAssertions; +using Xunit; +using YamlDotNet.Core; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace YamlDotNet.Test.Serialization.BufferedDeserialization +{ + public class TypeDiscriminatingNodeDeserializerTests + { + [Fact] + public void TypeDiscriminatingNodeDeserializer_ThrowsWhen_MaxDepthExceeded() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator("kind", new Dictionary()); + }, + maxDepth: 2, + maxLength: 40) + .Build(); + + Action act = () => bufferedDeserializer.Deserialize(KubernetesServiceYaml); + act + .ShouldThrow() + .WithMessage("Failed to buffer yaml node") + .WithInnerException() + .Where(e => e.InnerException.Message.Contains("Parser buffer exceeded max depth")); + } + + [Fact] + public void TypeDiscriminatingNodeDeserializer_ThrowsWhen_MaxLengthExceeded() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddKeyValueTypeDiscriminator("kind", new Dictionary()); + }, + maxDepth: 3, + maxLength: 20) + .Build(); + + Action act = () => bufferedDeserializer.Deserialize(KubernetesServiceYaml); + act + .ShouldThrow() + .WithMessage("Failed to buffer yaml node") + .WithInnerException() + .Where(e => e.InnerException.Message.Contains("Parser buffer exceeded max length")); + } + + public const string KubernetesServiceYaml = @" +apiVersion: v1 +kind: Service +metadata: + name: my-service +spec: + selector: + app.kubernetes.io/name: MyApp + ports: + - protocol: TCP + port: 80 + targetPort: 9376 +"; + } +} diff --git a/YamlDotNet.Test/Serialization/BufferedDeserialization/UniqueKeyTypeDiscriminatorTests.cs b/YamlDotNet.Test/Serialization/BufferedDeserialization/UniqueKeyTypeDiscriminatorTests.cs new file mode 100644 index 00000000..6d26e10c --- /dev/null +++ b/YamlDotNet.Test/Serialization/BufferedDeserialization/UniqueKeyTypeDiscriminatorTests.cs @@ -0,0 +1,103 @@ +// 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.Linq; +using FluentAssertions; +using Xunit; +using YamlDotNet.Core; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace YamlDotNet.Test.Serialization.BufferedDeserialization +{ + public class UniqueKeyTypeDiscriminatorTests + { + [Fact] + public void UniqueKeyTypeDiscriminator_WithInterfaceBaseType() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddUniqueKeyTypeDiscriminator( + new Dictionary() + { + { "cheeseSupply", typeof(Mouse) }, + { "avgDailyMeows", typeof(Cat) } + } + ); + }, + maxDepth: 3, + maxLength: 10) + .Build(); + + var characters = bufferedDeserializer.Deserialize>(TomAndJerryYaml); + characters[0].Should().BeOfType(); + characters[1].Should().BeOfType(); + } + + [Fact] + public void UniqueKeyTypeDiscriminator_WithObjectBaseType() + { + var bufferedDeserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeDiscriminatingNodeDeserializer(options => { + options.AddUniqueKeyTypeDiscriminator( + new Dictionary() + { + { "cheeseSupply", typeof(Mouse) }, + { "avgDailyMeows", typeof(Cat) } + } + ); + }, + maxDepth: 3, + maxLength: 10) + .Build(); + + var charactersObj = bufferedDeserializer.Deserialize(TomAndJerryYaml); + var characters = (List)charactersObj; + characters[0].Should().BeOfType(); + characters[1].Should().BeOfType(); + } + + public const string TomAndJerryYaml = @" +- name: Jerry + cheeseSupply: 5 +- name: Tom + avgDailyMeows: 20.0 +"; + + public interface ICharacter { } + + public class Mouse : ICharacter + { + public string Name { get; set; } + public int CheeseSupply { get; set; } + } + + public class Cat : ICharacter + { + public string Name { get; set; } + public float AvgDailyMeows { get; set; } + } + } +} diff --git a/YamlDotNet/Core/ParserExtensions.cs b/YamlDotNet/Core/ParserExtensions.cs index 674de2bd..b94e875a 100644 --- a/YamlDotNet/Core/ParserExtensions.cs +++ b/YamlDotNet/Core/ParserExtensions.cs @@ -147,5 +147,71 @@ public static bool Accept(this IParser parser) where T : ParsingEvent { return Accept(parser, out var _); } + + /// + /// Attempts to find a key on a YAML mapping that matches our predicate. + /// This is useful for scanning a mapping for type discriminator information. + /// For example: looking for a `kind` key on an object. + /// + /// This function only checks mappings, and only looks at the current depth. + /// + /// If the event is a mapping and has a key that satisfies the predicate the scan + /// will stop, return true, and set and + /// . All events up until the predicate is matched will + /// be consumed. + /// + /// If the event is not a mapping event or a matching key is not found, returns false. + /// + /// The IParser which will have its current value checked for a matching mapping entry + /// The selector to filter the mapping by + /// The matching key of the mapping as a Scalar, or null if no matching key found + /// The matching value of the mapping as a ParsingEvent, or null if no matching key found + /// Returns true if the current event is a mapping entry with a key that matches the selector; + /// otherwise returns false. + public static bool TryFindMappingEntry(this IParser parser, Func selector, [MaybeNullWhen(false)] out Scalar? key, [MaybeNullWhen(false)] out ParsingEvent? value) + { + if (parser.TryConsume(out var _start)) + { + while (parser.Current != null) + { + // so we only want to check keys in this mapping, don't descend + switch (parser.Current) + { + case Scalar scalar: + // we've found a scalar, check if it's value matches our predicate + var keyMatched = selector(scalar); + + // move head so we can read or skip value + parser.MoveNext(); + + // read the value of the mapping key + if (keyMatched) + { + // success + value = parser.Current; + key = scalar; + return true; + } + + // skip the value + parser.SkipThisAndNestedEvents(); + + break; + case MappingStart _: + case SequenceStart _: + parser.SkipThisAndNestedEvents(); + break; + default: + // do nothing, skip to next node + parser.MoveNext(); + break; + } + } + } + + key = null; + value = null; + return false; + } } } diff --git a/YamlDotNet/Serialization/BufferedDeserialization/ITypeDiscriminatingNodeDeserializerOptions.cs b/YamlDotNet/Serialization/BufferedDeserialization/ITypeDiscriminatingNodeDeserializerOptions.cs new file mode 100644 index 00000000..a8678dba --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/ITypeDiscriminatingNodeDeserializerOptions.cs @@ -0,0 +1,47 @@ +// 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.Linq; +using System.Collections.Generic; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.Serialization.NodeDeserializers; +using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; + +namespace YamlDotNet.Serialization.BufferedDeserialization +{ + public interface ITypeDiscriminatingNodeDeserializerOptions + { + public void AddTypeDiscriminator(ITypeDiscriminator discriminator); + public void AddKeyValueTypeDiscriminator(string discriminatorKey, IDictionary valueTypeMapping); + +#if NET7_0_OR_GREATER + public void AddKeyValueTypeDiscriminator(string discriminatorKey, params (string, Type)[] valueTypeMapping) => AddKeyValueTypeDiscriminator(discriminatorKey, valueTypeMapping.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2)); +#endif + + public void AddUniqueKeyTypeDiscriminator(IDictionary uniqueKeyTypeMapping); + +#if NET7_0_OR_GREATER + public void AddUniqueKeyTypeDiscriminator(params (string, Type)[] uniqueKeyTypeMapping) => AddUniqueKeyTypeDiscriminator(uniqueKeyTypeMapping.ToDictionary(kvp => kvp.Item1, kvp => kvp.Item2)); +#endif + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs b/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs new file mode 100644 index 00000000..2128f7f5 --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/ParserBuffer.cs @@ -0,0 +1,92 @@ +// 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.Core.Events; + +namespace YamlDotNet.Serialization.BufferedDeserialization +{ + /// + /// Wraps a instance and allows it to be buffered as a LinkedList in memory and replayed. + /// + public class ParserBuffer : IParser + { + private readonly LinkedList buffer; + + private LinkedListNode? current; + + /// + /// Initializes a new instance of the class. + /// + /// The Parser to buffer. + /// The maximum depth of the parser to buffer before raising an ArgumentOutOfRangeException. + /// The maximum length of the LinkedList can buffer before raising an ArgumentOutOfRangeException. + /// If parser does not fit within the max depth and length specified. + public ParserBuffer(IParser parserToBuffer, int maxDepth, int maxLength) + { + buffer = new LinkedList(); + buffer.AddLast(parserToBuffer.Consume()); + var depth = 0; + do + { + var next = parserToBuffer.Consume(); + depth += next.NestingIncrease; + buffer.AddLast(next); + + if (maxDepth > -1 && depth > maxDepth) + { + throw new ArgumentOutOfRangeException(nameof(parserToBuffer), "Parser buffer exceeded max depth"); + } + if (maxLength > -1 && buffer.Count > maxLength) + { + throw new ArgumentOutOfRangeException(nameof(parserToBuffer), "Parser buffer exceeded max length"); + } + } while (depth >= 0); + + current = buffer.First; + } + + /// + /// Gets the current event. Returns null after returns false. + /// + public ParsingEvent? Current => current?.Value; + + /// + /// Moves to the next event. + /// + /// Returns true if there are more events available, otherwise returns false. + public bool MoveNext() + { + current = current?.Next; + return current != null; + } + + /// + /// Resets the buffer back to it's first event. + /// + public void Reset() + { + current = buffer.First; + } + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializer.cs b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializer.cs new file mode 100644 index 00000000..5c0466ba --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializer.cs @@ -0,0 +1,120 @@ +// 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.Linq; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NodeDeserializers; +using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; + +namespace YamlDotNet.Serialization.BufferedDeserialization +{ + /// + /// The TypeDiscriminatingNodeDeserializer acts as a psuedo . + /// If any of it's has a matching BaseType, the TypeDiscriminatingNodeDeserializer will + /// begin buffering the yaml stream. It will then use the matching s to determine + /// a dotnet output type for the yaml node. As the node is buffered, the s are + /// able to examine the actual values within, and use these when discriminating a type. + /// Once a matching type is found, the TypeDiscriminatingNodeDeserializer uses it's inner deserializers to perform + /// the final deserialization for that type & object. + /// Usually you will want all default s that exist in the outer + /// to also be used as inner deserializers. + /// + public class TypeDiscriminatingNodeDeserializer : INodeDeserializer + { + private readonly IList innerDeserializers; + private readonly IList typeDiscriminators; + private readonly int maxDepthToBuffer; + private readonly int maxLengthToBuffer; + + public TypeDiscriminatingNodeDeserializer(IList innerDeserializers, IList typeDiscriminators, int maxDepthToBuffer, int maxLengthToBuffer) + { + this.innerDeserializers = innerDeserializers; + this.typeDiscriminators = typeDiscriminators; + this.maxDepthToBuffer = maxDepthToBuffer; + this.maxLengthToBuffer = maxLengthToBuffer; + } + + public bool Deserialize(IParser reader, Type expectedType, Func nestedObjectDeserializer, out object? value) + { + if (!reader.Accept(out var mapping)) + { + value = null; + return false; + } + + // Can any of the registered discriminators deal with the expected type? + var possibleDiscriminators = typeDiscriminators.Where(t => t.BaseType.IsAssignableFrom(expectedType)); + if (!possibleDiscriminators.Any()) + { + value = null; + return false; + } + + // Now buffer all the nodes in this mapping + var start = reader.Current!.Start; + Type actualType = expectedType; + ParserBuffer buffer; + try + { + buffer = new ParserBuffer(reader, maxDepth: maxDepthToBuffer, maxLength: maxLengthToBuffer); + } + catch (Exception exception) + { + throw new YamlException(start, reader.Current.End, "Failed to buffer yaml node", exception); + } + + try + { + // use the discriminator to tell us what type it is really expecting by letting it inspect the parsing events + foreach (var discriminator in possibleDiscriminators) + { + buffer.Reset(); + if (discriminator.TryDiscriminate(buffer, out var descriminatedType)) + { + actualType = descriminatedType!; + break; + } + } + } + catch (Exception exception) + { + throw new YamlException(start, reader.Current.End, "Failed to discriminate type", exception); + } + + // now continue by re-emitting parsing events and using the inner deserializers to handle + buffer.Reset(); + foreach (var deserializer in innerDeserializers) + { + if (deserializer.Deserialize(buffer, actualType, nestedObjectDeserializer, out value)) + { + return true; + } + } + + value = null; + return false; + } + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializerOptions.cs b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializerOptions.cs new file mode 100644 index 00000000..7cec41d3 --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminatingNodeDeserializerOptions.cs @@ -0,0 +1,67 @@ +// 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.Serialization; +using YamlDotNet.Serialization.NamingConventions; +using YamlDotNet.Serialization.NodeDeserializers; +using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; + +namespace YamlDotNet.Serialization.BufferedDeserialization +{ + public class TypeDiscriminatingNodeDeserializerOptions : ITypeDiscriminatingNodeDeserializerOptions + { + internal readonly List discriminators = new List(); + + /// + /// Adds an to be checked by the TypeDiscriminatingNodeDeserializer. + /// + /// The to add. + public void AddTypeDiscriminator(ITypeDiscriminator discriminator) + { + this.discriminators.Add(discriminator); + } + + /// + /// Adds a to be checked by the TypeDiscriminatingNodeDeserializer. + /// s use the value of a specified key on the yaml object to map + /// to a target type. + /// + /// The yaml key to discriminate on. + /// A dictionary of values for the yaml key mapping to their respective types. + public void AddKeyValueTypeDiscriminator(string discriminatorKey, IDictionary valueTypeMapping) + { + this.discriminators.Add(new KeyValueTypeDiscriminator(typeof(T), discriminatorKey, valueTypeMapping)); + } + + /// + /// Adds a to be checked by the TypeDiscriminatingNodeDeserializer. + /// s use the presence of unique keys on the yaml object to map + /// to different target types. + /// + /// A dictionary of unique yaml keys mapping to their respective types. + public void AddUniqueKeyTypeDiscriminator(IDictionary uniqueKeyTypeMapping) + { + this.discriminators.Add(new UniqueKeyTypeDiscriminator(typeof(T), uniqueKeyTypeMapping)); + } + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/IValueTypeDiscriminator.cs b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/IValueTypeDiscriminator.cs new file mode 100644 index 00000000..5f5e0598 --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/IValueTypeDiscriminator.cs @@ -0,0 +1,51 @@ +// 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 YamlDotNet.Core; + +namespace YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators +{ + /// + /// An ITypeDiscriminator provides an interface for discriminating which dotnet type to deserialize a yaml + /// stream into. They require the yaml stream to be buffered as they + /// can inspect the yaml value, determine the desired type, and reset the yaml stream to then deserialize into + /// that type. + /// + public interface ITypeDiscriminator + { + /// + /// Gets the BaseType of the discriminator. All types that an ITypeDiscriminator may discriminate into must + /// inherit from this type. This enables the deserializer to only buffer values of matching types. + /// If you would like an ITypeDiscriminator to discriminate all yaml values, the BaseType will be object. + /// + Type BaseType { get; } + + /// + /// Trys to discriminate a type from the current IParser. As discriminating the type will consume the parser, the + /// parser will usually need to be a buffer so an instance of the discriminated type can be deserialized later. + /// + /// The IParser to consume and discriminate a type from. + /// The output type discriminated. Null if no type matched the discriminator. + /// Returns true if the discriminator matched the yaml stream. + bool TryDiscriminate(IParser buffer, out Type? suggestedType); + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/KeyValueTypeDiscriminator.cs b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/KeyValueTypeDiscriminator.cs new file mode 100644 index 00000000..3006304a --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/KeyValueTypeDiscriminator.cs @@ -0,0 +1,96 @@ +// 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.Linq; +using System.Collections.Generic; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators +{ + /// + /// A TypeDiscriminator that discriminates which type to deserialize a yaml stream into by checking the value + /// of a known key. + /// + public class KeyValueTypeDiscriminator : ITypeDiscriminator + { + public Type BaseType { get; private set; } + private readonly string targetKey; + private readonly IDictionary typeMapping; + + /// + /// Initializes a new instance of the class. + /// The KeyValueTypeDiscriminator will check the target key specified, and if it's value is contained within the + /// type mapping dictionary, the coresponding type will be discriminated. + /// + /// The base type which all discriminated types will implement. Use object if you're discriminating + /// unrelated types. Note the less specific you are with the base type the more yaml will need to be buffered. + /// The known key to check the value of when discriminating. + /// A mapping dictionary of yaml values to types. + /// If any of the target types do not implement the base type. + public KeyValueTypeDiscriminator(Type baseType, string targetKey, IDictionary typeMapping) + { + foreach (var keyValuePair in typeMapping) + { + if (!baseType.IsAssignableFrom(keyValuePair.Value)) + { + throw new ArgumentOutOfRangeException(nameof(typeMapping), $"{keyValuePair.Value} is not a assignable to {baseType}"); + } + } + this.BaseType = baseType; + this.targetKey = targetKey; + this.typeMapping = typeMapping; + } + + /// + /// Checks if the current parser contains the target key, and that it's value matches one of the type mappings. + /// If so, return true, and the matching type. + /// Otherwise, return false. + /// This will consume the parser, so you will usually need the parser to be a buffer so an instance + /// of the discriminated type can be deserialized later. + /// + /// The IParser to consume and discriminate a type from. + /// The output type discriminated. Null if there target key was not present of if the value + /// of the target key was not within the type mapping. + /// Returns true if the discriminator matched the yaml stream. + public bool TryDiscriminate(IParser parser, out Type? suggestedType) + { + if (parser.TryFindMappingEntry( + scalar => targetKey == scalar.Value, + out Scalar? key, + out ParsingEvent? value)) + { + // read the value of the discriminator key + if (value is Scalar valueScalar && typeMapping.TryGetValue(valueScalar.Value, out var childType)) + { + suggestedType = childType; + return true; + } + } + + // we could not find our key, thus we could not determine correct child type + suggestedType = null; + return false; + } + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/UniqueKeyTypeDiscriminator.cs b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/UniqueKeyTypeDiscriminator.cs new file mode 100644 index 00000000..cd96c3c1 --- /dev/null +++ b/YamlDotNet/Serialization/BufferedDeserialization/TypeDiscriminators/UniqueKeyTypeDiscriminator.cs @@ -0,0 +1,88 @@ +// 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.Linq; +using System.Collections.Generic; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; + +namespace YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators +{ + /// + /// A TypeDiscriminator that discriminates which type to deserialize a yaml stream into by checking the existence + /// of specific keys. + /// + public class UniqueKeyTypeDiscriminator : ITypeDiscriminator + { + public Type BaseType { get; private set; } + + private readonly IDictionary typeMapping; + + /// + /// Initializes a new instance of the class. + /// The UniqueKeyTypeDiscriminator will check if any of the keys specified exist, and discriminate the coresponding type. + /// + /// The base type which all discriminated types will implement. Use object if you're discriminating + /// unrelated types. Note the less specific you are with the base type the more yaml will need to be buffered. + /// A mapping dictionary of yaml keys to types. + /// If any of the target types do not implement the base type. + public UniqueKeyTypeDiscriminator(Type baseType, IDictionary typeMapping) + { + foreach (var keyValuePair in typeMapping) + { + if (!baseType.IsAssignableFrom(keyValuePair.Value)) + { + throw new ArgumentOutOfRangeException(nameof(typeMapping), $"{keyValuePair.Value} is not a assignable to {baseType}"); + } + } + this.BaseType = baseType; + this.typeMapping = typeMapping; + } + + /// + /// Checks if the current parser contains of the unique keys this discriminator has in it's type mapping. + /// If so, return true, and the matching type. + /// Otherwise, return false. + /// This will consume the parser, so you will usually need the parser to be a buffer so an instance + /// of the discriminated type can be deserialized later. + /// + /// The IParser to consume and discriminate a type from. + /// The output type discriminated. Null if there target key was not present of if the value + /// of the target key was not within the type mapping. + /// Returns true if the discriminator matched the yaml stream. + public bool TryDiscriminate(IParser parser, out Type? suggestedType) + { + if (parser.TryFindMappingEntry( + scalar => this.typeMapping.ContainsKey(scalar.Value), + out Scalar key, + out ParsingEvent _)) + { + suggestedType = this.typeMapping[key.Value]; + return true; + } + + suggestedType = null; + return false; + } + } +} \ No newline at end of file diff --git a/YamlDotNet/Serialization/DeserializerBuilder.cs b/YamlDotNet/Serialization/DeserializerBuilder.cs index 6cbc3efa..5fafb145 100755 --- a/YamlDotNet/Serialization/DeserializerBuilder.cs +++ b/YamlDotNet/Serialization/DeserializerBuilder.cs @@ -25,6 +25,7 @@ using System.Diagnostics.CodeAnalysis; #endif using YamlDotNet.Core; +using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; @@ -246,6 +247,28 @@ public DeserializerBuilder WithoutNodeDeserializer(Type 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. /// diff --git a/YamlDotNet/Serialization/StaticDeserializerBuilder.cs b/YamlDotNet/Serialization/StaticDeserializerBuilder.cs index cbb51ff6..a763ff48 100644 --- a/YamlDotNet/Serialization/StaticDeserializerBuilder.cs +++ b/YamlDotNet/Serialization/StaticDeserializerBuilder.cs @@ -25,6 +25,7 @@ using System.Diagnostics.CodeAnalysis; #endif using YamlDotNet.Core; +using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; @@ -205,6 +206,28 @@ public StaticDeserializerBuilder WithoutNodeDeserializer(Type nodeDeserializerTy 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. ///