diff --git a/YamlDotNet.Test/Serialization/SerializationTests.cs b/YamlDotNet.Test/Serialization/SerializationTests.cs index 470ad185..21ad6e13 100644 --- a/YamlDotNet.Test/Serialization/SerializationTests.cs +++ b/YamlDotNet.Test/Serialization/SerializationTests.cs @@ -1117,17 +1117,27 @@ public void SerializationEmitsPropertyWhenValueDifferFromDefaultValueAttribute() } [Fact] - public void SerializationHandlesThrowingProperties() + public void SerializationHandlesTargetInvocationException() { + var serializer = new SerializerBuilder().WithTargetInvocationExceptionsHandling().Build(); var writer = new StringWriter(); var obj = new ThrowingPropertyExample(); - Serializer.Serialize(writer, obj); + serializer.Serialize(writer, obj); var serialized = writer.ToString(); serialized.Should() .Be("Value: Exception of type System.InvalidOperationException was thrown\r\n".NormalizeNewLines()); } + + [Fact] + public void SerializationDoesntHandleTargetInvocationExceptionByDefault() + { + var writer = new StringWriter(); + var obj = new ThrowingPropertyExample(); + + Assert.Throws(() => Serializer.Serialize(writer, obj)); + } [Fact] public void SerializingAGenericDictionaryShouldNotThrowTargetException() diff --git a/YamlDotNet/Serialization/SerializerBuilder.cs b/YamlDotNet/Serialization/SerializerBuilder.cs index 8b817f7c..2434b139 100755 --- a/YamlDotNet/Serialization/SerializerBuilder.cs +++ b/YamlDotNet/Serialization/SerializerBuilder.cs @@ -62,6 +62,7 @@ public sealed class SerializerBuilder : BuilderSkeleton private ScalarStyle defaultScalarStyle = ScalarStyle.Any; private bool quoteNecessaryStrings; private bool quoteYaml1_1Strings; + private bool handleTargetInvocationExceptions; public SerializerBuilder() : base(new DynamicTypeResolver()) @@ -120,6 +121,15 @@ public SerializerBuilder WithQuotingNecessaryStrings(bool quoteYaml1_1Strings = return this; } + /// + /// Enables handling TargetInvocationExceptions thrown by a property so that information about exception is serialized as string value of the property. + /// + public SerializerBuilder WithTargetInvocationExceptionsHandling() + { + handleTargetInvocationExceptions = true; + return this; + } + /// /// Sets the default quoting style for scalar values. The default value is /// @@ -598,7 +608,7 @@ public IValueSerializer BuildValueSerializer() internal ITypeInspector BuildTypeInspector() { - ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties); + ITypeInspector innerInspector = new ReadablePropertiesTypeInspector(typeResolver, includeNonPublicProperties, handleTargetInvocationExceptions); if (!ignoreFields) { diff --git a/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs b/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs index 49df151c..8a55be80 100644 --- a/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs +++ b/YamlDotNet/Serialization/TypeInspectors/ReadablePropertiesTypeInspector.cs @@ -34,16 +34,21 @@ public sealed class ReadablePropertiesTypeInspector : TypeInspectorSkeleton { private readonly ITypeResolver typeResolver; private readonly bool includeNonPublicProperties; + private readonly bool handleTargetInvocationExceptions; public ReadablePropertiesTypeInspector(ITypeResolver typeResolver) : this(typeResolver, false) { } - public ReadablePropertiesTypeInspector(ITypeResolver typeResolver, bool includeNonPublicProperties) + public ReadablePropertiesTypeInspector( + ITypeResolver typeResolver, + bool includeNonPublicProperties, + bool handleTargetInvocationExceptions = false) { this.typeResolver = typeResolver ?? throw new ArgumentNullException(nameof(typeResolver)); this.includeNonPublicProperties = includeNonPublicProperties; + this.handleTargetInvocationExceptions = handleTargetInvocationExceptions; } private static bool IsValidProperty(PropertyInfo property) @@ -57,18 +62,23 @@ public override IEnumerable GetProperties(Type type, object return type .GetProperties(includeNonPublicProperties) .Where(IsValidProperty) - .Select(p => (IPropertyDescriptor)new ReflectionPropertyDescriptor(p, typeResolver)); + .Select(p => (IPropertyDescriptor)new ReflectionPropertyDescriptor(p, typeResolver, handleTargetInvocationExceptions)); } private sealed class ReflectionPropertyDescriptor : IPropertyDescriptor { private readonly PropertyInfo propertyInfo; private readonly ITypeResolver typeResolver; + private readonly bool handleTargetInvocationExceptions; - public ReflectionPropertyDescriptor(PropertyInfo propertyInfo, ITypeResolver typeResolver) + public ReflectionPropertyDescriptor( + PropertyInfo propertyInfo, + ITypeResolver typeResolver, + bool handleTargetInvocationExceptions) { this.propertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); this.typeResolver = typeResolver ?? throw new ArgumentNullException(nameof(typeResolver)); + this.handleTargetInvocationExceptions = handleTargetInvocationExceptions; ScalarStyle = ScalarStyle.Any; } @@ -93,16 +103,24 @@ public void Write(object target, object? value) public IObjectDescriptor Read(object target) { object? propertyValue; - try + if (handleTargetInvocationExceptions) { - propertyValue = propertyInfo.ReadValue(target); + try + { + propertyValue = propertyInfo.ReadValue(target); + } + catch (TargetInvocationException e) + { + return new ObjectDescriptor( + $"Exception of type {e.InnerException!.GetType().FullName} was thrown", + typeof(string), typeof(string), ScalarStyle.Any); + } } - catch (TargetInvocationException e) + else { - return new ObjectDescriptor( - $"Exception of type {e.InnerException!.GetType().FullName} was thrown", - typeof(string), typeof(string), ScalarStyle.Any); + propertyValue = propertyInfo.ReadValue(target); } + var actualType = TypeOverride ?? typeResolver.Resolve(Type, propertyValue); return new ObjectDescriptor(propertyValue, actualType, Type, ScalarStyle); }