From b542ab93462dc5403b98f2fb49732fa08745758f Mon Sep 17 00:00:00 2001 From: Antoine Aubry Date: Tue, 12 Feb 2019 15:09:51 +0000 Subject: [PATCH] Enable serialization of public fields --- README.md | 7 ++ .../Serialization/SerializationTests.cs | 44 ++++++++++ YamlDotNet.Test/StringExtensions.cs | 5 ++ YamlDotNet.Test/Yaml.cs | 8 +- YamlDotNet.Test/YamlDotNet.Test.csproj | 1 + YamlDotNet/Helpers/Portability.cs | 10 +++ YamlDotNet/Serialization/BuilderSkeleton.cs | 24 ++++- .../TypeInspectors/CompositeTypeInspector.cs | 51 +++++++++++ .../ReadableFieldsTypeInspector.cs | 87 +++++++++++++++++++ 9 files changed, 232 insertions(+), 5 deletions(-) create mode 100644 YamlDotNet/Serialization/TypeInspectors/CompositeTypeInspector.cs create mode 100644 YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs diff --git a/README.md b/README.md index c312364ab..f8d5c3145 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,13 @@ Please read [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. # Changelog +## Version 5.4.0 + +New features: +* **Enable serialization of public fields.** + YamlDotNet will now also serialize public fields. This feature is enabled by default, + but it can be disabled by calling `IgnoreFields()` no the `SerializerBuilder` or `DeserializerBuilder`. + ## Version 5.3.1 New features: diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index aeabc475a..47eddc3d1 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -1645,6 +1645,50 @@ public void InfiniteRecursionIsDetected() var exception = Assert.Throws(() => sut.Serialize(recursionRoot)); } + [Fact] + public void TuplesAreSerializable() + { + var sut = new SerializerBuilder() + .Build(); + + var yaml = sut.Serialize(new[] + { + Tuple.Create(1, "one"), + Tuple.Create(2, "two"), + }); + + var expected = Yaml.Text(@" + - Item1: 1 + Item2: one + - Item1: 2 + Item2: two + "); + + Assert.Equal(expected.NormalizeNewLines(), yaml.NormalizeNewLines().TrimNewLines()); + } + + [Fact] + public void ValueTuplesAreSerializableWithoutMetadata() + { + var sut = new SerializerBuilder() + .Build(); + + var yaml = sut.Serialize(new[] + { + (num: 1, txt: "one"), + (num: 2, txt: "two"), + }); + + var expected = Yaml.Text(@" + - Item1: 1 + Item2: one + - Item1: 2 + Item2: two + "); + + Assert.Equal(expected.NormalizeNewLines(), yaml.NormalizeNewLines().TrimNewLines()); + } + [TypeConverter(typeof(DoublyConvertedTypeConverter))] public class DoublyConverted { diff --git a/YamlDotNet.Test/StringExtensions.cs b/YamlDotNet.Test/StringExtensions.cs index 08be831cf..8f8589110 100644 --- a/YamlDotNet.Test/StringExtensions.cs +++ b/YamlDotNet.Test/StringExtensions.cs @@ -31,5 +31,10 @@ public static string NormalizeNewLines(this string value) .Replace("\r\n", "\n") .Replace("\n", Environment.NewLine); } + + public static string TrimNewLines(this string value) + { + return value.TrimEnd('\r', '\n'); + } } } \ No newline at end of file diff --git a/YamlDotNet.Test/Yaml.cs b/YamlDotNet.Test/Yaml.cs index d8d7f6241..cecae1e1a 100644 --- a/YamlDotNet.Test/Yaml.cs +++ b/YamlDotNet.Test/Yaml.cs @@ -77,6 +77,11 @@ public static Scanner ScannerForText(string yamlText) } public static StringReader ReaderForText(string yamlText) + { + return new StringReader(Text(yamlText)); + } + + public static string Text(string yamlText) { var lines = yamlText .Split('\n') @@ -102,8 +107,7 @@ public static StringReader ReaderForText(string yamlText) .ToList(); } - var reader = new StringReader(string.Join("\n", lines.ToArray())); - return reader; + return string.Join("\n", lines.ToArray()); } } } \ No newline at end of file diff --git a/YamlDotNet.Test/YamlDotNet.Test.csproj b/YamlDotNet.Test/YamlDotNet.Test.csproj index d232a1e85..5a585105e 100644 --- a/YamlDotNet.Test/YamlDotNet.Test.csproj +++ b/YamlDotNet.Test/YamlDotNet.Test.csproj @@ -82,6 +82,7 @@ + diff --git a/YamlDotNet/Helpers/Portability.cs b/YamlDotNet/Helpers/Portability.cs index 1510f7358..bbdfc84f4 100644 --- a/YamlDotNet/Helpers/Portability.cs +++ b/YamlDotNet/Helpers/Portability.cs @@ -193,6 +193,11 @@ public static IEnumerable GetPublicProperties(this Type type) : type.GetRuntimeProperties().Where(instancePublic); } + public static IEnumerable GetPublicFields(this Type type) + { + return type.GetRuntimeFields().Where(f => !f.IsStatic && f.IsPublic); + } + public static IEnumerable GetPublicStaticMethods(this Type type) { return type.GetRuntimeMethods() @@ -327,6 +332,11 @@ public static IEnumerable GetPublicProperties(this Type type) : type.GetProperties(instancePublic); } + public static IEnumerable GetPublicFields(this Type type) + { + return type.GetFields(BindingFlags.Instance | BindingFlags.Public); + } + public static IEnumerable GetPublicStaticMethods(this Type type) { return type.GetMethods(BindingFlags.Static | BindingFlags.Public); diff --git a/YamlDotNet/Serialization/BuilderSkeleton.cs b/YamlDotNet/Serialization/BuilderSkeleton.cs index ff874d81c..cacf06d30 100755 --- a/YamlDotNet/Serialization/BuilderSkeleton.cs +++ b/YamlDotNet/Serialization/BuilderSkeleton.cs @@ -38,6 +38,7 @@ public abstract class BuilderSkeleton internal readonly YamlAttributeOverrides overrides; internal readonly LazyComponentRegistrationList typeConverterFactories; internal readonly LazyComponentRegistrationList typeInspectorFactories; + private bool ignoreFields; internal BuilderSkeleton() { @@ -54,9 +55,26 @@ internal BuilderSkeleton() internal ITypeInspector BuildTypeInspector() { - return typeInspectorFactories.BuildComponentChain( - new ReadablePropertiesTypeInspector(typeResolver) - ); + ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver); + if (!ignoreFields) + { + innerInspector = new CompositeTypeInspector( + new ReadableFieldsTypeInspector(typeResolver), + innerInspector + ); + } + + return typeInspectorFactories.BuildComponentChain(innerInspector); + } + + /// + /// Prevents serialization and deserialization of fields. + /// + /// + public TBuilder IgnoreFields() + { + ignoreFields = true; + return Self; } /// diff --git a/YamlDotNet/Serialization/TypeInspectors/CompositeTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/CompositeTypeInspector.cs new file mode 100644 index 000000000..7ec7fe1e4 --- /dev/null +++ b/YamlDotNet/Serialization/TypeInspectors/CompositeTypeInspector.cs @@ -0,0 +1,51 @@ +// 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; + +namespace YamlDotNet.Serialization.TypeInspectors +{ + /// + /// Aggregates the results from multiple into a single one. + /// + public sealed class CompositeTypeInspector : TypeInspectorSkeleton + { + private readonly IEnumerable typeInspectors; + + public CompositeTypeInspector(params ITypeInspector[] typeInspectors) + : this((IEnumerable)typeInspectors) + { + } + + public CompositeTypeInspector(IEnumerable typeInspectors) + { + this.typeInspectors = typeInspectors?.ToList() ?? throw new ArgumentNullException(nameof(typeInspectors)); + } + + public override IEnumerable GetProperties(Type type, object container) + { + return typeInspectors + .SelectMany(i => i.GetProperties(type, container)); + } + } +} diff --git a/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs new file mode 100644 index 000000000..7ca58ebe4 --- /dev/null +++ b/YamlDotNet/Serialization/TypeInspectors/ReadableFieldsTypeInspector.cs @@ -0,0 +1,87 @@ +// 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 and fields of a type that are readable. + /// + public sealed class ReadableFieldsTypeInspector : TypeInspectorSkeleton + { + private readonly ITypeResolver typeResolver; + + public ReadableFieldsTypeInspector(ITypeResolver typeResolver) + { + this.typeResolver = typeResolver ?? throw new ArgumentNullException("typeResolver"); + } + + public override IEnumerable GetProperties(Type type, object container) + { + return type + .GetPublicFields() + .Select(p => (IPropertyDescriptor)new ReflectionFieldDescriptor(p, typeResolver)); + } + + private sealed class ReflectionFieldDescriptor : IPropertyDescriptor + { + private readonly FieldInfo fieldInfo; + private readonly ITypeResolver typeResolver; + + public ReflectionFieldDescriptor(FieldInfo fieldInfo, ITypeResolver typeResolver) + { + this.fieldInfo = fieldInfo; + this.typeResolver = typeResolver; + ScalarStyle = ScalarStyle.Any; + } + + public string Name { get { return fieldInfo.Name; } } + public Type Type { get { return fieldInfo.FieldType; } } + public Type TypeOverride { get; set; } + public int Order { get; set; } + public bool CanWrite { get { return !fieldInfo.IsInitOnly; } } + public ScalarStyle ScalarStyle { get; set; } + + public void Write(object target, object value) + { + fieldInfo.SetValue(target, value); + } + + public T GetCustomAttribute() where T : Attribute + { + var attributes = fieldInfo.GetCustomAttributes(typeof(T), true); + return (T)attributes.FirstOrDefault(); + } + + public IObjectDescriptor Read(object target) + { + var propertyValue = fieldInfo.GetValue(target); + var actualType = TypeOverride ?? typeResolver.Resolve(Type, propertyValue); + return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle); + } + } + } +}