diff --git a/YamlDotNet.Test/Serialization/EmitDefaultValuesTests.cs b/YamlDotNet.Test/Serialization/EmitDefaultValuesTests.cs index 72bf7a7a..a74cad3d 100644 --- a/YamlDotNet.Test/Serialization/EmitDefaultValuesTests.cs +++ b/YamlDotNet.Test/Serialization/EmitDefaultValuesTests.cs @@ -19,7 +19,9 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using Xunit; using YamlDotNet.Serialization; @@ -44,6 +46,17 @@ private class Model public int? ANullableNonZeroInteger => 1; [DefaultValue(2)] public int? ANullableNonZeroDefaultInteger => 2; [DefaultValue(2)] public int? ANullableNonZeroNonDefaultInteger => 1; + + // Enumerables + public int[] AnEmptyArray => new int[0]; + public IList AnEmptyList => new List(); + public Dictionary AnEmptyDictionary => new Dictionary(); + public IEnumerable AnEmptyEnumerable => Enumerable.Empty(); + + public string[] ANonEmptyArray => new[] { "foo", "bar" }; + public IList ANonEmptyList => new List { 6, 9, 42 }; + public IEnumerable ANonEmptyEnumerable => new[] { true, false }; + public Dictionary ANonEmptyDictionary => new Dictionary() { { "foo", "bar" } }; } [Fact] @@ -132,6 +145,29 @@ public void All_default_values_are_omitted_when_DefaultValuesHandling_is_OmitAll Assert.Contains(nameof(Model.ANullableNonZeroNonDefaultInteger) + ':', yaml); } + [Fact] + public void Empty_enumerables_are_omitted_when_DefaultValuesHandling_is_OmitEmpty() + { + // Arrange + var sut = new SerializerBuilder() + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitEmptyCollections) + .Build(); + + // Act + var yaml = sut.Serialize(new Model()); + + // Assert enumerables + Assert.DoesNotContain(nameof(Model.AnEmptyArray) + ':', yaml); + Assert.DoesNotContain(nameof(Model.AnEmptyList) + ':', yaml); + Assert.DoesNotContain(nameof(Model.AnEmptyDictionary) + ':', yaml); + Assert.DoesNotContain(nameof(Model.AnEmptyEnumerable) + ':', yaml); + + Assert.Contains(nameof(Model.ANonEmptyArray) + ':', yaml); + Assert.Contains(nameof(Model.ANonEmptyList) + ':', yaml); + Assert.Contains(nameof(Model.ANonEmptyDictionary) + ':', yaml); + Assert.Contains(nameof(Model.ANonEmptyEnumerable) + ':', yaml); + } + [Fact] public void YamlMember_overrides_default_value_handling() { diff --git a/YamlDotNet/Serialization/DefaultValuesHandling.cs b/YamlDotNet/Serialization/DefaultValuesHandling.cs index 6af1c1fe..176a45bd 100644 --- a/YamlDotNet/Serialization/DefaultValuesHandling.cs +++ b/YamlDotNet/Serialization/DefaultValuesHandling.cs @@ -19,26 +19,34 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +using System; + namespace YamlDotNet.Serialization { /// /// Specifies the strategy to handle default and null values during serialization of properties. /// + [Flags] public enum DefaultValuesHandling { /// /// Specifies that all properties are to be emitted regardless of their value. This is the default behavior. /// - Preserve, + Preserve = 0, + + /// + /// Specifies that properties that contain null references or a null Nullable<T> are to be omitted. + /// + OmitNull = 1, /// - /// Specifies that properties that contain null references or a null Nullable<T> are to be omitted. + /// Specifies that properties that that contain their default value, either default(T) or the value specified in DefaultValueAttribute are to be omitted. /// - OmitNull, + OmitDefaults = 2, /// - /// Specifies that properties that that contain their default value, either default(T) or the value specified in DefaultValueAttribute are to be omitted. + /// Specifies that properties that that contain collections/arrays/enumerations that are empty are to be omitted. /// - OmitDefaults, + OmitEmptyCollections = 4, } } diff --git a/YamlDotNet/Serialization/ObjectGraphVisitors/DefaultValuesObjectGraphVisitor.cs b/YamlDotNet/Serialization/ObjectGraphVisitors/DefaultValuesObjectGraphVisitor.cs index b2c2628b..7e478fa2 100644 --- a/YamlDotNet/Serialization/ObjectGraphVisitors/DefaultValuesObjectGraphVisitor.cs +++ b/YamlDotNet/Serialization/ObjectGraphVisitors/DefaultValuesObjectGraphVisitor.cs @@ -20,6 +20,7 @@ // SOFTWARE. using System; +using System.Collections; using System.ComponentModel; using YamlDotNet.Core; @@ -42,29 +43,46 @@ public DefaultValuesObjectGraphVisitor(DefaultValuesHandling handling, IObjectGr public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context) { - var configuration = this.handling; + var configuration = handling; var yamlMember = key.GetCustomAttribute(); if (yamlMember != null && yamlMember.IsDefaultValuesHandlingSpecified) { configuration = yamlMember.DefaultValuesHandling; } - switch (configuration) + if ((configuration & DefaultValuesHandling.OmitNull) != 0) { - case DefaultValuesHandling.OmitNull: - if (value.Value is null) + if (value.Value is null) + { + return false; + } + } + + if ((configuration & DefaultValuesHandling.OmitEmptyCollections) != 0) + { + if (value.Value is IEnumerable enumerable) + { + var enumerator = enumerable.GetEnumerator(); + var canMoveNext = enumerator.MoveNext(); + if (enumerator is IDisposable disposable) { - return false; + disposable.Dispose(); } - break; - case DefaultValuesHandling.OmitDefaults: - var defaultValue = key.GetCustomAttribute()?.Value ?? GetDefault(key.Type); - if (Equals(value.Value, defaultValue)) + if (!canMoveNext) { return false; } - break; + } + } + + if ((configuration & DefaultValuesHandling.OmitDefaults) != 0) + { + var defaultValue = key.GetCustomAttribute()?.Value ?? GetDefault(key.Type); + if (Equals(value.Value, defaultValue)) + { + return false; + } } return base.EnterMapping(key, value, context);