The question Loading a PowerShell hashtable from a file? documents how to load a file that contains a hashtable in PSON format into a variable, but how does one save a hashtable to a file in PSON format?
Hashtable:
@{
"name" = "report 0"
"parameters" = @(
@{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
@{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
)
}
I came to the same question.
To use the ConvertTo-JSON is indeed the most obvious work around but in earlier versions of PowerShell this Cmdlet is not available, besides why should you speak another language (e.g. JSON) if you do not want to interchange any data with another language?
I found out that a ConvertTo-PSON Cmdlet will be nice to store data and also be very helpful in displaying, revealing and exploring the exact information of an object variable, so this is the result:
Function ConvertTo-PSON($Object, [Int]$Depth = 9, [Int]$Layers = 1, [Switch]$Strict, [Version]$Version = $PSVersionTable.PSVersion) {
$Format = $Null
$Quote = If ($Depth -le 0) {""} Else {""""}
$Space = If ($Layers -le 0) {""} Else {" "}
If ($Object -eq $Null) {"`$Null"} Else {
$Type = "[" + $Object.GetType().Name + "]"
$PSON = If ($Object -is "Array") {
$Format = "@(", ",$Space", ")"
If ($Depth -gt 1) {For ($i = 0; $i -lt $Object.Count; $i++) {ConvertTo-PSON $Object[$i] ($Depth - 1) ($Layers - 1) -Strict:$Strict}}
} ElseIf ($Object -is "Xml") {
$Type = "[Xml]"
$String = New-Object System.IO.StringWriter
$Object.Save($String)
$Xml = "'" + ([String]$String).Replace("`'", "'") + "'"
If ($Layers -le 0) {($Xml -Replace "\r\n\s*", "") -Replace "\s+", " "} ElseIf ($Layers -eq 1) {$Xml} Else {$Xml.Replace("`r`n", "`r`n`t")}
$String.Dispose()
} ElseIf ($Object -is "DateTime") {
"$Quote$($Object.ToString('s'))$Quote"
} ElseIf ($Object -is "String") {
0..11 | ForEach {$Object = $Object.Replace([String]"```'""`0`a`b`f`n`r`t`v`$"[$_], ('`' + '`''"0abfnrtv$'[$_]))}; "$Quote$Object$Quote"
} ElseIf ($Object -is "Boolean") {
If ($Object) {"`$True"} Else {"`$False"}
} ElseIf ($Object -is "Char") {
If ($Strict) {[Int]$Object} Else {"$Quote$Object$Quote"}
} ElseIf ($Object -is "ValueType") {
$Object
} ElseIf ($Object.Keys -ne $Null) {
If ($Type -eq "[OrderedDictionary]") {$Type = "[Ordered]"}
$Format = "@{", ";$Space", "}"
If ($Depth -gt 1) {$Object.GetEnumerator() | ForEach {$_.Name + "$Space=$Space" + (ConvertTo-PSON $_.Value ($Depth - 1) ($Layers - 1) -Strict:$Strict)}}
} ElseIf ($Object -is "Object") {
If ($Version -le [Version]"2.0") {$Type = "New-Object PSObject -Property "}
$Format = "@{", ";$Space", "}"
If ($Depth -gt 1) {$Object.PSObject.Properties | ForEach {$_.Name + "$Space=$Space" + (ConvertTo-PSON $_.Value ($Depth - 1) ($Layers - 1) -Strict:$Strict)}}
} Else {$Object}
If ($Format) {
$PSON = $Format[0] + (&{
If (($Layers -le 1) -or ($PSON.Count -le 0)) {
$PSON -Join $Format[1]
} Else {
("`r`n" + ($PSON -Join "$($Format[1])`r`n")).Replace("`r`n", "`r`n`t") + "`r`n"
}
}) + $Format[2]
}
If ($Strict) {"$Type$PSON"} Else {"$PSON"}
}
} Set-Alias PSON ConvertTo-PSON -Description "Convert variable to PSON"
I have nicknamed (aliased) it to simply PSON as a ConvertFrom-PSON cmdlet indeed already exist in the form of: Invoke-Expression:
Set-Alias ConvertFrom-PSON Invoke-Expression -Description "Convert variable from PSON"
It converts most (all?) of the native PowerShell object types which I have listed here in a single test object:
$Object = @{
String = [String]"Text"
Char = [Char]65
Byte = [Byte]66
Int = [Int]67
Long = [Long]68
Null = $Null
Booleans = $False, $True
Decimal = [Decimal]69
Single = [Single]70
Double = [Double]71
DateTime = [DateTime]"Monday, October 7, 1963 9:47:00 PM"
Array = @("One", "Two", @("Three", "Four"), "Five")
HashTable = @{city="New York"; currency="Dollar (`$)"; postalCode=10021; Etc = @("Three", "Four", "Five")}
Ordered = [Ordered]@{One = 1; Two = 2; Three = 3; Four = 4}
Object = New-Object PSObject -Property @{Name = "One"; Value = 1; Text = @("First", "1st")}
}
To convert the object to PSON string you can simply give the command:
PSON $Object
This basic example will converts the object into a PowerShell string similar to the ConvertTo-JSON cmdlet which can be converted back again with the native Invoke-Expression PowerShell cmdlet.
Strict
The ConvertTo-PSON has also a –Strict option which prevents any type casting by explicitly adding the type names this will give a big advantage to the JSON format.
This type casting will usually happen with the object type when to convert them:
Type JSON PSON PSON -Strict
----------------------- -------------- --------- ------------
String String String String
Char String String Char
Byte Int32 Int32 Byte
Int32 (Int) Int32 Int32 Int32
Int64 (Long) Int32 Int32 Int64
Null Null Null Null
Boolean Boolean Boolean Boolean
Decimal Int32 Int32 Decimal
Single Int32 Int32 Single
Double Int32 Int32 Double
DateTime DateTime DateTime DateTime
Object[] (Array) Object[] Object[] Object[]
HashTable PSCustomObject HashTable HashTable
Ordered PSCustomObject HashTable Ordered
PSCustomObject (Object) PSCustomObject HashTable PSCustomObject
XML (TypeName) String XML
To store the object in e.g. a file, registry, database, etc. the best is to use the –Strict option and define 0 layers which will compress the data (remove all spaces):
$PSON1 = PSON -Layers 0 -Strict $Object
Layers
The layers option will define how the layer levels will expanded over separate lines. Each layer level will be get an extra tab indent.
-Layers 0 One line without any spaces (compressed mode)
-Layers 1 One line formatted with spaces (default)
-Layers X Multiple layers are expanded over multiple lines until a depth of X levels (Indents)
Let’s convert the PSON string back and confirm the results:
$Test1 = invoke-expression $PSON1 # ConvertFrom-PSON
$PSON2 = PSON -Layers 4 -Strict $Test1 # ConvertTo-PSON again in a better readable format
Write-Host $PSON2
$Test2 = invoke-expression $PSON2 #Confirm object structure is still intact
This is the result:
[Hashtable]@{
Decimal = [Decimal]69;
Long = [Int64]68;
Char = [Char]65;
Array = [Object[]]@(
[String]"One",
[String]"Two",
[Object[]]@(
[String]"Three",
[String]"Four"
),
[String]"Five"
);
Object = [PSCustomObject]@{
Name = [String]"One";
Value = [Int32]1;
Text = [Object[]]@(
[String]"First",
[String]"1st"
)
};
Int = [Int32]67;
Byte = [Byte]66;
HashTable = [Hashtable]@{
postalCode = [Int32]10021;
currency = [String]"Dollar`t(`$)";
city = [String]"New York";
Etc = [Object[]]@(
[String]"Three",
[String]"Four",
[String]"Five"
)
};
Booleans = [Object[]]@(
[Boolean]$False,
[Boolean]$True
);
Null = $Null;
String = [String]"Text";
Ordered = [Ordered]@{
One = [Int32]1;
Two = [Int32]2;
Three = [Int32]3;
Four = [Int32]4
};
DateTime = [DateTime]"1963-10-07T21:47:00";
Single = [Single]70;
Double = [Double]71
}
Depth
The depth option is similar to the depth option in then ConvertTo-JSON command. By default the depth is limited to 9 levels. A depth of 0 levels will remove the double quotes from strings this might come in handy where you expect a string but would like to be alerted when it is not, e.g.:
Write-Host "User name:" (PSON $UserName 0)
Version
PowerShell 2.0 does not except the [PSCustomObject]
type which will be replace with New-Object PSObject –Property
when ran PowerShell 2.0, if you like to interchange data from a higher version of PowerShell with PowerShell 2.0 you can use the –Version 2.0 option.
Examples
Using the hashtable in the original question from Craig:
$Craig = @{
"name" = "report 0"
"parameters" = @(
@{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
@{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
)
}
Write-Host (PSON $Craig) # Standard
@{name = "report 0"; parameters = @(@{values=@(1,2,3,4);default=1;name="parameter 0"},@{values=@("A","B","C");default="A";name="parameter 1"})}
Write-Host (PSON $Craig -Layers 0) # Compressed
@{name="report 0";parameters=@(@{values=@(1,2,3,4);default=1;name="parameter 0"},@{values=@("A","B","C");default="A";name="parameter 1"})}
Write-Host (PSON $Craig -Layers 3) # 3 Layers
@{
name = "report 0";
parameters = @(
@{values = @(1,2,3,4); default = 1; name = "parameter 0"},
@{values = @("A","B","C"); default = "A"; name = "parameter 1"}
)
}
Write-Host (PSON $Craig -Strict -Layers 9) # Strict, 9 (all) layers
[Hashtable]@{
name = [String]"report 0";
parameters = [Object[]]@(
[Hashtable]@{
values = [Object[]]@(
[Int32]1,
[Int32]2,
[Int32]3,
[Int32]4
);
default = [Int32]1;
name = [String]"parameter 0"
},
[Hashtable]@{
values = [Object[]]@(
[String]"A",
[String]"B",
[String]"C"
);
default = [String]"A";
name = [String]"parameter 1"
}
)
}
For the latest see: ConvertTo-Expression
at the PowerShell Gallery.
Try the *-CliXml
cmdlets. To save the object:
@{
"name" = "report 0"
"parameters" = @(
@{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
@{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
)
} | Export-Clixml -Path c:\hash.xml
To read it back:
Import-Clixml c:\hash.xml
One way would be to put the hashtable definition in a scriptblock:
$hashtable = {
@{
"name" = "report 0"
"parameters" = @(
@{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
@{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
)
}
}
$hashtable.tostring()
@{
"name" = "report 0"
"parameters" = @(
@{"name" = "parameter 0"; "default" = 1; "values"=1,2,3,4},
@{"name" = "parameter 1"; "default" = 'A'; "values" = 'A','B','C'}
)
}
Within the script, you'd need to invoke the script block to instantiate the hashtable:
$hash = .$hashtable