Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified lib/netstandard2.0/PowerShellYamlSerializer.dll
Binary file not shown.
83 changes: 63 additions & 20 deletions powershell-yaml.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ function Convert-ValueToProperType {
$intTypes = @([int], [long])
if ([string]::IsNullOrEmpty($Node.Tag) -eq $false) {
switch ($Node.Tag) {
'!' {
return $Node.Value
}
'tag:yaml.org,2002:str' {
return $Node.Value
}
Expand Down Expand Up @@ -310,6 +313,20 @@ function Convert-OrderedHashtableToDictionary {
return $Data
}

function Convert-GenericOrderedDictionaryToOrderedDictionary {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[System.Object]$Data
)
# Convert System.Collections.Generic ordered dictionaries to System.Collections.Specialized.OrderedDictionary
# to preserve key order when serializing with YamlDotNet
$ordered = [System.Collections.Specialized.OrderedDictionary]::new()
foreach ($key in $Data.Keys) {
$ordered[$key] = Convert-PSObjectToGenericObject $Data[$key]
}
return $ordered
}

function Convert-ListToGenericList {
param(
[Parameter(Mandatory = $false, ValueFromPipeline = $true)]
Expand All @@ -333,13 +350,38 @@ function Convert-PSObjectToGenericObject {
}

$dataType = $data.GetType()

# Check for OrderedDictionary types first (before generic IDictionary check)
if (([System.Collections.Specialized.OrderedDictionary].IsAssignableFrom($dataType))) {
return Convert-OrderedHashtableToDictionary $data
} elseif (([System.Collections.IDictionary].IsAssignableFrom($dataType))) {
}

# Check for System.Collections.Generic ordered dictionary types
# These need to be converted to OrderedDictionary to preserve key order in YamlDotNet
if ($dataType.IsGenericType) {
$genericDef = $dataType.GetGenericTypeDefinition()
$genericName = $genericDef.FullName

# Handle System.Collections.Generic.OrderedDictionary<K,V>
if ($genericName -eq 'System.Collections.Generic.OrderedDictionary`2') {
return Convert-GenericOrderedDictionaryToOrderedDictionary $data
}

# Handle System.Collections.Generic.SortedDictionary<K,V>
if ($genericName -eq 'System.Collections.Generic.SortedDictionary`2') {
return Convert-GenericOrderedDictionaryToOrderedDictionary $data
}
}

# Generic IDictionary handling (for Hashtable, Dictionary, etc.)
if (([System.Collections.IDictionary].IsAssignableFrom($dataType))) {
return Convert-HashtableToDictionary $data
} elseif (([System.Collections.IList].IsAssignableFrom($dataType))) {
}

if (([System.Collections.IList].IsAssignableFrom($dataType))) {
return Convert-ListToGenericList $data
}

return $data
}

Expand All @@ -357,9 +399,7 @@ function ConvertFrom-Yaml {
$d = ''
}
process {
if ($Yaml -is [string]) {
$d += $Yaml + "`n"
}
$d += $Yaml + "`n"
}

end {
Expand Down Expand Up @@ -435,8 +475,7 @@ function ConvertTo-Yaml {

[Parameter(ParameterSetName = 'NoOptions')]
[switch]$JsonCompatible,
[switch]$UseFlowStyle,


[switch]$KeepArray,

[switch]$Force
Expand All @@ -450,7 +489,7 @@ function ConvertTo-Yaml {
}
}
end {
if ($d -eq $null -or $d.Count -eq 0) {
if ($null -eq $d -or $d.Count -eq 0) {
return
}
if ($d.Count -eq 1 -and !($KeepArray)) {
Expand All @@ -465,11 +504,8 @@ function ConvertTo-Yaml {
if ((Test-Path $OutFile) -and !$Force) {
throw 'Target file already exists. Use -Force to overwrite.'
}
$wrt = New-Object 'System.IO.StreamWriter' $OutFile
} else {
$wrt = New-Object 'System.IO.StringWriter'
}

if ($PSCmdlet.ParameterSetName -eq 'NoOptions') {
$Options = 0
if ($JsonCompatible) {
Expand All @@ -478,18 +514,25 @@ function ConvertTo-Yaml {
}
}

if ($OutFile) {
$wrt = New-Object 'System.IO.StreamWriter' $OutFile
} else {
$wrt = New-Object 'System.IO.StringWriter'
}

try {
$serializer = Get-Serializer $Options
$serializer.Serialize($wrt, $norm)
} catch {
$_

if ($OutFile) {
return
} else {
return $wrt.ToString()
}
} finally {
$wrt.Close()
}
if ($OutFile) {
return
} else {
return $wrt.ToString()
if ($null -ne $wrt) {
$wrt.Dispose()
}
}
}
}
Expand Down
69 changes: 36 additions & 33 deletions src/PowerShellYamlSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ public override bool EnterMapping(IObjectDescriptor key, IObjectDescriptor value
}
}

internal static class PSObjectHelper {
/// <summary>
/// Unwraps a PSObject to its BaseObject if the BaseObject is not a PSCustomObject.
/// </summary>
/// <param name="obj">The object to potentially unwrap</param>
/// <param name="unwrappedType">The type of the unwrapped object</param>
/// <returns>The unwrapped object if it was a PSObject wrapping a non-PSCustomObject, otherwise the original object</returns>
public static object UnwrapIfNeeded(object obj, out Type unwrappedType) {
if (obj is PSObject psObj && psObj.BaseObject != null) {
var baseType = psObj.BaseObject.GetType();
if (baseType != typeof(System.Management.Automation.PSCustomObject)) {
unwrappedType = baseType;
return psObj.BaseObject;
}
}
unwrappedType = obj?.GetType();
return obj;
}
}

public class BigIntegerTypeConverter : IYamlTypeConverter {
public bool Accepts(Type type) {
return typeof(BigInteger).IsAssignableFrom(type);
Expand Down Expand Up @@ -76,26 +96,16 @@ public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerialize
emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, true, mappingStyle));
foreach (DictionaryEntry entry in hObj) {
if(entry.Value == null) {
if (this.omitNullValues == true) {
if (this.omitNullValues) {
continue;
}
serializer(entry.Key, entry.Key.GetType());
emitter.Emit(new Scalar(AnchorName.Empty, "tag:yaml.org,2002:null", "", ScalarStyle.Plain, true, false));
continue;
}
serializer(entry.Key, entry.Key.GetType());
var objType = entry.Value.GetType();
var val = entry.Value;
if (entry.Value is PSObject nestedObj) {
var nestedType = nestedObj.BaseObject.GetType();
if (nestedType != typeof(System.Management.Automation.PSCustomObject)) {
objType = nestedObj.BaseObject.GetType();
val = nestedObj.BaseObject;
}
serializer(val, objType);
} else {
serializer(entry.Value, entry.Value.GetType());
}
var unwrapped = PSObjectHelper.UnwrapIfNeeded(entry.Value, out var unwrappedType);
serializer(unwrapped, unwrappedType);
}
emitter.Emit(new MappingEnd());
}
Expand Down Expand Up @@ -124,32 +134,25 @@ public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeseria

public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) {
var psObj = (PSObject)value;
if (!typeof(IDictionary).IsAssignableFrom(psObj.BaseObject.GetType()) && !typeof(PSCustomObject).IsAssignableFrom(psObj.BaseObject.GetType())) {
if (psObj.BaseObject != null &&
!typeof(IDictionary).IsAssignableFrom(psObj.BaseObject.GetType()) &&
!typeof(PSCustomObject).IsAssignableFrom(psObj.BaseObject.GetType())) {
serializer(psObj.BaseObject, psObj.BaseObject.GetType());
return;
}
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) {
if (this.omitNullValues) {
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());
var objType = prop.Value.GetType();
var val = prop.Value;
if (prop.Value is PSObject nestedPsObj) {
var nestedType = nestedPsObj.BaseObject.GetType();
if (nestedType != typeof(System.Management.Automation.PSCustomObject)) {
objType = nestedPsObj.BaseObject.GetType();
val = nestedPsObj.BaseObject;
}
}
serializer(val, objType);

var unwrapped = PSObjectHelper.UnwrapIfNeeded(prop.Value, out var unwrappedType);
serializer(unwrapped, unwrappedType);
}
}
emitter.Emit(new MappingEnd());
Expand Down Expand Up @@ -197,7 +200,7 @@ public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter) {

public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter){
eventInfo.Style = SequenceStyle.Flow;
nextEmitter.Emit(eventInfo, emitter);
base.Emit(eventInfo, emitter);
}
}

Expand All @@ -206,19 +209,19 @@ public FlowStyleSequenceEmitter(IEventEmitter next): base(next) {}

public override void Emit(SequenceStartEventInfo eventInfo, IEmitter emitter){
eventInfo.Style = SequenceStyle.Flow;
nextEmitter.Emit(eventInfo, emitter);
base.Emit(eventInfo, emitter);
}
}

class BuilderUtils {
public class BuilderUtils {
public static SerializerBuilder BuildSerializer(
SerializerBuilder builder,
bool omitNullValues = false,
bool useFlowStyle = false,
bool useSequenceFlowStyle = false,
bool jsonCompatible = false) {

if (jsonCompatible == true) {
if (jsonCompatible) {
useFlowStyle = true;
useSequenceFlowStyle = true;
}
Expand All @@ -228,14 +231,14 @@ public static SerializerBuilder BuildSerializer(
.WithTypeConverter(new BigIntegerTypeConverter())
.WithTypeConverter(new IDictionaryTypeConverter(omitNullValues, useFlowStyle))
.WithTypeConverter(new PSObjectTypeConverter(omitNullValues, useFlowStyle));
if (omitNullValues == true) {
if (omitNullValues) {
builder = builder
.WithEmissionPhaseObjectGraphVisitor(args => new NullValueGraphVisitor(args.InnerVisitor));
}
if (useFlowStyle == true) {
if (useFlowStyle) {
builder = builder.WithEventEmitter(next => new FlowStyleAllEmitter(next));
}
if (useSequenceFlowStyle == true) {
if (useSequenceFlowStyle) {
builder = builder.WithEventEmitter(next => new FlowStyleSequenceEmitter(next));
}

Expand Down