かずき先生から謎APIをご提供いただいたので触ってみましたw 何分初めて真面目にapiを叩いたり、JSON触ったので寄り道しまくりで。 無理やりJSON無視して-replaceで抜き出したりとか、JSON Serializationでやってみたりとかしました。 ====
どんなAPI?
期間限定で用意していただいたので、キャプチャだけ。 http://guitarrapc.azurewebsites.net/api/people
http://guitarrapc.azurewebsites.net/api/people/200
なんか、2000号まで量産されました。
まずはAPI叩いてみよう
Wheatherほにゃららの時同様に、New-WebServiceProxy
コマンドレットでどうかなと。
New-WebServiceProxy cmdlet - TechNet - Microsoft
えいやっと。
New-WebServiceProxy "http://guitarrapc.azurewebsites.net/api/people"
…あれ?WSDLドキュメントじゃないよー、とエラーが出てしまい使えないようです。
New-WebServiceProxy : URL http://guitarrapc.azurewebsites.net/api/people のドキュメントは既知のドキュメントの種類として認識されませんでした。 それぞれの既知の種類に関するエラー メッセージを参照して問題を解決してください。 - 'WSDL ドキュメント' からのレポート: 'XML ドキュメント (1,1) でエラーが発生しました。' - ルート レベルのデータが無効です。 行 1、位置 1。 - 'XML スキーマ' からのレポート: 'ルート レベルのデータが無効です。 行 1、位置 1。' - 'DISCO ドキュメント' からのレポート: 'ルート レベルのデータが無効です。 行 1、位置 1。' 発生場所 行:1 文字:1 + New-WebServiceProxy "http://guitarrapc.azurewebsites.net/api/people" + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (http://guitarrapc.net/api/people:Uri) [New-WebServiceProxy]、InvalidOperationException + FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.NewWebServiceProxy
気を取り直して、System.Net.WebClientではどうでしょうか。
MSDN - WebClient クラス
$uri = "http://guitarrapc.azurewebsites.net/api/people" $client = New-Object System.Net.WebClient $stream = $client.OpenRead($uri) $stream
無事に接続できました。
CanTimeout : True ReadTimeout : 300000 WriteTimeout : 300000 CanRead : True CanSeek : False CanWrite : False Length : Position :
後は、System.IO.StreamReader().ReadLine()を使って読み取るだけですね。
$uri = "http://guitarrapc.azurewebsites.net/api/people" $encode_utf8 = [Text.Encoding]::GetEncoding("utf-8") $client = New-Object System.Net.WebClient $stream = $client.OpenRead($uri) $streamReader = New-Object System.IO.StreamReader($stream,$encode_utf8) $dataSream = $streamReader.ReadLine()
[Text.Encoding]::GetEncoding()を使って、読み取り時にEncodingとして"Utf-8"を指定します。 "Shift-JIS"では日本語化けします。 ここまでをいったん纏めて、データを取得する簡単なfunctionを書いてみました。
function Get-guitarrapcDataSream{ [CmdletBinding()] param( [string]$uri ) $encode_utf8 = [Text.Encoding]::GetEncoding("utf-8") $client = New-Object System.Net.WebClient $stream = $client.OpenRead($uri) $streamReader = New-Object System.IO.StreamReader($stream,$encode_utf8) $dataSream = $streamReader.ReadLine() return $dataSream }
URIを指定して取得してみましょう。
$uri = "http://guitarrapc.azurewebsites.net/api/people" Get-guitarrapcDataSream -uri $uri
わざわざJSONフォーマットで取得されました
JSONをxmlにシリアライズ
PowerShellはJSONをそのまま扱うより、一旦xmlにバイパスすることで、簡単にデータアクセスできます。 そこで、この記事を参考にWCF DataContractJsonSerializerでXMLにバイパスさせてみました。
JSON Serialization/Deserialization in PowerShell
function Get-guitarrapcDataSream{ [CmdletBinding()] param( [string]$uri ) $encode_utf8 = [Text.Encoding]::GetEncoding("utf-8") $client = New-Object System.Net.WebClient $stream = $client.OpenRead($uri) $streamReader = New-Object System.IO.StreamReader($stream,$encode_utf8) $dataSream = $streamReader.ReadLine() return $dataSream } function Get-HashTableToCustomObject{ [CmdletBinding()] param( [object[]]$xmldata ) $output = $xmldata ` | %{ [PSCustomObject]@{ Id=[int]$_.id.InnerText; Name=[string]$_.Name.InnerText}} return $output } function Convert-guitarrapcJsonToXml{ [CmdletBinding()] param( [int]$setId=-1 ) #region Uri add Setid parameter exist $uri = "http://guitarrapc.azurewebsites.net/api/people" if ($setId -ne -1) { $uri = $uri + "/" + $setId } #endregion #obtain Json data from uri $dataSream = Get-guitarrapcDataSream -uri $uri # Encode data ToBytes $bytes = [Text.Encoding]::UTF8.GetBytes($dataSream) # Create JsonReader $quotas = [System.Xml.XmlDictionaryReaderQuotas]::Max $JSONReader = [System.Runtime.Serialization.Json.JsonReaderWriterFactory]::CreateJsonReader($bytes,$quotas) # Convert Json to XML $xml = New-Object System.Xml.XmlDataDocument $xml.Load($JSONReader) $JSONReader.Close() if ($xml.root.Item -is [System.Object[]]) { # Convert HashTable to CustomObject $output = $output = Get-HashTableToCustomObject -xmldata $xml.root.item # sort data by id(cause HashTable not sorted) return $output.GetEnumerator() | sort id } else { # Convert HashTable to CustomObject $output = Get-HashTableToCustomObject -xmldata $xml.root # only one array return, no sorting required return $output } }
[byte][char]では日本語が含まれるとエラーが出る
少し引っかかったので。
[byte[]][char[]]"あ"
ようは、「byteは0から255までだから、日本語を1byteには変換できない」のが原因と。
値 "あ" を型 "System.Byte" に変換できません。エラー: "符号なしバイト型に対して値が大きすぎるか、または小さすぎます。" 発生場所 行:1 文字:1 + [byte[]][char[]]"あ" + ~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidArgument: (:) []、RuntimeException + FullyQualifiedErrorId : InvalidCastIConvertible
そこで、[Text.Encoding]::UTF8.GetBytes()を使ってマルチバイト(日本語)をバイト シーケンスにエンコードしています。
Encoding.GetBytes メソッド
[[Text.Encoding]::UTF8.GetBytes("あ")
無事にいきました。
227 129 130
[PSCustomObject]を使ってNew-Obejctの注意
HashTableは、Dictionaryなので「順序」が保障されていません。 そのため、以下のコードでは-eqでの指定はできても-ltなどで比較ができません。
$output = $xmldata ` | %{ [PSCustomObject]@{ Id=[int]$_.id.InnerText; Name=[string]$_.Name.InnerText}} $output
そこで、.GetEnumerator()してからsortをすることで、指定したプロパティをキーにきっちり並びます。
$output.GetEnumerator() | sort id
XMLにバイパスした際の値の指定
複数のデータが含まれている時と、一つの時では、$xmlでデータを指定する際に微妙に異なります。
$xml.root.item #複数のデータが含まれたJSONをxmlにバイパス時 $xml.root #一つのデータが含まれたJSONをxmlにバイパス時
この違いは、GetType()すると判定できます。
($xml.root.Item).gettype().fullname
System.Object[] #複数のデータが含まれたJSONをxmlにバイパス時 System.Management.Automation.PSParameterizedProperty #一つのデータが含まれたJSONをxmlにバイパス時
あとは、データが一つの時は、$output.GetEnumerator() | sort id
ではなく$output
ですね。
実行してみる
ででんと。
Convert-guitarrapcJsonToXml| ?{$_.id -eq 200} | Format-Table -AutoSize Convert-guitarrapcJsonToXml | ?{$_.Name -match ".*100.*"} | Format-Table -AutoSize Convert-guitarrapcJsonToXml | ?{$_.id -lt 10} | Format-Table -AutoSize Convert-guitarrapcJsonToXml -setId 1 | Format-List
上手くデータを指定できていますね。
Id Name -- ---- 200 量産型ぎたぱそ200号 Id Name -- ---- 100 量産型ぎたぱそ100号 1000 量産型ぎたぱそ1000号 1001 量産型ぎたぱそ1001号 1002 量産型ぎたぱそ1002号 1003 量産型ぎたぱそ1003号 1004 量産型ぎたぱそ1004号 1005 量産型ぎたぱそ1005号 1006 量産型ぎたぱそ1006号 1007 量産型ぎたぱそ1007号 1008 量産型ぎたぱそ1008号 1009 量産型ぎたぱそ1009号 1100 量産型ぎたぱそ1100号 Id Name -- ---- 1 量産型ぎたぱそ1号 2 量産型ぎたぱそ2号 3 量産型ぎたぱそ3号 4 量産型ぎたぱそ4号 5 量産型ぎたぱそ5号 6 量産型ぎたぱそ6号 7 量産型ぎたぱそ7号 8 量産型ぎたぱそ8号 9 量産型ぎたぱそ9号 Id : 1 Name : 量産型ぎたぱそ1号
Json楽しいです!
無理やり力技
アンチパターンとして自戒の念を込めて晒しておきます。 勉強が足りませんね。
#Required -Version -3.0 #return $dataSream from api function Get-guitarrapcDataSream{ [CmdletBinding()] param( [string]$uri ) $encode_utf8 = [Text.Encoding]::GetEncoding("utf-8") $replaceSream = "[`[{}`]]" $client = New-Object System.Net.WebClient $stream = $client.OpenRead($uri) $streamReader = New-Object System.IO.StreamReader($stream,$encode_utf8) $dataSream = $streamReader.ReadLine() return $dataSream } #return split $dataSream to each row function Split-guitarrapcDataSream{ [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [string]$streamData ) $splitStream = "},{" $replaceSream = "[`[{}`]]" return $streamData -split $splitstream -replace $replaceSream,"" } #return formatted api data function Get-guitarrapcApi{ [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [int]$setId=-1 ) #region Uri add Setid parameter exist $uri = "http://guitarrapc.azurewebsites.net/api/people" if ($setId -ne -1) { $uri = $uri + "/" + $setId } #endregion #obtain data from uri $dataSream = Get-guitarrapcDataSream -uri $uri #split $dataSream to each row $dataSplit = Split-guitarrapcDataSream -streamData $dataSream #region create PScustomObject from string $pattern = "`"Id`":(?<id>.*),`"Name`":`"(?<Name>.*)`"" $customData = $dataSplit ` | %{ $_ -cmatch $pattern ` | %{ [PSCustomObject]@{ id=$Matches.id; Name=$Matches.Name } } } #endregion return $customData }
実行してみます。
Get-guitarrapcApi -setId 1998 | select id Get-guitarrapcApi | select -First 2 | Format-Table -AutoSize Get-guitarrapcApi ` | ?{$_.id -eq 100} ` | select Name ` | Format-List
一応、取れてますが、まったく応用が利かないので没です。
id -- 1998 id Name -- ---- 1 量産型ぎたぱそ1号 2 量産型ぎたぱそ2号 Name : 量産型ぎたぱそ100号
おまけ1
ただデータ取得するだけです。
function Get-guitarrapcToFile{ [CmdletBinding()] param( [string]$uri="http://guitarrapc.azurewebsites.net/api/people", [string]$path, [string]$filename ) $savefile = $path + "\" + $filename $client = New-Object System.Net.WebClient $client.DownloadFile($uri,$savefile) } Get-guitarrapcToFile -path ((Get-Location).Path) -filename "sample2.JSON"
おまけ2
ダウンロードしたJSONを適当に整形するカジュアルワンライナー (反省しています
Get-Content .\sample.JSON | ForEach-Object{$_ -replace "\[" , "[`n`t" -replace "{`"" , "{`n`t`t`"" -replace ",`"" , ",`n`t`t`"" -replace "`"}" , "`"`n`t}" -replace "},{" , "},`n`t{" -replace "\]" , "`n]"} ` | Out-File .\sample_formatted.JSON
一応改行するとこうです。
Get-Content .\sample.JSON ` | ForEach-Object{$_ ` -replace "\[" , "[`n`t" ` -replace "{`"" , "{`n`t`t`"" ` -replace ",`"" , ",`n`t`t`"" ` -replace "`"}" , "`"`n`t}" ` -replace "},{" , "},`n`t{" ` -replace "\]" , "`n]" ` } ` | Out-File .\sample_formatted.JSON
全く流用できませんね……
ODATAはどうした
@guitarrapc こいつは、ODataのクエリを使って検索とかできますよ
— かずきさん (@okazuki) 2013年2月20日
いやー、サンプルサイトでは問題なく書けたのですが……こんかいの謎APIでは上手くいかなくて……すいません><
@guitarrapc ぎたぱそさんなら、原文のドキュメント当たるのが一番早そう odata.org/media/30002/OD…
— かずきさん (@okazuki) 2013年2月20日
参考サイト
Windows PowerShell を使用して株価をすばやく確認する方法はありますか PowerShell Scripting Weblog - PowerShellでJScript.NETを利用してJSONをパースする JSON Serialization/Deserialization in PowerShell ODATAサンプルサイト OData原文仕様