10

使用 Json.NET 序列化 PowerShell PSCustomObject 物件

 3 years ago
source link: https://blog.darkthread.net/blog/jsonnet-serialize-pscustomobject/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
使用 Json.NET 序列化 PowerShell PSCustomObject 物件-黑暗執行緒

遇到特殊需求,PowerShell 產生 JSON 時需將中文字元轉成 UNC (Unicode Character Name,例如 "\u9ED1\u6697\u57F7\u884C\u7DD2"),之前處理過 ASP.NET Core JSON 中文編碼問題,大致有概念,用 Newtonsoft Json.NET 指定 JsonSerializerSettings.StringEscapeHandling = StringEscapeHandling.EscapeNonAscii 應可搞定。

不過,序列化對象是自訂 PSCustomObject 陣列,用 ConvertTo-Json 能正常轉換,改成 Newtonsoft.Json.Convert.SerializeObject() 卻變成奇怪的 XML。

講序列化問題前,先幫不熟 PSCustomObject 的同學做個補充。在 PowerShell 裡,用 @{ Prop1 = ...; Prop2 = ... } 就能建立自訂物件並動態指定屬性,例如:

$custObj = @{
    Prop1 = "One"
    Prop2 = "Two"
}
$custObj.Prop3 = "Three"
$custObj | ConvertTo-Json
$custObj | ConvertTo-Csv

但有兩個問題,第一是 ConvertTo-Json 可順利轉成 JSON,但卻沒有依循屬性宣告順序,原因是 $custObj 骨子裡是 Hashtable,這衍生第二個問題,ConvertTo-Csv 或 Format-Table 時,不會以 Prop1、Prop2、Prop3 屬性欄位方式呈現。

改用 PSCustomObject 方式,除了加屬性需改用 Add-Member 麻煩些,其餘行為更符合我們對自訂物件的期待。

$custObj = [PSCustomObject]@{
    Prop1 = "One"
    Prop2 = "Two"
}
$custObj | Add-Member -MemberType NoteProperty -Name "Prop3" -Value "Three"
$custObj | ConvertTo-Json
$custObj | ConvertTo-Csv

下面範例展示 Hashtable、PSCustomObject 在 Format-Table、Where-Object、Select-Object 處理上的異同:

$rnd = New-Object System.Random(123)
$players = 1..10 | ForEach-Object {
    # Hashtable
    @{
        Name = "Player$_"
        Score = $rnd.Next(256)
    }
}
Write-Host 'Hashtable 陣列接 Format-Table' -ForegroundColor Yellow
$players | Format-Table
Write-Host '篩選 Score > 127 並輸出 Name 字串陣列' -ForegroundColor Yellow
$players | Where-Object { $_.Score -gt 127 } | ForEach-Object { $_.Name }

$rnd = New-Object System.Random(123)
$players = 1..10 | ForEach-Object {
    [PSCustomObject]@{
        Name = "Player$_"
        Score = $rnd.Next(256)
    }
}
Write-Host 'PSCustomObject 陣列接 Format-Table' -ForegroundColor Yellow
$players | Format-Table
$greaterThan127 = $players | Where-Object { $_.Score -gt 127 } 
Write-Host '篩選 Score > 127 並輸出含 Name 屬性的物件陣列' -ForegroundColor Yellow
$greaterThan127 | Select-Object Name | Format-Table
Write-Host '篩選 Score > 127 並輸出 Name 字串陣列' -ForegroundColor Yellow
$greaterThan127 | Select-Object -ExpandProperty Name

MS Docs 有篇您想知道有關 PSCustomObject 的一切,名符其實,整理十分完整,值得一讀。

回到這次的問題上,PSCustomObject 物件陣列用 ConvertTo-Json 轉換很 OK,為了將中文字元顯示為 UCN 改用 [Newtonsoft.Json.JsonConvert]::SerializeObject() 卻得到奇怪的 CliXml XML 內容。

Add-Type -Path "$PSScriptRoot\Newtonsoft.Json.dll"

$list = @()
$list += [PSCustomObject]@{
    Id   = 'A001'
    Name = 'Jeffrey'
}
$list += [PSCustomObject]@{
    Id   = 'A002'
    Name = '黑暗執行緒'
}

$list | ConvertTo-Json

function ConvertToJsonByJsonNet($object) {

    $jsonSettings = New-Object Newtonsoft.Json.JsonSerializerSettings
    $jsonSettings.StringEscapeHandling = [Newtonsoft.Json.StringEscapeHandling]::EscapeNonAscii
    $jsonSettings.Formatting = [Newtonsoft.Json.Formatting]::Indented
    [Newtonsoft.Json.JsonConvert]::SerializeObject($object, $jsonSettings)
}

ConvertToJsonByJsonNet($list)

研究了一下,所謂 Clixml 是 PowerShell 物件內部採用的 XML 物件表示格式,PowerShell 還有提供 Export-ClixmlImport-Clixml 等匯出匯入工具。而從 .NET 角度存取 PSCustomObject,看到的就只有 Clixml 屬性。

我找到幾種解法:

  1. 將 PSCustomObject 轉成 Hashtable 參考
  2. 使用 ConvertTo-NewtonsoftJson.ps1 等現成函式 參考
  3. 先 ConvertTo-Json 轉成 JSON 再用 JSON.NET 轉成一般物件:
     $json = $list | ConvertTo-Json
     $fixed = [Newtonsoft.Json.JsonConvert]::DeserializeObject($json)
     ConvertToJsonByJsonNet($fixed)
    

用 ConvertTo-Json 轉 JSON 再 [Newtonsoft.Json.JsonConvert]::DeserializeObject() 感覺最簡便,最後用它搞定。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK