Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for private properties. #482

Merged
merged 1 commit into from Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 28 additions & 0 deletions YamlDotNet.Test/Serialization/SerializationTestHelper.cs
Expand Up @@ -539,4 +539,32 @@ public class NameConvention
[YamlIgnore]
public string fourthTest { get; set; }
}

public class NonPublicPropertiesExample
{
public string Public { get; set; } = "public";

internal string Internal { get; set; } = "internal";

protected string Protected { get; set; } = "protected";

private string Private { get; set; } = "private";

/// <inheritdoc />
public override string ToString() => $"{Public},{Internal},{Protected},{Private}";
}

public class NonPublicFieldsExample
{
public string Public = "public";

internal string Internal = "internal";

protected string Protected = "protected";

private string Private = "private";

/// <inheritdoc />
public override string ToString() => $"{Public},{Internal},{Protected},{Private}";
}
}
74 changes: 74 additions & 0 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Expand Up @@ -1764,6 +1764,80 @@ public void AnchorWithAllowedCharactersCanBeDeserialized()
Assert.Equal("some value", deserialized["interpolated value"]);
}

[Fact]
public void SerializationNonPublicPropertiesAreIgnored()
{
var sut = new SerializerBuilder().Build();
var yaml = sut.Serialize(new NonPublicPropertiesExample());
Assert.Equal("Public: public", yaml.TrimNewLines());
}

[Fact]
public void SerializationNonPublicPropertiesAreIncluded()
{
var sut = new SerializerBuilder().IncludeNonPublicProperties().Build();
var yaml = sut.Serialize(new NonPublicPropertiesExample());

var expected = Yaml.Text(@"
Public: public
Internal: internal
Protected: protected
Private: private
");

Assert.Equal(expected.NormalizeNewLines(), yaml.NormalizeNewLines().TrimNewLines());
}

[Fact]
public void DeserializationNonPublicPropertiesAreIgnored()
{
var sut = new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
var deserialized = sut.Deserialize<NonPublicPropertiesExample>(Yaml.ReaderForText(@"
Public: public2
Internal: internal2
Protected: protected2
Private: private2
"));

Assert.Equal("public2,internal,protected,private", deserialized.ToString());
}

[Fact]
public void DeserializationNonPublicPropertiesAreIncluded()
{
var sut = new DeserializerBuilder().IncludeNonPublicProperties().Build();
var deserialized = sut.Deserialize<NonPublicPropertiesExample>(Yaml.ReaderForText(@"
Public: public2
Internal: internal2
Protected: protected2
Private: private2
"));

Assert.Equal("public2,internal2,protected2,private2", deserialized.ToString());
}

[Fact]
public void SerializationNonPublicFieldsAreIgnored()
{
var sut = new SerializerBuilder().Build();
var yaml = sut.Serialize(new NonPublicFieldsExample());
Assert.Equal("Public: public", yaml.TrimNewLines());
}

[Fact]
public void DeserializationNonPublicFieldsAreIgnored()
{
var sut = new DeserializerBuilder().IgnoreUnmatchedProperties().Build();
var deserialized = sut.Deserialize<NonPublicFieldsExample>(Yaml.ReaderForText(@"
Public: public2
Internal: internal2
Protected: protected2
Private: private2
"));

Assert.Equal("public2,internal,protected,private", deserialized.ToString());
}

[TypeConverter(typeof(DoublyConvertedTypeConverter))]
public class DoublyConverted
{
Expand Down
40 changes: 29 additions & 11 deletions YamlDotNet/Helpers/Portability.cs
Expand Up @@ -182,15 +182,22 @@ public static Type[] GetGenericArguments(this Type type)
return type.GetRuntimeProperty(name);
}

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
public static IEnumerable<PropertyInfo> GetProperties(this Type type, bool includeNonPublic)
{
var instancePublic = new Func<PropertyInfo, bool>(
p => !p.GetMethod.IsStatic && p.GetMethod.IsPublic);
var instancePublic
= new Func<PropertyInfo, bool>(
p => !p.GetMethod.IsStatic
&& (p.GetMethod.IsPublic || includeNonPublic));
return type.IsInterface()
? (new Type[] { type })
.Concat(type.GetInterfaces())
.SelectMany(i => i.GetRuntimeProperties().Where(instancePublic))
: type.GetRuntimeProperties().Where(instancePublic);
? (new Type[] { type })
.Concat(type.GetInterfaces())
.SelectMany(i => i.GetRuntimeProperties().Where(instancePublic))
: type.GetRuntimeProperties().Where(instancePublic);
}

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
{
return GetProperties(type, false);
}

public static IEnumerable<FieldInfo> GetPublicFields(this Type type)
Expand Down Expand Up @@ -225,7 +232,7 @@ public static IEnumerable<MethodInfo> GetPublicStaticMethods(this Type type)
.FirstOrDefault(m => m.IsPublic && !m.IsStatic && m.Name.Equals(name));
}

public static MethodInfo GetGetMethod(this PropertyInfo property)
public static MethodInfo GetGetMethod(this PropertyInfo property, bool _)
{
return property.GetMethod;
}
Expand Down Expand Up @@ -324,12 +331,23 @@ public static TypeCode GetTypeCode(this Type type)

public static IEnumerable<PropertyInfo> GetPublicProperties(this Type type)
{
var instancePublic = BindingFlags.Instance | BindingFlags.Public;
return GetProperties(type, false);
}

public static IEnumerable<PropertyInfo> GetProperties(this Type type, bool includeNonPublic)
{
var bindingFlags = BindingFlags.Instance | BindingFlags.Public;

if (includeNonPublic)
{
bindingFlags |= BindingFlags.NonPublic;
}

return type.IsInterface
? (new Type[] { type })
.Concat(type.GetInterfaces())
.SelectMany(i => i.GetProperties(instancePublic))
: type.GetProperties(instancePublic);
.SelectMany(i => i.GetProperties(bindingFlags))
: type.GetProperties(bindingFlags);
}

public static IEnumerable<FieldInfo> GetPublicFields(this Type type)
Expand Down
2 changes: 1 addition & 1 deletion YamlDotNet/RepresentationModel/YamlMappingNode.cs
Expand Up @@ -412,7 +412,7 @@ public static YamlMappingNode FromObject(object mapping)
foreach (var property in mapping.GetType().GetPublicProperties())
{
// CanRead == true => GetGetMethod() != null
if (property.CanRead && property.GetGetMethod()!.GetParameters().Length == 0)
if (property.CanRead && property.GetGetMethod(false)!.GetParameters().Length == 0)
{
var value = property.GetValue(mapping, null);
if (!(value is YamlNode valueNode))
Expand Down
12 changes: 11 additions & 1 deletion YamlDotNet/Serialization/BuilderSkeleton.cs
Expand Up @@ -40,6 +40,7 @@ public abstract class BuilderSkeleton<TBuilder>
internal readonly LazyComponentRegistrationList<Nothing, IYamlTypeConverter> typeConverterFactories;
internal readonly LazyComponentRegistrationList<ITypeInspector, ITypeInspector> typeInspectorFactories;
private bool ignoreFields;
private bool includeNonPublicProperties = false;

internal BuilderSkeleton(ITypeResolver typeResolver)
{
Expand All @@ -59,7 +60,7 @@ internal BuilderSkeleton(ITypeResolver typeResolver)

internal ITypeInspector BuildTypeInspector()
{
ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver);
ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties);
if (!ignoreFields)
{
innerInspector = new CompositeTypeInspector(
Expand All @@ -81,6 +82,15 @@ public TBuilder IgnoreFields()
return Self;
}

/// <summary>
/// Allows serialization and deserialization of non-public properties.
/// </summary>
public TBuilder IncludeNonPublicProperties()
{
includeNonPublicProperties = true;
return Self;
}

/// <summary>
/// Sets the <see cref="INamingConvention" /> that will be used by the (de)serializer.
/// </summary>
Expand Down
Expand Up @@ -33,22 +33,29 @@ namespace YamlDotNet.Serialization.TypeInspectors
public sealed class ReadablePropertiesTypeInspector : TypeInspectorSkeleton
{
private readonly ITypeResolver typeResolver;
private readonly bool includeNonPublicProperties;

public ReadablePropertiesTypeInspector(ITypeResolver typeResolver)
:this(typeResolver, false)
{
}

public ReadablePropertiesTypeInspector(ITypeResolver typeResolver, bool includeNonPublicProperties)
{
this.typeResolver = typeResolver ?? throw new ArgumentNullException(nameof(typeResolver));
this.includeNonPublicProperties = includeNonPublicProperties;
}

private static bool IsValidProperty(PropertyInfo property)
{
return property.CanRead
&& property.GetGetMethod()!.GetParameters().Length == 0;
&& property.GetGetMethod(true)!.GetParameters().Length == 0;
}

public override IEnumerable<IPropertyDescriptor> GetProperties(Type type, object? container)
{
return type
.GetPublicProperties()
.GetProperties(includeNonPublicProperties)
.Where(IsValidProperty)
.Select(p => (IPropertyDescriptor)new ReflectionPropertyDescriptor(p, typeResolver));
}
Expand Down