寫了一段 PowerShell 從 JSON 還原陣列,打算濾掉部分內容將另存成 JSON 檔,不料遇到 Where-Object 篩選無效的問題。

以下是重現問題的範例:

$json = @"
[ 
    { "Nam": "Jeffrey", "Score": 255 },
    { "Name": "darkthread", "Score": 9999 },
    { "Name": "Ironman", "Score": 32767 }
]
"@
$json | ConvertFrom-Json | Where-Object { $_.Score -gt 256 } | ConvertTo-Json

預期結果應該只看到兩筆,但三筆都在。而且,等等! JSON 顯示它不是陣列,而是一個具有 value 跟 Count 屬性的物件。

問題出在 ConvertFrom-Json 會將陣列包成單一物件傳給 Pipeline,故簡化成 '[1,2,3]' | ConvertFrom-Json | ConvertTo-Json 也能重現問題:

解法很簡單,用括號把 ConvertTo-Json 結果包起來,或是將 ConvertTo-Json 結果存入變數都會強迫展開成陣列:

有趣的是,有些 Cmdlet 接到 Pipeline 傳來包成單一物件的陣列會自動展開,例如 Fomrat-List,但 Where-Object 不會,所以會出現下面這種結果:

不管有沒有包括號,Format-List 都能列出陣列的三個數字,但套上 Where-Object 條件,有加括號展開陣列傳入才有篩選效果。這個現象蠻玄的,害我在處理時多迷惑一陣子。

如果對背後的原理有興趣,這則 stackoverflow 回答提供了完整解釋,摘要重點如下:

  1. ConvertFrom-Json 解析陣列 JSON 字串時未依慣例一個一個元素傳入 Pipeline,而是整個包成一個 System.Array 物件傳下去。
  2. PowerShell ETS(Extended Type System) 為 System.Array 加入了 Count 屬性,而 ConvertTo-Json 顯示時會把它包含進來。用 ,(1,2,3) | ConvertTo-Json可以觀察到類似結果:
  3. 用括號包住或設為變數都有強制展開陣列的效果,這是 Workaround 有效的原因。
  4. ETS 加入 .Count 的行為 PSv3 起已歸為過時,但 PSv5.1 仍存在,PowerSell Core (PS6+) 之後已取消。
  5. 依此原理,除了加括號存變數,用 Remove-TypeData System.Array 也能解決問題:

Explaining the unexpected behavior of ConvertFrom-Json when passing array results to the pipeline.


Comments

Be the first to post a comment

Post a comment