PowerShell-Yayaml icon indicating copy to clipboard operation
PowerShell-Yayaml copied to clipboard

Feature request: Add `-AsPSObject` to ConvertFrom-Yaml

Open powercode opened this issue 7 months ago • 4 comments

It would be nice to have an option to generate PSObject instead of OrderedDictionaries when generating objects.

powercode avatar May 08 '25 12:05 powercode

You can actually do that today with a custom schema by transforming the OrderedDictionary value to whatever you want.

$schema = New-YamlSchema -ParseMap {
    param ($Map, $Schema)

    [PSCustomObject]$Map.Values
}

$obj = ConvertFrom-Yaml -InputObject @'
Prop1: 1
Foo: Bar
'@ -Schema $schema 

$obj

# Prop1 Foo
# ----- ---
#     1 Bar

$obj | Get-Member

#    TypeName: System.Management.Automation.PSCustomObject
#
# Name        MemberType   Definition
# ----        ----------   ----------
# Equals      Method       bool Equals(System.Object obj)
# GetHashCode Method       int GetHashCode()
# GetType     Method       type GetType()
# ToString    Method       string ToString()
# Foo         NoteProperty string Foo=Bar
# Prop1       NoteProperty int Prop1=1

You can also do the same with -ParseSequence to emit a list entry as whatever you list and -ParseScalar for simple values. This is all documented under https://github.com/jborean93/PowerShell-Yayaml/blob/main/docs/en-US/about_YamlParsing.md.

In saying that it's certainly easier to add a parameter to the cmdlet which can control the output in an easier way like -AsPSObject or -MapType PSObject but I'm wondering whether it is worth it. What are the reasons why you would want a PSObject over an OrderedDictionary here and do you think it would be worth it to create a helper parameter for this?

jborean93 avatar May 08 '25 19:05 jborean93

I would prefer that it was the default :)

That would make it consistent with ConvertFrom-Json and all the type system features that comes with PSObject would be readily available.

powercode avatar May 08 '25 21:05 powercode

I can potentially see it being convenient for parameter binding by name but I'm unsure what else you really get by default with the ETS that you couldn't get with an OrderedDictionary.

The reason why I didn't go with it by default like ConvertFrom-Json is because

  • Yaml can have more than just strings as a key

A simple example would be '1: value' | ConvertFrom-Yaml but you can even get tricky and have mapping/sequence values as a key:

@'
{some: key}: value
'@ | ConvertFrom-Yaml

# Name                           Value
# ----                           -----
# {[some, key]}                  value

Granted this is a niche case but if the default output was PSObject then the properties would always have to be casted to a string.

  • Properties are case insensitive

A yaml mapping can have keys that differ only by case

@'
Foo: test 1
foo: test 2
'@ | ConvertFrom-Yaml

Another niche case but something that would break if the default was a PSObject.

  • It is quite verbose to check if a property member exists vs a key

Consider these two examples:

$ht = @{Foo = 0}
$ht.ContainsKey('Foo')

$psobj = [PSCustomObject]@{Foo = 0}
[bool]$psobj.PSObject.Properties.Match('Foo')

It's a lot simpler and more intuitive to check if a key in a IDictionary type exists vs a PSObject. You could argument you can just do if ($psobj.Foo) { ... } but that breaks if the value is falsey like 0 in the above example so you need to be careful about that.

  • You can splat an IDictionary value

This is on the flipside to being able to bind by parameter input, with an OrderedDictionary you can splat it to a parameter whereas you cannot do so for a PSObject.

$obj1 = 'Foo: Bar' | ConvertFrom-Yaml
$obj2 = [PSCustomObject]@{Foo = 'Bar'}

Function Test-Function { param($Foo) "Test-Function -Foo $Foo" }

Test-Function @obj1
# Test-Function -Foo Bar

Test-Function @obj2
# Test-Function -Foo @{Foo=Bar}
  • It's easier to go from OrderedDictionary to PSObject then the opposite

If you wanted the PSObject functionality then you can simply just "cast" the value like in my schema example or add on | ForEach-Object { [PSCustomObject]$_ } to the pipeline. Whereas trying to go from a PSObject to a Dictionary means you need to initialise the dictionary, enumerate the properties, then add that to the dictionary.

Ultimately I'll think about it some more and see whether it warrants a new parameter for this vs using the schema or adding | ForEach-Object { [PSCustomObject]$_ } to the snippets that want a PSObject.

jborean93 avatar May 08 '25 22:05 jborean93

I wasn't aware of the yaml intricacies. Json is also in the same position regarding case sensitivity. In practice, it is rarely a problem.

I can create a proxy command that uses your schema solution to add the AsPsObject parameter.

The main difference is in formatting, not when programming with them.

powercode avatar May 08 '25 22:05 powercode