Skip to content

Commit

Permalink
Merge pull request #902 from EdwardCooke/ec-901
Browse files Browse the repository at this point in the history
Fixed #901 - private constructor and tag issue

+semver:fix
  • Loading branch information
EdwardCooke authored Feb 4, 2024
2 parents fa65881 + b735083 commit 19d7875
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 20 deletions.
130 changes: 130 additions & 0 deletions YamlDotNet.Test/Serialization/PrivateConstructorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// SOFTWARE.

using System;
using System.IO;
using Xunit;
using YamlDotNet.Core;
using YamlDotNet.Serialization;
Expand Down Expand Up @@ -101,6 +102,135 @@ public void PrivateConstructorsAreNotConsideredWhenNotEnabled()
Assert.IsType<InvalidOperationException>(exception.InnerException);
Assert.IsType<MissingMethodException>(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<T>(Func<T> 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<T>(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; }
Expand Down
11 changes: 8 additions & 3 deletions YamlDotNet/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,14 @@ public static bool IsEnum(this Type type)
/// </returns>
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ namespace YamlDotNet.Serialization.EventEmitters
{
public sealed class TypeAssigningEventEmitter : ChainedEventEmitter
{
private readonly bool requireTagWhenStaticAndActualTypesAreDifferent;
private readonly IDictionary<Type, TagName> tagMappings;
private readonly bool quoteNecessaryStrings;
private readonly Regex? isSpecialStringValue_Regex;
Expand Down Expand Up @@ -73,7 +72,6 @@ public sealed class TypeAssigningEventEmitter : ChainedEventEmitter
private readonly INamingConvention enumNamingConvention;

public TypeAssigningEventEmitter(IEventEmitter nextEmitter,
bool requireTagWhenStaticAndActualTypesAreDifferent,
IDictionary<Type, TagName> tagMappings,
bool quoteNecessaryStrings,
bool quoteYaml1_1Strings,
Expand All @@ -82,7 +80,6 @@ public TypeAssigningEventEmitter(IEventEmitter nextEmitter,
INamingConvention enumNamingConvention)
: base(nextEmitter)
{
this.requireTagWhenStaticAndActualTypesAreDifferent = requireTagWhenStaticAndActualTypesAreDifferent;
this.defaultScalarStyle = defaultScalarStyle;
this.formatter = formatter;
this.tagMappings = tagMappings;
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 0 additions & 2 deletions YamlDotNet/Serialization/SerializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ public SerializerBuilder()
{
typeof(TypeAssigningEventEmitter), inner =>
new TypeAssigningEventEmitter(inner,
false,
tagMappings,
quoteNecessaryStrings,
quoteYaml1_1Strings,
Expand Down Expand Up @@ -295,7 +294,6 @@ public SerializerBuilder EnsureRoundtrip()

WithEventEmitter(inner =>
new TypeAssigningEventEmitter(inner,
true,
tagMappings,
quoteNecessaryStrings,
quoteYaml1_1Strings,
Expand Down
2 changes: 0 additions & 2 deletions YamlDotNet/Serialization/StaticSerializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ public StaticSerializerBuilder(StaticContext context)
{
typeof(TypeAssigningEventEmitter), inner =>
new TypeAssigningEventEmitter(inner,
false,
tagMappings,
quoteNecessaryStrings,
quoteYaml1_1Strings,
Expand Down Expand Up @@ -299,7 +298,6 @@ public StaticSerializerBuilder EnsureRoundtrip()
factory
);
WithEventEmitter(inner => new TypeAssigningEventEmitter(inner,
true,
tagMappings,
quoteNecessaryStrings,
false,
Expand Down

0 comments on commit 19d7875

Please sign in to comment.