diff --git a/Dockerfile b/Dockerfile index 6c17c3bc9..2fae2c897 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/YamlDotNet.Test/Serialization/DeserializerTest.cs b/YamlDotNet.Test/Serialization/DeserializerTest.cs index 03f6ae982..f2165be16 100644 --- a/YamlDotNet.Test/Serialization/DeserializerTest.cs +++ b/YamlDotNet.Test/Serialization/DeserializerTest.cs @@ -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(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; } diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index dc4686780..13d7cc11e 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -1388,17 +1388,19 @@ public static IEnumerable 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 }); } diff --git a/YamlDotNet.Test/YamlDotNet.Test.csproj b/YamlDotNet.Test/YamlDotNet.Test.csproj index 50c40ff3b..e29e17319 100644 --- a/YamlDotNet.Test/YamlDotNet.Test.csproj +++ b/YamlDotNet.Test/YamlDotNet.Test.csproj @@ -1,6 +1,6 @@  - net60;netcoreapp3.1 + net60;net47;netcoreapp3.1 false ..\YamlDotNet.snk true diff --git a/YamlDotNet/Portability/netstandard2.0+netstandard2.1/include/ReflectionExtensions.cs b/YamlDotNet/Portability/netstandard2.0+netstandard2.1/include/ReflectionExtensions.cs index c9920cbcd..2c6ea7c4b 100644 --- a/YamlDotNet/Portability/netstandard2.0+netstandard2.1/include/ReflectionExtensions.cs +++ b/YamlDotNet/Portability/netstandard2.0+netstandard2.1/include/ReflectionExtensions.cs @@ -182,8 +182,8 @@ public static IEnumerable GetConstructors(this Type type) .Where(c => c.IsPublic && !c.IsStatic); } - private static readonly Func IsInstance = (PropertyInfo property) => !property.GetMethod.IsStatic; - private static readonly Func IsInstancePublic = (PropertyInfo property) => IsInstance(property) && property.GetMethod.IsPublic; + private static readonly Func IsInstance = (PropertyInfo property) => !(property.GetMethod ?? property.SetMethod).IsStatic; + private static readonly Func IsInstancePublic = (PropertyInfo property) => IsInstance(property) && (property.GetMethod ?? property.SetMethod).IsPublic; public static IEnumerable GetProperties(this Type type, bool includeNonPublic) { diff --git a/YamlDotNet/Serialization/BuilderSkeleton.cs b/YamlDotNet/Serialization/BuilderSkeleton.cs index 137806af4..b4d68f917 100755 --- a/YamlDotNet/Serialization/BuilderSkeleton.cs +++ b/YamlDotNet/Serialization/BuilderSkeleton.cs @@ -39,8 +39,8 @@ public abstract class BuilderSkeleton internal readonly YamlAttributeOverrides overrides; internal readonly LazyComponentRegistrationList typeConverterFactories; internal readonly LazyComponentRegistrationList typeInspectorFactories; - private bool ignoreFields; - private bool includeNonPublicProperties = false; + internal bool ignoreFields; + internal bool includeNonPublicProperties = false; internal BuilderSkeleton(ITypeResolver typeResolver) { @@ -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) diff --git a/YamlDotNet/Serialization/DeserializerBuilder.cs b/YamlDotNet/Serialization/DeserializerBuilder.cs index 3ad041de6..1d996184d 100755 --- a/YamlDotNet/Serialization/DeserializerBuilder.cs +++ b/YamlDotNet/Serialization/DeserializerBuilder.cs @@ -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); + } + /// /// Sets the that will be used by the deserializer. /// diff --git a/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs new file mode 100644 index 000000000..ebb8967b4 --- /dev/null +++ b/YamlDotNet/Serialization/TypeInspectors/WritablePropertiesTypeInspector.cs @@ -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 +{ + /// + /// Returns the properties of a type that are writable. + /// + 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 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() where T : Attribute + { + var attributes = propertyInfo.GetAllCustomAttributes(); + 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); + } + } + } +}