使用 Json.NET 序列化 PowerShell PSCustomObject 物件
| | 0 | | ![]() |
遇到特殊需求,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-Clixml、Import-Clixml 等匯出匯入工具。而從 .NET 角度存取 PSCustomObject,看到的就只有 Clixml 屬性。
我找到幾種解法:
- 將 PSCustomObject 轉成 Hashtable 參考
- 使用 ConvertTo-NewtonsoftJson.ps1 等現成函式 參考
- 先 ConvertTo-Json 轉成 JSON 再用 JSON.NET 轉成一般物件:
$json = $list | ConvertTo-Json $fixed = [Newtonsoft.Json.JsonConvert]::DeserializeObject($json) ConvertToJsonByJsonNet($fixed)
用 ConvertTo-Json 轉 JSON 再 [Newtonsoft.Json.JsonConvert]::DeserializeObject() 感覺最簡便,最後用它搞定。
Tips of how to serializing PSCustomObject with Json.NET.
Comments
Be the first to post a comment