以下の内容はhttps://tech.guitarrapc.com/entry/2013/02/20/100238より取得しました。


PowerShellでJSONを触ってみる

かずき先生から謎APIをご提供いただいたので触ってみました。

何分初めて真面目にapiを叩いたり、JSON触ったので寄り道しまくりで。 無理やりJSON無視して-replaceで抜き出したりとか、JSON Serializationでやってみたりとかしました。

まずは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 クラス.aspx)

$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]::UTF-8.GetBytes()を使ってマルチバイト(日本語)をバイト シーケンスにエンコードしています。

Encoding.GetBytes メソッド.aspx)

[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にバイパスした際の値の指定

複数のデータが含まれている時と、1つの時では、$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にバイパス時

あとは、データが1つの時は、$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 量産型ぎたぱそ1001000 量産型ぎたぱそ10001001 量産型ぎたぱそ10011002 量産型ぎたぱそ10021003 量産型ぎたぱそ10031004 量産型ぎたぱそ10041005 量産型ぎたぱそ10051006 量産型ぎたぱそ10061007 量産型ぎたぱそ10071008 量産型ぎたぱそ10081009 量産型ぎたぱそ10091100 量産型ぎたぱそ1100号



Id Name
-- ----
 1 量産型ぎたぱそ12 量産型ぎたぱそ23 量産型ぎたぱそ34 量産型ぎたぱそ45 量産型ぎたぱそ56 量産型ぎたぱそ67 量産型ぎたぱそ78 量産型ぎたぱそ89 量産型ぎたぱそ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&gt>`""
  $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  量産型ぎたぱそ12  量産型ぎたぱそ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はどうした

かずきさん (@okazuki)

サンプルサイトでは問題なく書けたのですが、謎APIでは上手くいかない。

かずきさん (@okazuki)

参考サイト




以上の内容はhttps://tech.guitarrapc.com/entry/2013/02/20/100238より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14