Node.js (JavaScript) でAPIからの情報取得などでJSONのデータを扱うことが多い。 JSONは構造化されたデータフォーマットで、階層構造(ネスト・入れ子構造)とすることもできる。
ネストされたJSONで子オブジェクトの特定データ(要素)を取得するための方法を紹介する。 一言で言うと、子オブジェクトのlengthを取ってforでループしてifで拾う。
前提モジュール
npm install sync-request
同期処理にする。
同期処理でrequestモジュールの戻り値を返す(Node.js)(非コールバック) - designetwork
npm install util
JSONオブジェクトをdumpする。
参考にさせていただきました。ありがとうございます。
Redmine APIを使用して試験する
サンプルとして自分で使用しているRedmineのAPIを使用する。
RedmineのAPIに関する情報はオフィシャルサイト参照。
Node.jsソースコード
Redmine APIからJSON取得及び階層から特定オブジェクトを取得するためのソースコードは以下の通り。下で各箇所の説明していく。
全ステータス(クローズ済み含む)のチケット数を取得する
以下のようにシンプルに取得すると、通常だとオープンなチケットのみを取得する。total_countが最新のチケット番号より少なく足りていないことが分かるだろう。
http://<Redmine URL>/issues.json?key=<API Access Key>
このとき、全チケットをGETする際にステータスID=*(%2a)を指定する必要がある。
http://<Redmine URL>/issues.json?status_id=%2a&key=<API Access Key>
チケットをJSONで取得する
チケットを取得する関数はredmineGet。試験用チケットのみを取得するためticketLimitを使用している。(mainから渡す)
function redmineGet(ticketLimit){ var ticketJson, ticketOffset=0; var ticketUrl="<Redmine URL>", ticketKey="<API Access Key>"; var response = request( 'GET', 'http://'+ticketUrl+'/issues.json?status_id=*&limit='+ticketLimit+'&offset='+ticketOffset+'&key='+ticketKey ); if (response.statusCode == 200) { //console.log("Redmine : "+response.body); ticketJson = JSON.parse(response.body); return ticketJson; } }
JSONをparse(解析)するとこのような構造になっている。[Object]になっている部分は記載の通りObjectで、階層が深く、もう一段dumpする必要がある。(ここでは割愛)
また、本方式だとチケットを降順で取得する。
{ issues: [ { id: 101, project: [Object], tracker: [Object], status: [Object], priority: [Object], author: [Object], assigned_to: [Object], category: [Object], subject: 'Node.jsの記事を投稿する', description: '', start_date: '2016-11-18', done_ratio: 0, custom_fields: [Object], created_on: '2016-11-18T01:15:59Z', updated_on: '2016-11-18T08:56:39Z' }, { id: 100, project: [Object], <snip> created_on: '2016-11-18T01:10:02Z', updated_on: '2016-11-18T06:36:54Z' } ], total_count: 101, offset: 0, limit: 4 }
階層JSONのオブジェクトを取得する
取得した各オブジェクト(=チケット)の情報を取得する。今回対象とするのは、Redmineのカスタムフィールド。
カスタムフィールドはユーザ任意で作れるもので、用途に応じて追加することができる。今回、試験用に「不具合番号」、「要求元」、「システム」という項目を作っている。 JSONの階層構造では以下のようになっている。
{ issues: [ { id: 99, subject: 'Node.jsが起動しない', <snip> custom_fields: { id: 1, name: '不具合番号', value: '634' }, { id: 3, name: '要求元', value: '営業' }, { id: 4, name: 'システム', value: 'GitHub' },
今回はチケットのうち、「システム」が「HatenaBlog」のものを抽出する。
各項目はカスタムフィールド内のidで管理されている。 一般的な使い方として、これらはトラッカーによって表示/非表示を切り替えている。 そのため、チケットによって以下の通り項目数(インデックス番号)が変わることになる。
その中で抽出対象に"HatenaBlog ticket!!"を付けている。(ソースコードは後述)
101 'Node.jsの記事を投稿する' customFields : index: 0 | id: 4 name: システム value: HatenaBlog HatenaBlog ticket!! 100 'Node.jsでJSONをGETできない' customFields : index: 0 | id: 1 name: 不具合番号 value: 635 index: 1 | id: 3 name: 要求元 value: 保守 index: 2 | id: 4 name: システム value: HatenaBlog HatenaBlog ticket!! 99 'Node.jsが起動しない' customFields : index: 0 | id: 1 name: 不具合番号 value: 634 index: 1 | id: 3 name: 要求元 value: 営業 index: 2 | id: 4 name: システム value: GitHub 98 'Node.jsで階層JSONから特定オブジェクトを取り出す' customFields : index: 0 | id: 3 name: 要求元 value: 営業 index: 1 | id: 4 name: システム value: Redmine
ネストされたJSONの中で、変動するインデックス番号を追従するために考慮が必要になる。
今回は、シンプルで愚直な考え方だが、オブジェクトの要素数を取得し、要素数分だけforでループする。各indexでif判定することにより、対象を抽出する。
var customFieldsLength, cf=["id", "name", "value"]; //cf:Custom Fields for(var i=0; i<ticketCount; i++){ console.log("\n");dump(returnJson["issues"][i]["id"]);dump(returnJson["issues"][i]["subject"]); customFieldsLength = returnJson["issues"][i]["custom_fields"].length; console.log("\n customFields : "); for(var j=0; j<customFieldsLength; j++){ cf = returnJson["issues"][i]["custom_fields"][j]; console.log(" index: "+j+" | id: "+cf.id+" name: "+cf.name+" value: "+cf.value); if((cf.id==4) && (cf.value == "HatenaBlog")){console.log(" HatenaBlog ticket!!");} } }
まとめ
Node.js (javascript) で階層JSONを取得し、子オブジェクトの特定データを抽出した。
Redmineのチケット一覧などの情報を整理するときに適用できる。