Skip to content

Commit

Permalink
Merge pull request #482 from willson556/feature/privateFields
Browse files Browse the repository at this point in the history
Add support for private properties.
  • Loading branch information
aaubry committed Nov 19, 2020
2 parents d0b687d + a148ad3 commit 5833470
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 15 deletions.
28 changes: 28 additions & 0 deletions YamlDotNet.Test/Serialization/SerializationTestHelper.cs
Expand Up @@ -558,4 +558,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 @@ -1807,6 +1807,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 @@ -343,12 +350,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

0 comments on commit 5833470

Please sign in to comment.