BLOGTIMES
2014/06/14

JSON と 日本語と PowerShell

  php  powershell  javascript  charset 
このエントリーをはてなブックマークに追加

ちょっとしたデータ保存に JSON を使おうと思って JSON をいじくり回していたのですが、いろいろなデータを入れていくうちに、日本語が \u#### でエスケープされていることに気づきました。この辺は PHP の json_encode()*1 や Ruby の gem json*2 でも全く同じ挙動のようです。

$ php -r 'echo json_encode(array("hoge"=>"ほげ","fuga"=>"ふが"));' {"hoge":"\u307b\u3052","fuga":"\u3075\u304c"} $ ruby -r rubygems -e 'require "json"; puts({"hoge" => "ほげ","fuga"=>"ふが"}.to_json)' {"fuga":"\u3075\u304c","hoge":"\u307b\u3052"}

普段、英数字しか入れてなかったのでよく理解していなかったのですが、そういえばTwitter の全発言アーカイブをダウンロードしたときは確かエンコードされていたような気もします。そんわなけで、ちょっとJSON の仕様*3*4を紐解いてみると、文字は Unicode に従うと書いてありますが、エンコードの種類は指定されていないようなので、UTF-8, UTF-16 などいろいろなエンコードが許容されるようです。もちろん \u#### も許容されています。

もちろんどちらも元に戻るので、PHP <-> Ruby 間で相互運用性に問題が生じることはありません。

$ echo '{"hoge":"\u307b\u3052","fuga":"\u3075\u304c"}' | php -r 'var_dump(json_decode(file_get_contents("php://stdin"),true));' array(2) { ["hoge"]=> string(6) "ほげ" ["fuga"]=> string(6) "ふが" } $ ruby -r rubygems -e 'require "json"; puts JSON.parse(ARGV[0])' -- '{"fuga":"\u3075\u304c","hoge":"\u307b\u3052"}' fugaふがhogeほげ

これをちょっと Windows 上でもお手軽にやろうと思って PowerShell での処理に挑戦してみました。

ひとまずデコードは問題なし

PowerShell にもバージョン 3.0 から ConvertFrom-Json*5ConvertTo-Json*6が使えるようになりました。Windows 8 に標準添付の PowerShell は 4.0 ですが、手元の 7 は 2.0 だったので最近のマシンでないと使うことができないのはネックといえばネックかもしれません。リファレンスにも注釈が入っていますが、この昨日は内部的には .NET Framework の JavaScriptSerializer*7 によって実現されているようです。

まずはデコード側を検証。ConvertFrom-JSON は問題なく値を読み取ることができました。

C:\>echo {"hoge":"\u307b\u3052","fuga":"\u3075\u304c"} | powershell -noprofile -command "$j = ConvertFrom-JSON $input; $j" hoge fuga ---- ---- ほげ ふが

エンコードするとエスケープされない!!

同様にエンコードも ConvertTo-JSON を使えば楽勝か?と思いましたが、一筋縄ではいきませんでした。
なんと、エンコードされていません。しかもこの出力をリダイレクトすると UTF-16 になります。

C:\>powershell -noprofile -command "ConvertTo-JSON -Compress @{hoge='ほげ';fuga='ふが';}" {"hoge":"ほげ","fuga":"ふが"}

ちょっと PowerShell だけ挙動が違うのも困るので、これを何とかすることにしました。まず日本語を \u#### にエスケープする関数を作ります。幸いにも String を HEX に変換するプログラムを見つけた*8ので、今回はこれを元に下記のような関数を書きました。

PS C:\> function escape_str($a){ >> $b=$a.ToCharArray() >> Foreach($element in $b){ >> $i = [System.Convert]::ToUInt32($element) >> if($i -ge 1000){ >> $c = $c + '\u' + [System.String]::Format('{0:x}', $i) >> }else{ >> $c = $c + $element >> } >> } >> $c >> } >> PS C:\> escape_str("ほげ") \u307b\u3052

PowerShell をちゃんと書くのは初めてですが -gt とか書くとシェルスクリプトっぽいですね。こんな感じでしょうか。最後に \\u -> \u という置換をかけていますが、これは ConvertTo-JSON が \ をさらにエスケープしてしまうのを防ぐためです。ちょっといい加減ですが、これでなんとか PHP や Ruby と同じような出力を得ることができました。

PS C:\> $hash = @{hoge='ほげ';fuga='ふが';} PS C:\> $result = @{} PS C:\> $hash.keys | %{ $result.$_ = escape_str($hash.$_) } PS C:\> $result Name Value ---- ----- hoge \u307b\u3052 fuga \u3075\u304c PS C:\> ConvertTo-JSON -Compress $result {"hoge":"\\u307b\\u3052","fuga":"\\u3075\\u304c"} PS C:\> (ConvertTo-JSON -Compress $result).Replace('\\u','\u') {"hoge":"\u307b\u3052","fuga":"\u3075\u304c"}

さらに、エスケープされていない JSON を Windows 上で手軽にエンコードされた形式に変換できるワンライナーがあると便利かなと思って、ワンライナー化してみました。ワンライナーといえない程、汚くて長いですが、ちょっとした変換用途くらいには耐えられると思います。

echo {"hoge":"ほげ","fuga":"ふが"} | powershell -noprofile -command "function e($a){$b=$a.ToCharArray();Foreach($element in $b){$u=[System.Convert]::ToUInt32($element);if($u -ge 1000){$c=$c+'\u'+[System.String]::Format('{0:x}',$u);}else{$c=$c+$element;};};$c;}$j=ConvertFrom-JSON $input;$j.psobject.properties.name|ForEach{$j.$_=e($j.$_);};(ConvertTo-JSON -Compress $j).Replace('\\u','\u')" {"hoge":"\u307b\u3052","fuga":"\u3075\u304c"}

JSON なかなか奥深いですね。


トラックバックについて
Trackback URL:
お気軽にどうぞ。トラックバック前にポリシーをお読みください。[policy]
このエントリへのTrackbackにはこのURLが必要です→https://blog.cles.jp/item/6786
Trackbacks
このエントリにトラックバックはありません
Comments
愛のあるツッコミをお気軽にどうぞ。[policy]
古いエントリについてはコメント制御しているため、即時に反映されないことがあります。
コメントはありません
Comments Form

コメントは承認後の表示となります。
OpenIDでログインすると、即時に公開されます。

OpenID を使ってログインすることができます。

Identity URL: Yahoo! JAPAN IDでログイン