From b735083daa14aed0810c85d9daff5c7487589cb0 Mon Sep 17 00:00:00 2001 From: Edward Cooke Date: Sun, 4 Feb 2024 11:39:36 -0700 Subject: [PATCH] Fixed #901 - private constructor and tag issue --- .../Serialization/PrivateConstructorTests.cs | 130 ++++++++++++++++++ YamlDotNet/ReflectionExtensions.cs | 11 +- .../TypeAssigningEventEmitter.cs | 13 -- YamlDotNet/Serialization/SerializerBuilder.cs | 2 - .../Serialization/StaticSerializerBuilder.cs | 2 - 5 files changed, 138 insertions(+), 20 deletions(-) diff --git a/YamlDotNet.Test/Serialization/PrivateConstructorTests.cs b/YamlDotNet.Test/Serialization/PrivateConstructorTests.cs index a3802152..0c35ee58 100644 --- a/YamlDotNet.Test/Serialization/PrivateConstructorTests.cs +++ b/YamlDotNet.Test/Serialization/PrivateConstructorTests.cs @@ -20,6 +20,7 @@ // SOFTWARE. using System; +using System.IO; using Xunit; using YamlDotNet.Core; using YamlDotNet.Serialization; @@ -101,6 +102,135 @@ public void PrivateConstructorsAreNotConsideredWhenNotEnabled() Assert.IsType(exception.InnerException); Assert.IsType(exception.InnerException.InnerException); } + + [Fact] + public void InternalConstructors() + { + Test(() => new TestClassInternal("test"), true); + Test(() => new TestClassInternal("test"), false); + } + + [Fact] + public void PrivateConstructors() + { + Test(() => new TestClassPrivate("test"), true); + Test(() => new TestClassPrivate("test"), false); + } + + [Fact] + public void ProtectedConstructors() + { + Test(() => new TestClassProtected("test"), true); + Test(() => new TestClassProtected("test"), false); + } + + [Fact] + public void PublicConstructors() + { + Test(() => new TestClassPublic("test"), true); + Test(() => new TestClassPublic("test"), false); + } + + void Test(Func constructor, bool ensureRoundTrip) + { + var testClass = constructor(); + var serializerBuilder = new SerializerBuilder() + .EnablePrivateConstructors() + .IncludeNonPublicProperties(); + + if (ensureRoundTrip) + { + serializerBuilder = serializerBuilder.EnsureRoundtrip(); + } + var serializer = serializerBuilder.Build(); + + var deserializer = new DeserializerBuilder() + .EnablePrivateConstructors() + .IncludeNonPublicProperties() + .Build(); + + var stringWriter = new StringWriter(); + serializer.Serialize(stringWriter, testClass); + var o = deserializer.Deserialize(stringWriter.ToString()); + } + + public class TestClassInternal + { + internal TestClassInternal() + { + } + + public TestClassInternal(string value) + { + Value = value; + } + + public string Value { get; private set; } + + public override string ToString() + { + return Value; + } + } + + public class TestClassPublic + { + public TestClassPublic() + { + } + + public TestClassPublic(string value) + { + Value = value; + } + + public string Value { get; private set; } + + public override string ToString() + { + return Value; + } + } + + public class TestClassPrivate + { + public TestClassPrivate() + { + } + + public TestClassPrivate(string value) + { + Value = value; + } + + public string Value { get; private set; } + + public override string ToString() + { + return Value; + } + } + + public class TestClassProtected + { + public TestClassProtected() + { + } + + public TestClassProtected(string value) + { + Value = value; + } + + public string Value { get; private set; } + + public override string ToString() + { + return Value; + } + } + + private class Outer { public string Value { get; set; } diff --git a/YamlDotNet/ReflectionExtensions.cs b/YamlDotNet/ReflectionExtensions.cs index ebafdc48..25b83656 100644 --- a/YamlDotNet/ReflectionExtensions.cs +++ b/YamlDotNet/ReflectionExtensions.cs @@ -68,9 +68,14 @@ public static bool IsEnum(this Type type) /// public static bool HasDefaultConstructor(this Type type, bool allowPrivateConstructors) { - var typeInfo = type.GetTypeInfo(); - return typeInfo.IsValueType || typeInfo.DeclaredConstructors - .Any(c => (c.IsPublic || (allowPrivateConstructors && c.IsPrivate)) && !c.IsStatic && c.GetParameters().Length == 0); + var bindingFlags = BindingFlags.Instance | BindingFlags.Public; + + if (allowPrivateConstructors) + { + bindingFlags |= BindingFlags.NonPublic; + } + + return type.IsValueType || type.GetConstructor(bindingFlags, null, Type.EmptyTypes, null) != null; } public static bool IsAssignableFrom(this Type type, Type source) diff --git a/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs b/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs index c81da5f3..e84b9b69 100644 --- a/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs +++ b/YamlDotNet/Serialization/EventEmitters/TypeAssigningEventEmitter.cs @@ -32,7 +32,6 @@ namespace YamlDotNet.Serialization.EventEmitters { public sealed class TypeAssigningEventEmitter : ChainedEventEmitter { - private readonly bool requireTagWhenStaticAndActualTypesAreDifferent; private readonly IDictionary tagMappings; private readonly bool quoteNecessaryStrings; private readonly Regex? isSpecialStringValue_Regex; @@ -73,7 +72,6 @@ public sealed class TypeAssigningEventEmitter : ChainedEventEmitter private readonly INamingConvention enumNamingConvention; public TypeAssigningEventEmitter(IEventEmitter nextEmitter, - bool requireTagWhenStaticAndActualTypesAreDifferent, IDictionary tagMappings, bool quoteNecessaryStrings, bool quoteYaml1_1Strings, @@ -82,7 +80,6 @@ public TypeAssigningEventEmitter(IEventEmitter nextEmitter, INamingConvention enumNamingConvention) : base(nextEmitter) { - this.requireTagWhenStaticAndActualTypesAreDifferent = requireTagWhenStaticAndActualTypesAreDifferent; this.defaultScalarStyle = defaultScalarStyle; this.formatter = formatter; this.tagMappings = tagMappings; @@ -230,16 +227,6 @@ private void AssignTypeIfNeeded(ObjectEventInfo eventInfo) { eventInfo.Tag = tag; } - else if (requireTagWhenStaticAndActualTypesAreDifferent && eventInfo.Source.Value != null && eventInfo.Source.Type != eventInfo.Source.StaticType) - { - throw new YamlException( - $"Cannot serialize type '{eventInfo.Source.Type.FullName}' where a '{eventInfo.Source.StaticType.FullName}' was expected " - + $"because no tag mapping has been registered for '{eventInfo.Source.Type.FullName}', " - + $"which means that it won't be possible to deserialize the document.\n" - + $"Register a tag mapping using the SerializerBuilder.WithTagMapping method.\n\n" - + $"E.g: builder.WithTagMapping(\"!{eventInfo.Source.Type.Name}\", typeof({eventInfo.Source.Type.FullName}));" - ); - } } private bool IsSpecialStringValue(string value) diff --git a/YamlDotNet/Serialization/SerializerBuilder.cs b/YamlDotNet/Serialization/SerializerBuilder.cs index 44793843..74b927f6 100755 --- a/YamlDotNet/Serialization/SerializerBuilder.cs +++ b/YamlDotNet/Serialization/SerializerBuilder.cs @@ -101,7 +101,6 @@ public SerializerBuilder() { typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, - false, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, @@ -295,7 +294,6 @@ public SerializerBuilder EnsureRoundtrip() WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, - true, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, diff --git a/YamlDotNet/Serialization/StaticSerializerBuilder.cs b/YamlDotNet/Serialization/StaticSerializerBuilder.cs index 9a29cf78..a0de9755 100644 --- a/YamlDotNet/Serialization/StaticSerializerBuilder.cs +++ b/YamlDotNet/Serialization/StaticSerializerBuilder.cs @@ -100,7 +100,6 @@ public StaticSerializerBuilder(StaticContext context) { typeof(TypeAssigningEventEmitter), inner => new TypeAssigningEventEmitter(inner, - false, tagMappings, quoteNecessaryStrings, quoteYaml1_1Strings, @@ -299,7 +298,6 @@ public StaticSerializerBuilder EnsureRoundtrip() factory ); WithEventEmitter(inner => new TypeAssigningEventEmitter(inner, - true, tagMappings, quoteNecessaryStrings, false,