【PowerShell】JSONを読み書きする方法|ConvertFrom-Json・ConvertTo-Json・ネスト・APIレスポンス処理

【PowerShell】JSONを読み書きする方法|ConvertFrom-Json・ConvertTo-Json・ネスト・APIレスポンス処理 PowerShell

JSONは、Web APIのやり取りや設定ファイルで最もよく使われるデータ形式です。PowerShellには、JSONを扱う2つのコマンドが標準で用意されています。文字列のJSONをオブジェクトに変換するConvertFrom-Jsonと、オブジェクトをJSON文字列に変換するConvertTo-Jsonです。

基本はこの2つを覚えるだけですが、ConvertTo-Jsonの既定のネスト深さが「2」しかなく、深い階層が勝手に切り捨てられるという、知らないと必ずハマる落とし穴があります。この記事では、読み込み・書き出し・APIレスポンス処理までを、実機のPowerShellで確認しながら解説します。

先に結論

  • JSON文字列をオブジェクトにするにはConvertFrom-Json。結果はPSCustomObjectで、$obj.nameのように読めます。
  • オブジェクトをJSONにするにはConvertTo-Json。ハッシュテーブルやオブジェクト配列も変換できます。
  • 最重要ConvertTo-Jsonの既定-Depthは2です。深いネストは切れるので、-Depth 10のように明示します。
  • 1行にまとめるなら-Compressを付けます。
  • ファイルから読むときはGet-Content -Rawで全体を1つの文字列として渡すのが確実です。
  • APIのJSONレスポンスは、Invoke-RestMethodなら自動でオブジェクトに変換されます。

JSONはハッシュテーブルやオブジェクトと密接に関わります。配列とハッシュテーブルの使い方、ファイル保存時のエンコードは文字化けを直す方法とあわせて読むと理解が深まります。同じくデータ形式を扱うCSVを読み書きする方法も、JSONと使い分ける場面で参考になります。

スポンサーリンク

ConvertFrom-JsonでJSONを読み込む

JSON形式の文字列をConvertFrom-Jsonに渡すと、PSCustomObjectというオブジェクトに変換されます。あとは普通のオブジェクトと同じく、ドット記法でプロパティを読み出せます。

convertfrom-basic.ps1
$json = '{"name":"山田","age":30}'

$obj = $json | ConvertFrom-Json

$obj.name   # 山田
$obj.age    # 30
$obj.GetType().Name   # PSCustomObject

実機でも、$obj.name山田、型がPSCustomObjectになることを確認しています。JSONのキーが、そのままオブジェクトのプロパティ名になります。

ネストや配列を含むJSONにアクセスする

入れ子になったオブジェクトや配列も、そのまま辿れます。ネストはドットでつなぎ、配列はインデックスで取り出します。

convertfrom-nested.ps1
$json = '{
  "name": "山田",
  "address": { "city": "東京", "zip": "100-0001" },
  "hobbies": ["読書", "旅行"]
}'

$obj = $json | ConvertFrom-Json

$obj.address.city   # 東京(ネストをドットで辿る)
$obj.hobbies[0]     # 読書(配列はインデックス)

# 配列をループ処理する
foreach ($hobby in $obj.hobbies) {
    Write-Host $hobby
}

ネストは$obj.address.cityのようにドットでつなぐだけ、配列は$obj.hobbies[0]のようにインデックスで取り出せます。配列の各要素はforeachで回せます。

ファイルからJSONを読み込む

JSONファイルを読むときは、Get-Contentで読み込んでからConvertFrom-Jsonに渡します。このとき、-Rawを付けてファイル全体を1つの文字列として渡すのが確実です。

read-json-file.ps1
# -Raw でファイル全体を1つの文字列として読む(推奨)
$config = Get-Content "C:\work\config.json" -Raw | ConvertFrom-Json

$config.name

# 文字化けする場合はエンコードも明示する
$config = Get-Content "C:\work\config.json" -Raw -Encoding UTF8 |
    ConvertFrom-Json
-Raw を付ける理由

-Rawを付けないと、Get-Contentはファイルを1行ずつの配列として読み込みます。環境やバージョンによっては、この複数行のまま渡すとJSONの解析に失敗することがあります。-Rawでファイル全体を1つの文字列にしてから渡せば、確実かつ効率的です。読み込み時に文字化けする場合は、-Encoding UTF8もあわせて指定してください。

ConvertTo-JsonでオブジェクトをJSONにする

逆に、PowerShellのオブジェクトをJSON文字列にするにはConvertTo-Jsonを使います。ハッシュテーブルでもPSCustomObjectでも変換できます。

convertto-basic.ps1
# ハッシュテーブルから
@{ name = "山田"; age = 30 } | ConvertTo-Json

# PSCustomObject から
[PSCustomObject]@{ name = "山田"; age = 30 } | ConvertTo-Json

# オブジェクトの配列から(JSON配列になる)
$people = @(
    [PSCustomObject]@{ name = "A"; score = 90 }
    [PSCustomObject]@{ name = "B"; score = 80 }
)
$people | ConvertTo-Json

オブジェクトの配列を渡すと、JSONの配列[ ... ]になります。実機でも、2件のオブジェクト配列が[{"name":"A","score":90},{"name":"B","score":80}]に変換されることを確認しています。

【最重要】既定のDepthは2、深いネストは切れる

ここがPowerShellのJSONで最もハマるポイントです。ConvertTo-Jsonは、既定では2階層までしかネストを展開しません。それより深い部分は、中身ではなく"System.Collections.Hashtable"のような型名の文字列に化けてしまいます。

convertto-depth.ps1
$data = @{ a = @{ b = @{ c = @{ d = "深い値" } } } }

# 既定(-Depth 2)だと深い階層が切れる
$data | ConvertTo-Json
# 出力例:
# {
#   "a": { "b": { "c": "System.Collections.Hashtable" } }
# }   ← d の値が失われている

# -Depth を十分に大きく指定すると、全階層が出る
$data | ConvertTo-Json -Depth 5
# {"a":{"b":{"c":{"d":"深い値"}}}}
ネストがあるなら必ず -Depth を指定する

実機で確認したところ、既定のままでは深い階層の値が"System.Collections.Hashtable"という文字列に置き換わり、データが失われました。エラーは出ないため気づきにくく、出力されたJSONを他システムへ渡してから発覚する、という事故になりがちです。ネストのあるデータは、-Depth 10のように余裕をもった深さを必ず指定してください(最大100まで指定できます)。

1行にまとめる -Compress と日本語の扱い

既定のConvertTo-Jsonは、見やすいように改行とインデントを入れた整形済みのJSONを出します。APIへ送るときなど1行にまとめたい場合は、-Compressを付けます。

convertto-compress.ps1
@{ name = "山田"; age = 30 } | ConvertTo-Json -Compress
# {"age":30,"name":"山田"}  ← 改行なしの1行になる

日本語の扱いには補足があります。ConvertTo-Jsonは、環境やバージョンによって、日本語などの非ASCII文字を\u5c71\u7530のようなUnicodeエスケープで出力することがあります(今回テストしたWindows PowerShell 5.1のビルドでは、山田がそのまま出力されました)。エスケープされていても正しいJSONであり、読み戻せば元の文字に戻るため、データとしては問題ありません。生のまま出したい場合は、エスケープを行わないPowerShell 7以降を使う方法があります。なお、ファイルに保存したときに画面表示が崩れる場合は、JSONのエスケープではなくファイルのエンコードが原因のことが多いです。

JSONファイルへ書き出す(エンコードに注意)

変換したJSONをファイルに保存するときは、エンコードに注意します。Windows PowerShell 5.1の既定では意図しない文字コードになりやすいため、-Encodingを明示します。

write-json-file.ps1
$data = [PSCustomObject]@{
    name    = "山田"
    age     = 30
    address = [PSCustomObject]@{ city = "東京" }
}

# ネストがあるので -Depth を指定し、UTF-8 で保存する
$data | ConvertTo-Json -Depth 10 |
    Out-File "C:\work\out.json" -Encoding UTF8

# BOM無しUTF-8が必要なら .NET で書き出す(PHPなど他ツール向け)
$json = $data | ConvertTo-Json -Depth 10
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText("C:\work\out.json", $json, $utf8NoBom)

5.1の-Encoding UTF8はBOM付きになるため、BOMを嫌う相手(PHPや一部のCLI)に渡すなら、.NETのWriteAllTextでBOM無しUTF-8にします。このあたりの詳細は文字化けを直す方法で解説しています。

値を変更してJSONを更新する

設定ファイルを読み込んで一部の値を書き換え、保存し直す、というのはよくある処理です。読み込んだPSCustomObjectのプロパティはそのまま変更でき、新しいプロパティはAdd-Memberで追加します。

modify-json.ps1
$obj = '{"name":"山田","age":30}' | ConvertFrom-Json

# 既存プロパティの変更
$obj.age = 31

# プロパティの追加
$obj | Add-Member -NotePropertyName "city" -NotePropertyValue "東京"

# JSONに戻す
$obj | ConvertTo-Json -Compress
# {"name":"山田","age":31,"city":"東京"}

実機でも、値の変更とAdd-Memberでの追加を行ったうえでConvertTo-Jsonに戻すと、変更が反映されたJSONが得られました。設定ファイルの更新は「読む→変える→-Depthを付けて書き戻す」という流れになります。

APIのJSONレスポンスを扱う(Invoke-RestMethod)

Web APIからJSONを受け取る場合、Invoke-RestMethodを使うとレスポンスを自動でオブジェクトに変換してくれます。ConvertFrom-Jsonを自分で呼ぶ必要はありません。

invoke-restmethod.ps1
# Invoke-RestMethod は JSON を自動でオブジェクト化する
$result = Invoke-RestMethod -Uri "https://api.example.com/users/1"
$result.name   # そのままプロパティにアクセスできる

# JSON を送る POST(オブジェクトを ConvertTo-Json して body に)
$body = @{ name = "山田"; age = 30 } | ConvertTo-Json
$response = Invoke-RestMethod -Uri "https://api.example.com/users" `
    -Method Post -Body $body -ContentType "application/json"

# Invoke-WebRequest を使う場合は .Content を手動で変換する
$res = Invoke-WebRequest -Uri "https://api.example.com/users/1"
$obj = $res.Content | ConvertFrom-Json

受け取り(GET)ではInvoke-RestMethodが変換まで担い、送信(POST)ではConvertTo-JsonでJSON文字列を作って-Bodyに渡します。Invoke-WebRequestを使う場合は、レスポンスの.Contentを自分でConvertFrom-Jsonします。

キーの順序を保つには [ordered] を使う

ハッシュテーブルからJSONを作ると、キーの順番が書いた順と変わることがあります。実機でも、@{ enabled=...; max=... }{"max":...,"enabled":...}と入れ替わりました。JSONのキー順を保ちたいときは、[ordered]付きのハッシュテーブルかPSCustomObjectを使います。

ordered-json.ps1
# 通常のハッシュテーブルは順序が保証されない
@{ id = 1; name = "山田"; age = 30 } | ConvertTo-Json -Compress

# [ordered] なら書いた順を保つ
[ordered]@{ id = 1; name = "山田"; age = 30 } | ConvertTo-Json -Compress
# {"id":1,"name":"山田","age":30}

# PSCustomObject も順序を保つ
[PSCustomObject]@{ id = 1; name = "山田"; age = 30 } | ConvertTo-Json -Compress

キー順が崩れて困るのは、人が読むファイルや、順序を前提にした相手に渡す場合です。[ordered]PSCustomObjectの使い分けは配列とハッシュテーブルの使い方でも触れています。

よくある失敗

ConvertTo-JsonでDepthを指定せず深い階層が消える

既定の-Depthは2です。ネストのあるデータは、深い部分が型名の文字列に化けて失われます。エラーが出ないため気づきにくいので、-Depth 10などを必ず指定してください。

ファイルを-Rawなしで読んで解析に失敗する

Get-Contentは既定で1行ずつの配列を返します。環境によっては解析に失敗するため、-Rawで全体を1つの文字列にしてからConvertFrom-Jsonへ渡します。

保存したJSONが文字化けする

これはJSON変換ではなく、ファイルのエンコードが原因です。5.1の既定エンコードは意図しない文字コードになりやすいので、-Encoding UTF8やBOM無しUTF-8で保存します。

ハッシュテーブルのキー順が変わって困る

通常のハッシュテーブルは順序を保証しません。順番が重要なら[ordered]@{}PSCustomObjectを使います。

APIレスポンスにConvertFrom-Jsonを二重にかける

Invoke-RestMethodはすでにオブジェクトを返します。これにさらにConvertFrom-Jsonをかけるとエラーになります。手動変換が必要なのはInvoke-WebRequest.Contentのほうです。

よくある質問

QConvertFrom-JsonとConvertTo-Jsonはどちらがどっちですか?
AConvertFrom-Jsonは「JSON文字列からオブジェクトへ」(読み込み)、ConvertTo-Jsonは「オブジェクトを JSON 」(書き出し)です。From=入力、To=出力と覚えると迷いません。
Q-Depth はどのくらいにすればいいですか?
A扱うデータのネストの深さより十分大きい値にします。迷ったら-Depth 10程度、複雑なデータなら-Depth 20などにしておくと安全です。最大100まで指定できます。読み込み側のConvertFrom-Jsonには深さ制限の心配は基本的にありません。
QJSONの日本語が \u30… のようになります。問題ありませんか?
AそれはUnicodeエスケープという正しいJSONの表記で、読み戻せば元の文字に戻るためデータ上は問題ありません。生の日本語で出力したい場合は、エスケープしないPowerShell 7以降を使う方法があります。
QJSONの特定の値だけを取り出すには?
AConvertFrom-Jsonでオブジェクトにしてから、$obj.プロパティ名$obj.配列[0]で辿ります。配列の中から条件で絞るならWhere-Objectを使います。
QInvoke-RestMethodとInvoke-WebRequestの違いは?
AInvoke-RestMethodはJSONなどのレスポンスを自動でオブジェクトに変換します。Invoke-WebRequestはステータスコードやヘッダーを含む生のレスポンスを返し、本文は.Contentから取り出して自分で変換します。APIのJSONを扱うなら前者が手軽です。

まとめ

  • ConvertFrom-JsonでJSON文字列をPSCustomObjectにし、$obj.nameのように読みます。
  • ネストはドット、配列はインデックスで辿れます。ファイルはGet-Content -Rawで読みます。
  • ConvertTo-JsonでオブジェクトをJSONにします。既定Depthは2なので、ネストがあれば-Depthを必ず指定します。
  • 1行にするなら-Compress、保存時は-Encodingで文字化けを防ぎます。
  • キー順を保つなら[ordered]PSCustomObjectを使います。
  • APIのJSONはInvoke-RestMethodが自動でオブジェクト化します。

PowerShellのJSON処理はConvertFrom-JsonConvertTo-Jsonの2つが基本で、覚えること自体は多くありません。ただし-Depthの既定値が2という落とし穴だけは、データを静かに失うため要注意です。ネストのあるデータには必ず-Depthを指定する、と習慣づけておけば安心です。