Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WiP] Implement deserializing to specific type #142

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

gabriel-samfira
Copy link
Member

@gabriel-samfira gabriel-samfira commented Aug 28, 2024

This is a Work in progress. More of a proposal to get feedback. Commandlet names, design of how this is exposed, class names, etc are all subject to change. This is just to showcase an idea, nothing is set in stone.

This change adds a new commandlet called ConvertFrom-YamlToClass and a couple of Attributes that can be used to decorate class properties.

There are just 2 attributes for now:

  • PowerShellYamlPropertyAliasAttribute - This can be used to map a yaml key to a class attribute, in case their names differ.
  • PowerShellYamlSerializable - to mark properties that powershell-yaml should recurse into, to attempt to resolve attribute overrides

This change also adds deserialization for PSCustomObjects and bigint. There are toggles for case insensitive deserialization among others, but currently we're just exploring the idea.

Example usage:

# Define a simple class
class Person {
    [PowerShellYamlPropertyAliasAttribute("last-name")]
    [string]$Surname
    [PowerShellYamlPropertyAliasAttribute("first-name")]
    [string]$Name
}

# Define another class and add a bunc of fields, including one that points to
# the above class
class TestClass {
    [PSCustomObject]$PsO
    [hashtable]$dict
    [PowerShellYamlPropertyAliasAttribute("some-text")]
    [string]$someString
    [System.Numerics.BigInteger]$BigNr
    [PowerShellYamlSerializable($true)]
    [Person]$person
}

$yaml = @"
PsO:
  field1: test
  field2: anotherTest
dict:
  hello: world
some-text: "just a string"
BigNr: 999999999999999999999999999999999999
person:
  last-name: Samfira
  first-name: Gabriel
"@

$deserialized = ConvertFrom-YamlToClass -Yaml $yaml -Type ([TestClass])

And the output is:

PS /> $deserialized = ConvertFrom-YamlToClass -Yaml $yaml -Type ([TestClass])
PS /> $deserialized                                                          

PsO        : @{field1=test; field2=anotherTest}
dict       : {[hello, world]}
someString : just a string
BigNr      : 999999999999999999999999999999999999
person     : Person

PS /> $deserialized.person

Surname Name
------- ----
Samfira Gabriel

PS /> $deserialized.person.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    Person                                   System.Object

PS /> $deserialized.BigNr           
999999999999999999999999999999999999
PS /> $deserialized.BigNr.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     True     BigInteger                               System.ValueType

Things that don't yet work is keeping the original key names when serializing back to yaml/json, but this is something to be tackled if there's interest in this.

Signed-off-by: Gabriel Adrian Samfira <[email protected]>
Signed-off-by: Gabriel Adrian Samfira <[email protected]>
@gabriel-samfira
Copy link
Member Author

CC @iRon7

@iRon7
Copy link

iRon7 commented Aug 28, 2024

My knowledge of Yaml/Json is limited but I have seen something similar with regards to serializing/deserializingclasses for Json.
Personally, I don't see much value in serializing/deserializing classes as is almost impossible to store a full class in a serialized string (thinking of constructors, methods, scripted-properties, accessors, sub-classes, etc).
For me, it would be enough to revive PSCustomObject (which might been seen as a very common class, mainly focused on the properties) or a dictionary, which also could be used to load the properties into a class:

class Person {
    [string]$Surname
    [string]$Name
}
[Person]$deserialized.person

@gabriel-samfira
Copy link
Member Author

In most languages you only concern yourself with attributes when serializing or deserializing. The goal is not to marshal the entire class with all private objects. The goal is to represent a yaml in a well defined structure.

In Go for example this is done with structs and tags:

type Person struct {
    Surname string `json:"last-name"`
    Name string `json:"first-name"`
}

This struct (like powershell classes) can have methods attached, can have more complex types like other structs, etc.

The nice thing about using classes is that you can define the type you want for each field with all the rules that each type comes with. But the above commandlet doesn't need to cast to a class necesarily. If all you need is a PSCustomObject you can just do:

PS /> $yaml = @"                                                  
>> PsO:
>>   field1: test
>>   field2: anotherTest
>> dict:
>>   hello: world
>> some-text: "just a string"
>> BigNr: 999999999999999999999999999999999999
>> person:
>>   last-name: Samfira
>>   first-name: Gabriel
>> "@
PS /> $res = ConvertFrom-YamlToClass -Yaml $yaml -Type ([pscustomobject])
PS /> $res.GetType()                                                     

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    PSCustomObject                           System.Object

PS /> $res

PsO       : {[field1, test], [field2, anotherTest]}
dict      : {[hello, world]}
some-text : just a string
BigNr     : 999999999999999999999999999999999999
person    : {[last-name, Samfira], [first-name, Gabriel]}

PS /> 

@gabriel-samfira gabriel-samfira changed the title [WiP] Implement deserializing to classes [WiP] Implement deserializing to specific type Aug 29, 2024
@MODUSCarstenScholling
Copy link

@gabriel-samfira

I like the idea of having this in general. I needed this kind of stuff for Json serialization in C# multiple times in the past (also **Ignore-attributes). When working with serialization, you often need to tweak types, names and stuff.

However, I personally have not used any class in thousands of PowerShell lines. In fact I thought about using classes in a recent PS project, but I skipped it because it did not work with the standard ConvertTo/From-Json serialization commands.

I tend to use Json over Yaml. Only if there is no other way :-)

@gabriel-samfira
Copy link
Member Author

gabriel-samfira commented Sep 23, 2024

@MODUSCarstenScholling fun fact, you can use powershell-yaml as a replacement for ConvertTo/From-Json 😄 . Json is a subset of yaml and is compatible.

Thanks for the feedback! I appreciate it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants