Skip to content

Commit

Permalink
Fix broken json compatibility
Browse files Browse the repository at this point in the history
The IDictionary and PSCustomObject type converters have to emit a MAppingStart
and MappingEnd. When we added these calls, we ommited to properly set the flow
style for these markers, which brije JSON compatibility.

This change adds the proper options to those Emit calls and adds tests to ensure
Flow styles are obeyed.

Signed-off-by: Gabriel Adrian Samfira <[email protected]>
  • Loading branch information
gabriel-samfira committed Dec 16, 2024
1 parent 352d6b0 commit 31abc84
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 10 deletions.
89 changes: 89 additions & 0 deletions Tests/powershell-yaml.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,95 @@ Import-Module $modulePath
InModuleScope $moduleName {
$compareStrictly = Get-EquivalencyOption -Comparator Equality

Describe "Test flow styles" {
Context "Mappings, sequences and PSCustomObjects" {
It "Should serialize Block flow (default) correctly" {
$obj = [ordered]@{
aStringKey = "test"
anIntKey = 1
anArrayKey = @(1, 2, 3)
}
$expected = @"
aStringKey: test
anIntKey: 1
anArrayKey:
- 1
- 2
- 3
"@
$serialized = ConvertTo-Yaml $obj
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized

$pso = [pscustomobject]$obj
$serialized = ConvertTo-Yaml $pso
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized
}

It "Should serialize Flow flow correctly" {
$obj = [ordered]@{
aStringKey = "test"
anIntKey = 1
anArrayKey = @(1, 2, 3)
}
$expected = @"
{aStringKey: test, anIntKey: 1, anArrayKey: [1, 2, 3]}
"@
$serialized = ConvertTo-Yaml -Options UseFlowStyle $obj
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized

$pso = [pscustomobject]$obj
$serialized = ConvertTo-Yaml -Options UseFlowStyle $pso
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized
}

It "Should serialize SequenceFlowStyle correctly" {
$obj = [ordered]@{
aStringKey = "test"
anIntKey = 1
anArrayKey = @(1, 2, 3)
}
$expected = @"
aStringKey: test
anIntKey: 1
anArrayKey: [1, 2, 3]
"@
$serialized = ConvertTo-Yaml -Options UseSequenceFlowStyle $obj
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized

$pso = [pscustomobject]$obj
$serialized = ConvertTo-Yaml -Options UseSequenceFlowStyle $pso
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized
}

It "Should serialize JsonCompatible correctly" {
$obj = [ordered]@{
aStringKey = "test"
anIntKey = 1
anArrayKey = @(1, 2, 3)
}
$expected = @"
{"aStringKey": "test", "anIntKey": 1, "anArrayKey": [1, 2, 3]}
"@
$serialized = ConvertTo-Yaml -Options JsonCompatible $obj
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized

$deserializedWithJSonCommandlet = $serialized | ConvertFrom-Json -AsHashtable
Assert-Equivalent -Options $compareStrictly -Expected $obj -Actual $deserializedWithJSonCommandlet

$pso = [pscustomobject]$obj
$serialized = ConvertTo-Yaml -Options JsonCompatible $pso
Assert-Equivalent -Options $compareStrictly -Expected $expected -Actual $serialized

$deserializedWithJSonCommandlet = $serialized | ConvertFrom-Json -AsHashtable
Assert-Equivalent -Options $compareStrictly -Expected $obj -Actual $deserializedWithJSonCommandlet
}
}
}

Describe "Test PSCustomObject wrapped values are serialized correctly" {
Context "A PSCustomObject containing nested PSCustomObjects" {
It "Should serialize correctly" {
Expand Down
Binary file modified lib/net47/PowerShellYamlSerializer.dll
Binary file not shown.
Binary file modified lib/netstandard2.1/PowerShellYamlSerializer.dll
Binary file not shown.
7 changes: 4 additions & 3 deletions powershell-yaml.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,8 @@ function Get-Serializer {
)

$builder = $yamlDotNetAssembly.GetType("YamlDotNet.Serialization.SerializerBuilder")::new()

$JsonCompatible = $Options.HasFlag([SerializationOptions]::JsonCompatible)

if ($Options.HasFlag([SerializationOptions]::Roundtrip)) {
$builder = $builder.EnsureRoundtrip()
}
Expand All @@ -402,7 +403,7 @@ function Get-Serializer {
if ($Options.HasFlag([SerializationOptions]::EmitDefaults)) {
$builder = $builder.EmitDefaults()
}
if ($Options.HasFlag([SerializationOptions]::JsonCompatible)) {
if ($JsonCompatible) {
$builder = $builder.JsonCompatible()
}
if ($Options.HasFlag([SerializationOptions]::DefaultToStaticType)) {
Expand All @@ -418,7 +419,7 @@ function Get-Serializer {
$useSequenceFlowStyle = $Options.HasFlag([SerializationOptions]::UseSequenceFlowStyle)

$stringQuoted = $stringQuotedAssembly.GetType("BuilderUtils")
$builder = $stringQuoted::BuildSerializer($builder, $omitNull, $useFlowStyle, $useSequenceFlowStyle)
$builder = $stringQuoted::BuildSerializer($builder, $omitNull, $useFlowStyle, $useSequenceFlowStyle, $JsonCompatible)

return $builder.Build()
}
Expand Down
29 changes: 22 additions & 7 deletions src/PowerShellYamlSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.EventEmitters;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization.NamingConventions;
using YamlDotNet.Serialization.ObjectGraphVisitors;

public sealed class NullValueGraphVisitor : ChainedObjectGraphVisitor
Expand Down Expand Up @@ -52,9 +53,11 @@ public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerialize
public class IDictionaryTypeConverter : IYamlTypeConverter {

private bool omitNullValues;
private bool useFlowStyle;

public IDictionaryTypeConverter(bool omitNullValues = false) {
public IDictionaryTypeConverter(bool omitNullValues = false, bool useFlowStyle = false) {
this.omitNullValues = omitNullValues;
this.useFlowStyle = useFlowStyle;
}

public bool Accepts(Type type) {
Expand All @@ -68,7 +71,9 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria

public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) {
var hObj = (IDictionary)value;
emitter.Emit(new MappingStart());
var mappingStyle = this.useFlowStyle ? MappingStyle.Flow : MappingStyle.Block;

emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, true, mappingStyle));
foreach (DictionaryEntry entry in hObj) {
if(entry.Value == null) {
if (this.omitNullValues == true) {
Expand All @@ -92,9 +97,11 @@ public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerialize
public class PSObjectTypeConverter : IYamlTypeConverter {

private bool omitNullValues;
private bool useFlowStyle;

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

public bool Accepts(Type type) {
Expand All @@ -110,7 +117,8 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria

public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) {
var psObj = (PSObject)value;
emitter.Emit(new MappingStart());
var mappingStyle = this.useFlowStyle ? MappingStyle.Flow : MappingStyle.Block;
emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, true, mappingStyle));
foreach (var prop in psObj.Properties) {
if (prop.Value == null) {
if (this.omitNullValues == true) {
Expand Down Expand Up @@ -196,12 +204,19 @@ public static SerializerBuilder BuildSerializer(
SerializerBuilder builder,
bool omitNullValues = false,
bool useFlowStyle = false,
bool useSequenceFlowStyle = false) {
bool useSequenceFlowStyle = false,
bool jsonCompatible = false) {

if (jsonCompatible == true) {
useFlowStyle = true;
useSequenceFlowStyle = true;
}

builder = builder
.WithEventEmitter(next => new StringQuotingEmitter(next))
.WithTypeConverter(new BigIntegerTypeConverter())
.WithTypeConverter(new IDictionaryTypeConverter(omitNullValues))
.WithTypeConverter(new PSObjectTypeConverter(omitNullValues));
.WithTypeConverter(new IDictionaryTypeConverter(omitNullValues, useFlowStyle))
.WithTypeConverter(new PSObjectTypeConverter(omitNullValues, useFlowStyle));
if (omitNullValues == true) {
builder = builder
.WithEmissionPhaseObjectGraphVisitor(args => new NullValueGraphVisitor(args.InnerVisitor));
Expand Down

0 comments on commit 31abc84

Please sign in to comment.