Skip to content

Commit

Permalink
Merge pull request #137 from gabriel-samfira/omit-null-values
Browse files Browse the repository at this point in the history
Add graph visitor that ignores null values
  • Loading branch information
gabriel-samfira authored Aug 26, 2024
2 parents 961aa95 + d7a60ee commit 0d5b89f
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 8 deletions.
33 changes: 33 additions & 0 deletions Tests/powershell-yaml.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ iAmEmptyString: ""
"@
}

It "should not serialize null value when -Options OmitNullValues is set" {
$toYaml = ConvertTo-Yaml $nullAndString -Options OmitNullValues
$toYaml | Should -Be "iAmEmptyString: """"$([Environment]::NewLine)"
}

It "should preserve nulls and empty strings from PowerShell" {
$toYaml = ConvertTo-Yaml $nullAndString
$backFromYaml = ConvertFrom-Yaml $toYaml
Expand Down Expand Up @@ -552,6 +557,7 @@ int64: 9223372036854775807
$PsO = [PSCustomObject]@{
Name = 'Value'
Nested = $nestedPsO
NullValue = $null
}

class TestClass {
Expand All @@ -569,11 +575,38 @@ int64: 9223372036854775807
$ret["PsO"]["Name"] = "Value"
$ret["PsO"]["Nested"] = [System.Collections.Specialized.OrderedDictionary]::new()
$ret["PsO"]["Nested"]["Nested"] = "NestedValue"
$ret["PsO"]["NullValue"] = $null
$ret["Ok"] = "aye"
Assert-Equivalent -Options $compareStrictly -Expected $ret -Actual $result
}
}

Context 'PSObject with null value is skipped when -Options OmitNullValues' {
BeforeAll {
$global:value = [PSCustomObject]@{
key1 = "value1"
key2 = $null
}
}
It 'Should serialise as a hash with only the non-null value' {
$result = ConvertTo-Yaml $value -Options OmitNullValues
$result | Should -Be "key1: value1$([Environment]::NewLine)"
}
}

Context 'PSObject with null value is included when -Options OmitNullValues is not set' {
BeforeAll {
$global:value = [PSCustomObject]@{
key1 = "value1"
key2 = $null
}
}
It 'Should serialise as a hash with the null value' {
$result = ConvertTo-Yaml $value
$result | Should -Be "key1: value1$([Environment]::NewLine)key2: $null$([Environment]::NewLine)"
}
}

Context 'PSCustomObject with a single property' {
BeforeAll {
$global:value = [PSCustomObject]@{key="value"}
Expand Down
Binary file modified lib/net47/StringQuotingEmitter.dll
Binary file not shown.
Binary file modified lib/netstandard2.1/StringQuotingEmitter.dll
Binary file not shown.
7 changes: 6 additions & 1 deletion powershell-yaml.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum SerializationOptions {
JsonCompatible = 8
DefaultToStaticType = 16
WithIndentedSequences = 32
OmitNullValues = 64
}
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$infinityRegex = [regex]::new('^[-+]?(\.inf|\.Inf|\.INF)$', "Compiled, CultureInvariant");
Expand Down Expand Up @@ -408,8 +409,12 @@ function Get-Serializer {
if ($Options.HasFlag([SerializationOptions]::WithIndentedSequences)) {
$builder = $builder.WithIndentedSequences()
}

$omitNull = $Options.HasFlag([SerializationOptions]::OmitNullValues)

$stringQuoted = $stringQuotedAssembly.GetType("StringQuotingEmitter")
$builder = $stringQuoted::Add($builder)
$builder = $stringQuoted::Add($builder, $omitNull)

return $builder.Build()
}

Expand Down
56 changes: 49 additions & 7 deletions src/serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,29 @@
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization.ObjectGraphVisitors;

public sealed class NullValueGraphVisitor : ChainedObjectGraphVisitor
{
public NullValueGraphVisitor(IObjectGraphVisitor<IEmitter> nextVisitor)
: base(nextVisitor)
{
}

public override bool EnterMapping(IPropertyDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) {
if (value.Value == null) {
return false;
}
return base.EnterMapping(key, value, context, serializer);
}

public override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value, IEmitter context, ObjectSerializer serializer) {
if (value.Value == null) {
return false;
}
return base.EnterMapping(key, value, context, serializer);
}
}

public class BigIntegerTypeConverter : IYamlTypeConverter {
public bool Accepts(Type type) {
Expand All @@ -28,6 +50,13 @@ public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerialize
}

public class PSObjectTypeConverter : IYamlTypeConverter {

private bool omitNullValues;

public PSObjectTypeConverter(bool omitNullValues = false) {
this.omitNullValues = omitNullValues;
}

public bool Accepts(Type type) {
return typeof(PSObject).IsAssignableFrom(type);
}
Expand All @@ -39,13 +68,21 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria
return deserializedObject;
}

public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) {
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) {
var psObj = (PSObject)value;

emitter.Emit(new MappingStart());
foreach (var prop in psObj.Properties) {
serializer(prop.Name, prop.Name.GetType());
serializer(prop.Value, prop.Value.GetType());
if (prop.Value == null) {
if (this.omitNullValues == true) {
continue;
}
serializer(prop.Name, prop.Name.GetType());
emitter.Emit(new Scalar(AnchorName.Empty, "tag:yaml.org,2002:null", "", ScalarStyle.Plain, true, false));
} else {
serializer(prop.Name, prop.Name.GetType());
serializer(prop.Value, prop.Value.GetType());
}
}
emitter.Emit(new MappingEnd());
}
Expand Down Expand Up @@ -81,10 +118,15 @@ public override void Emit(ScalarEventInfo eventInfo, IEmitter emitter) {
base.Emit(eventInfo, emitter);
}
// objectGraphVisitor, w => w.OnTop()
public static SerializerBuilder Add(SerializerBuilder builder) {
return builder
public static SerializerBuilder Add(SerializerBuilder builder, bool omitNullValues = false) {
builder = builder
.WithEventEmitter(next => new StringQuotingEmitter(next))
.WithTypeConverter(new BigIntegerTypeConverter())
.WithTypeConverter(new PSObjectTypeConverter());
.WithTypeConverter(new PSObjectTypeConverter(omitNullValues));
if (omitNullValues == true) {
builder = builder
.WithEmissionPhaseObjectGraphVisitor(args => new NullValueGraphVisitor(args.InnerVisitor));
}
return builder;
}
}
}

0 comments on commit 0d5b89f

Please sign in to comment.