Skip to content

Commit

Permalink
Merge pull request #698 from EdwardCooke/ec-643-setonlyproperties
Browse files Browse the repository at this point in the history
Allow set only properties during deserialization
  • Loading branch information
EdwardCooke authored Jul 14, 2022
2 parents 6e8d6d1 + 7543314 commit 5089ff1
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 9 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ RUN dotnet pack -c Release YamlDotNet/YamlDotNet.csproj -o /output/package /p:Ve

RUN dotnet test -c Release YamlDotNet.sln --framework net60 --logger:"trx;LogFileName=/output/tests.net60.trx" --logger:"console;Verbosity=detailed"
RUN dotnet test -c Release YamlDotNet.Test/YamlDotNet.Test.csproj --framework netcoreapp3.1 --logger:"trx;LogFileName=/output/tests.netcoreapp3.1.trx" --logger:"console;Verbosity=detailed"
RUN dotnet test -c Release YamlDotNet.Test/YamlDotNet.Test.csproj --framework net47 --logger:"trx;LogFileName=/output/tests.net47.trx" --logger:"console;Verbosity=detailed"

FROM alpine
VOLUME /output
Expand Down
18 changes: 18 additions & 0 deletions YamlDotNet.Test/Serialization/DeserializerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,24 @@ public void Deserialize_YamlWithTwoInterfaceTypesAndMappings_ReturnsModel()
person.Cars[1].Spec.DriveType.Should().Be("FWD");
}

[Fact]
public void SetterOnlySetsWithoutException()
{
var yaml = @"
Value: bar
";
var deserializer = new DeserializerBuilder().Build();
var result = deserializer.Deserialize<SetterOnly>(yaml);
result.Actual.Should().Be("bar");
}

public class SetterOnly
{
private string _value;
public string Value { set => _value = value; }
public string Actual { get => _value; }
}

public class Person
{
public string Name { get; private set; }
Expand Down
8 changes: 5 additions & 3 deletions YamlDotNet.Test/Serialization/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1388,17 +1388,19 @@ public static IEnumerable<object[]> SpecialFloats
new FloatTestCase("double.PositiveInfinity", double.PositiveInfinity, ".inf"),
new FloatTestCase("double.NegativeInfinity", double.NegativeInfinity, "-.inf"),
new FloatTestCase("double.Epsilon", double.Epsilon, double.Epsilon.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("double.MinValue", double.MinValue, double.MinValue.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("double.MaxValue", double.MaxValue, double.MaxValue.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("double.26.67", 26.67D, "26.67"),

new FloatTestCase("float.NaN", float.NaN, ".nan"),
new FloatTestCase("float.PositiveInfinity", float.PositiveInfinity, ".inf"),
new FloatTestCase("float.NegativeInfinity", float.NegativeInfinity, "-.inf"),
new FloatTestCase("float.Epsilon", float.Epsilon, float.Epsilon.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("float.26.67", 26.67F, "26.67"),
#if NETCOREAPP3_1_OR_GREATER
new FloatTestCase("double.MinValue", double.MinValue, double.MinValue.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("double.MaxValue", double.MaxValue, double.MaxValue.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("float.MinValue", float.MinValue, float.MinValue.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("float.MaxValue", float.MaxValue, float.MaxValue.ToString("G", CultureInfo.InvariantCulture)),
new FloatTestCase("float.26.67", 26.67F, "26.67")
#endif
}
.Select(tc => new object[] { tc });
}
Expand Down
2 changes: 1 addition & 1 deletion YamlDotNet.Test/YamlDotNet.Test.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net60;netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net60;net47;netcoreapp3.1</TargetFrameworks>
<IsPackable>false</IsPackable>
<AssemblyOriginatorKeyFile>..\YamlDotNet.snk</AssemblyOriginatorKeyFile>
<SignAssembly>true</SignAssembly>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ public static IEnumerable<ConstructorInfo> GetConstructors(this Type type)
.Where(c => c.IsPublic && !c.IsStatic);
}

private static readonly Func<PropertyInfo, bool> IsInstance = (PropertyInfo property) => !property.GetMethod.IsStatic;
private static readonly Func<PropertyInfo, bool> IsInstancePublic = (PropertyInfo property) => IsInstance(property) && property.GetMethod.IsPublic;
private static readonly Func<PropertyInfo, bool> IsInstance = (PropertyInfo property) => !(property.GetMethod ?? property.SetMethod).IsStatic;
private static readonly Func<PropertyInfo, bool> IsInstancePublic = (PropertyInfo property) => IsInstance(property) && (property.GetMethod ?? property.SetMethod).IsPublic;

public static IEnumerable<PropertyInfo> GetProperties(this Type type, bool includeNonPublic)
{
Expand Down
6 changes: 3 additions & 3 deletions YamlDotNet/Serialization/BuilderSkeleton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ public abstract class BuilderSkeleton<TBuilder>
internal readonly YamlAttributeOverrides overrides;
internal readonly LazyComponentRegistrationList<Nothing, IYamlTypeConverter> typeConverterFactories;
internal readonly LazyComponentRegistrationList<ITypeInspector, ITypeInspector> typeInspectorFactories;
private bool ignoreFields;
private bool includeNonPublicProperties = false;
internal bool ignoreFields;
internal bool includeNonPublicProperties = false;

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

protected abstract TBuilder Self { get; }

internal ITypeInspector BuildTypeInspector()
internal virtual ITypeInspector BuildTypeInspector()
{
ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties);
if (!ignoreFields)
Expand Down
14 changes: 14 additions & 0 deletions YamlDotNet/Serialization/DeserializerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ public DeserializerBuilder()

protected override DeserializerBuilder Self { get { return this; } }

internal override ITypeInspector BuildTypeInspector()
{
ITypeInspector innerInspector = new WritablePropertiesTypeInspector(typeResolver, includeNonPublicProperties);
if (!ignoreFields)
{
innerInspector = new CompositeTypeInspector(
new ReadableFieldsTypeInspector(typeResolver),
innerInspector
);
}

return typeInspectorFactories.BuildComponentChain(innerInspector);
}

/// <summary>
/// Sets the <see cref="IObjectFactory" /> that will be used by the deserializer.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// 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 System.Reflection;
using YamlDotNet.Core;

namespace YamlDotNet.Serialization.TypeInspectors
{
/// <summary>
/// Returns the properties of a type that are writable.
/// </summary>
public sealed class WritablePropertiesTypeInspector : TypeInspectorSkeleton
{
private readonly ITypeResolver typeResolver;
private readonly bool includeNonPublicProperties;

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

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

private static bool IsValidProperty(PropertyInfo property)
{
return property.CanWrite
&& property.GetSetMethod(true)!.GetParameters().Length == 1;
}

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

private sealed class ReflectionPropertyDescriptor : IPropertyDescriptor
{
private readonly PropertyInfo propertyInfo;
private readonly ITypeResolver typeResolver;

public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typeResolver)
{
this.propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo));
this.typeResolver = typeResolver ?? throw new ArgumentNullException(nameof(typeResolver));
ScalarStyle = ScalarStyle.Any;
}

public string Name => propertyInfo.Name;
public Type Type => propertyInfo.PropertyType;
public Type? TypeOverride { get; set; }
public int Order { get; set; }
public bool CanWrite => propertyInfo.CanWrite;
public ScalarStyle ScalarStyle { get; set; }

public void Write(object target, object? value)
{
propertyInfo.SetValue(target, value, null);
}

public T GetCustomAttribute<T>() where T : Attribute
{
var attributes = propertyInfo.GetAllCustomAttributes<T>();
return (T)attributes.FirstOrDefault();
}

public IObjectDescriptor Read(object target)
{
var propertyValue = propertyInfo.ReadValue(target);
var actualType = TypeOverride ?? typeResolver.Resolve(Type, propertyValue);
return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle);
}
}
}
}

0 comments on commit 5089ff1

Please sign in to comment.