Skip to content

Commit

Permalink
Merge pull request #608 from premun/DefaultValuesOrEmpty
Browse files Browse the repository at this point in the history
Allow omitting empty collections via `DefaultValuesHandling`
+semver:feature
  • Loading branch information
aaubry authored Jun 12, 2021
2 parents 3bea68e + 173fe44 commit 78a57da
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 15 deletions.
36 changes: 36 additions & 0 deletions YamlDotNet.Test/Serialization/EmitDefaultValuesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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<int> AnEmptyList => new List<int>();
public Dictionary<string, string> AnEmptyDictionary => new Dictionary<string, string>();
public IEnumerable<int> AnEmptyEnumerable => Enumerable.Empty<int>();

public string[] ANonEmptyArray => new[] { "foo", "bar" };
public IList<int> ANonEmptyList => new List<int> { 6, 9, 42 };
public IEnumerable<bool> ANonEmptyEnumerable => new[] { true, false };
public Dictionary<string, string> ANonEmptyDictionary => new Dictionary<string, string>() { { "foo", "bar" } };
}

[Fact]
Expand Down Expand Up @@ -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()
{
Expand Down
18 changes: 13 additions & 5 deletions YamlDotNet/Serialization/DefaultValuesHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/// <summary>
/// Specifies the strategy to handle default and null values during serialization of properties.
/// </summary>
[Flags]
public enum DefaultValuesHandling
{
/// <summary>
/// Specifies that all properties are to be emitted regardless of their value. This is the default behavior.
/// </summary>
Preserve,
Preserve = 0,

/// <summary>
/// Specifies that properties that contain null references or a null Nullable&lt;T&gt; are to be omitted.
/// </summary>
OmitNull = 1,

/// <summary>
/// Specifies that properties that contain null references or a null Nullable&lt;T&gt; 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.
/// </summary>
OmitNull,
OmitDefaults = 2,

/// <summary>
/// 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.
/// </summary>
OmitDefaults,
OmitEmptyCollections = 4,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
// SOFTWARE.

using System;
using System.Collections;
using System.ComponentModel;
using YamlDotNet.Core;

Expand All @@ -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<YamlMemberAttribute>();
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<DefaultValueAttribute>()?.Value ?? GetDefault(key.Type);
if (Equals(value.Value, defaultValue))
if (!canMoveNext)
{
return false;
}
break;
}
}

if ((configuration & DefaultValuesHandling.OmitDefaults) != 0)
{
var defaultValue = key.GetCustomAttribute<DefaultValueAttribute>()?.Value ?? GetDefault(key.Type);
if (Equals(value.Value, defaultValue))
{
return false;
}
}

return base.EnterMapping(key, value, context);
Expand Down

0 comments on commit 78a57da

Please sign in to comment.