PowerShellを使っていて、他の言語やAPIと連携させようとすると、データ形式が大事になってきます。
XMLでもCSVでも何でもいいのですが、オブジェクトを書くのならばやっぱりJSONが扱いやすいですね。
標準のコマンドレットでは、PSオブジェクトからJSON文字列を作成するConvertTo-Json
や、
その逆のConvertFrom-Json
があります。癖はありますが、一通りの機能が揃っています。
が、それはPowerShell 3.0以降の話。つまりはWindows 7やらWindows Server 2008R2あたりではまだ標準搭載されていないということです。PowerShell (というかWMF)をバージョンアップする手もありますが、今回は素のPowerShell 2.0で行けるところまでやってみたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | # Windows10で実行 PS C:\xxx> powershell 'ConvertTo-Json @(1,2,3)' [ 1, 2, 3 ] # Version 2.0を指定して実行 PS C:\xxx> powershell -v 2 'ConvertTo-Json @(1,2,3)' ConvertTo-Json : 用語 'ConvertTo-Json' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前 として認識されません。名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してか ら、再試行してください。 発生場所 行:1 文字:15 + ConvertTo-Json <<<< @(1,2,3) + CategoryInfo : ObjectNotFound: (ConvertTo-Json:String) []、CommandNotFoundException + FullyQualifiedErrorId : CommandNotFoundException |
もくじ:
はじめに
PowerShellはWindows APIや.NETの機能を使う分には便利なのですが、さすがに単体で規模の大きなソフトウェアを作るのは力不足。
ということで、せめてJSON形式の文字列にして保存しておけば他のプログラムで読めるはず。
PowerShell 3.0以降であればConvertTo-Json
でJSON文字列が生成できるので、これをそのまま書き出せば大丈夫。
# -Depth
パラメータを充分な大きさに指定しないと展開されないのが罠っぽいですね
コンパクト版(-Compress
パラメータあり)なら、スキルの乏しい私にもオブジェクトを解析して何とか作れそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | # オブジェクトの定義 PS C:\xxx> $obj = @{key1="value1"; key2=@{subkey1=@{array=@("x","y"); subobj=@{child="child_value"}}}} # JSON文字列化(インデント付き) PS C:\xxx> ConvertTo-Json $obj -Depth 5 { "key1": "value1", "key2": { "subkey1": { "subobj": { "child": "child_value" }, "array": [ "x", "y" ] } } } # インデントなしコンパクト版 PS C:\xxx> ConvertTo-Json $obj -Depth 5 -Compress {"key1":"value1","key2":{"subkey1":{"subobj":{"child":"child_value"},"array":["x","y"]}}} |
環境はWindows10 + Python 3.6、PowerShellはVersion 5ですが、上記の例と同じくバージョンを2に指定して動作を確認しています。
シンプルなConvertTo-Jsonを作成する
まずはサンプルを掲載しておきます。前半部分はオブジェクトからJSON文字列を組み立てる関数、後半部分はそれを使ったテスト用の記載です。うーん、何とも突貫な感じ。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | function ConvertTo-Json_Alt ($obj){ <# ConvertTo-Json alternative for PowerShell 2.0 No indent, just given as compact JSON string #> # determine object type and initialize result string if ($obj -is [Array]){ $isArray = $true; $res = "["; }elseif ($obj.GetType() -eq @{}.GetType()){ $isArray = $false; $res = "{"; }else{ throw "unexpected object type: {0} ({1})" -f $obj, $obj.GetType(); } # private function for formatting value function _format($item){ $str = $item.ToString().Replace("\", "\\") return $str.Replace("`n", "\n").Replace("`r", "\r").Replace("`t", "\t").Replace("`"", "`"") } # parse content and call recursively if enumerative $accessor = if ($isArray) { @(0..($obj.Count - 1)); } else { $obj.Keys; } switch ($accessor){ {$obj[$_] -isnot [Array] -and $obj[$_] -eq $null} { $res += if ($isArray) { "null," } else { "`"{0}`":null," -f $_; } continue; } {$obj[$_] -is [String]}{ $res += if ($isArray) { "" } else { "`"{0}`":" -f $_; } $res += "`"{0}`"," -f (_format $obj[$_]); } {$obj[$_] -is [ValueType] -and $obj[$_] -isnot [Boolean]}{ $res += if ($isArray) { "" } else { "`"{0}`":" -f $_; } $res +="{0}," -f (_format $obj[$_]) } {$obj[$_] -is [Boolean]}{ $res += if ($isArray) { "" } else { "`"{0}`":" -f $_; } $res +="{0}," -f $obj[$_].ToString().ToLower(); } default{ $res += if ($isArray) { "" } else { "`"{0}`":" -f $_; } $res += "{0}," -f (ConvertTo-Json_Alt $obj[$_]); } } # close bracket and format response $res += if ($isArray) { "]" } else { "}" } return $res.Replace(",]", "]").Replace(",}", "}") } ### $file_name = "data.json"; # define sample object $data = @{"key_str" = "val_str"; "key_int" = -10; "key_uint" = 92; "key_double" = 21.4; "key_text" = "this`nis`ttest`"statement"; "key_bool" = $false; "key_null" = $null; "key_date" = [DateTime]::Now.ToString(); "key_jp" = "テスト"; "key_array" = @("value", "string", 3.2, $null, $true); "key_object" = @{"str" = "string"; "bool" = $true; "null" = $null; "array" = @(1, 2, 3)}} # convert and save as json file ConvertTo-Json_Alt $data | Out-File -FilePath $file_name -Encoding utf8 |
以下にポイントをメモしておきます。
ConvertTo-Json_Alt関数
要するに、オブジェクトの要素を辿って、カッコで括れれば良いのです。
メンバの型で条件分岐させ、値ならば整形し、配列(array
)または連想配列(hashtable
)なら再帰的に処理します。JSONの仕様では文字列・数値・論理値・NULL値を扱えればいいはず。
ちょっとややこしいのは$null
(NULL型)の判定。配列が非NULLを含んでいても真に判定されてしまうので、配列をフィルタしています。
1 2 3 4 5 6 7 | switch ($accessor) { {$obj[$_] -isnot [Array] -and $obj[$_] -eq $null} { $result += if ($isArray) { "null," } else { "`"{0}`":null," -f $_; } continue; } ... } |
標準の<ConvertTo-Json
はJScriptを内部的に呼び出しているようなので、おそらく.NETのAPIを使っても同じようなことができるような気がします。まあ厳密なものを作りたいわけではないので、動けば良し。
JSONファイルへの保存
後でPython (3.x系)に渡したいので、UTF-8で保存しておきます。パイプでOut-File
に渡し、-Encoding
オプションを指定します。
1 | ConvertTo-Json_Alt $data | Out-File -FilePath $file_name -Encoding utf8 |
PowerShellで実行するとこんな感じ。ずらずら出力されて読みにくいですが、たぶんエンコードされているはず。
返り値が文字型(String)なので、Rest APIを使うときはこのままInvoke-RestMethod
コマンドレットなどに渡せます。
1 2 3 4 | PS C:\xxx> powershell -v 2 -c .\test_convert.ps1 {"key_object":{"str":"string","null":null,"array":[1,2,3],"bool":true},"key_str":"val_str","key_jp":"テスト","key_int":- 10,"key_uint":92,"key_bool":false,"key_text":"this\nis\ttest\"statement","key_date":"2017 /10/04 21:35:28","key_array":["value","string",3.2,null,true],"key_null":null,"key_double":21.4} |
Pythonへのインポート
ここではPowerShellで出力したJSON文字列ファイルを、Pythonへインポートしてオブジェクトへ変換する処理のサンプルを記載します。
標準のjson
モジュールで読めるはずですが、おそらくこのスクリプトはPython 2系統で動きません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import json def load_json(file_name): with open(file_name, "r", encoding='utf-8-sig') as f: return json.load(f) def save_json(data, file_name): with open(file_name, "w", encoding='utf-8-sig') as f: json.dump(data, f, ensure_ascii=False) if __name__ == "__main__": # load object from JSON file file_path = "data.json" data = load_json(file_path) # debug print(json.dumps(data, indent=4, ensure_ascii=False)) data["additional_key"] = "additional_value" # save object to another json file file_path = "saved-again.json" save_json(data, file_path) |
以下、上と同様にポイントを書いておきます。
JSONファイルの読み込み
やや扱いが面倒なのは、Windows処理系にありがちなBOM付きテキストファイルですね。文字コードをうまく指定しないと、文字化けするかデコードエラーが発生します。
1 2 3 4 5 | import json def load_json(file_name): with open(file_name, "r", encoding='utf-8-sig') as f: return json.load(f) |
Python 2系統では試していませんが、おそらく同様にcodecs
モジュールで文字コードを指定すれば動くと思われます。
JSONファイルの書き出し
逆にファイルへ書き出す時もBOM付きUTF-8を指定すると、PowerShellなどWindows系の環境で扱いやすくなるでしょう。
日本語を含む場合を想定してensure_ascii
を指定しておきます。
1 2 3 4 5 | import json def save_json(data, file_name): with open(file_name, "w", encoding='utf-8-sig') as f: json.dump(data, f, ensure_ascii=False) |
前述のスクリプトを実行すると、"data.json"
ファイルを読み込み、Pythonオブジェクトとして解釈します。インデント付きで表示させて分かる通り、期待通りにJSONファイルを作成できているようですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | PS C:\xxx> python .\import_json.py { "key_object": { "str": "string", "null": null, "array": [ 1, 2, 3 ], "bool": true }, "key_str": "val_str", "key_jp": "テスト", "key_int": -10, "key_uint": 92, "key_bool": false, "key_text": "this\nis\ttest\"statement", "key_date": "2017/10/04 21:35:28", "key_array": [ "value", "string", 3.2, null, true ], "key_null": null, "key_double": 21.4 } |