最近はスクリプト言語を使う時に何を使うのが良いか迷います。

いや、もともと大して選んでいないんですが、Azure 関連の操作がメインだったので PowerShell をよく使っています。

PowerShell は C#っぽく書けるので悪くないんですが、改めて、OS 関係なく軽快に動いてくれる言語として go に注目しています。

プログラミングのリハビリを兼ねて go で Grafana を扱うコードを書いていたんですが、json を扱うところで苦労したのでノウハウをメモしておこうと思います。

encoding/json を使用する場合のお話になります。

1. 礼儀正しく扱う

json の構造をすべて把握している場合は、その構造を表現した type を宣言し、json.Unmarshal で変換された値を突っ込むことができます。

一部定義するだけでも良いようです。

例えば Grafana で GET /api/search/ を実行した結果の json を処理したい場合を考えます。

https://grafana.com/docs/grafana/latest/http_api/folder_dashboard_search/

得られる json には、id、uid、title、・・・といくつかの値が得られますが、例えばその中で title と uid の値だけ拾いたいときは下記のような感じです。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type DashboardOverview struct {
    Title string `json:"title"`
    Uid   string `json:"uid"`
}

func main() {
    dashboardJson := Grafanaからjsonを取得する処理
    var dashboards []DashboardOverview
    if err := json.Unmarshal(dashboardJson, &dashboards); err != nil {
        log.Fatal(err)
    }
    fmt.Println(dashboards)
}

こうすると、変数 dashboards にダッシュボードの title と uid が配列で格納されます。

2. 子要素を文字列として扱う

json のある要素以下の内容をごっそり文字列で取り出したいときは、json.RawMessage という型を使えばよいみたいです。

例えば Grafana で GET /api/dashboards/uid/:uid を実行した結果の json を処理したい場合を考えます。

https://grafana.com/docs/grafana/latest/http_api/dashboard/#get-dashboard-by-uid

得られるデータの中に大きく dashboardmetadata があります。

dashboard の中の json だけ抽出したいときは下記のような感じです。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type DashboardDetail struct {
    Meta      json.RawMessage `json:"meta"`
    Dashboard json.RawMessage `json:"dashboard"`
}

func main() {
    detailJson := Grafanaからjsonを取得する処理
    var detail DashboardDetail
    if err := json.Unmarshal(detailJson, &detail); err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(detail.Dashboard))
}

こうすると、detail.Dashboarddashboard 要素の内容が入りますので、string に変換するなどして扱います。

3. ある特定の値だけピンポイントに操作する

ある特定の値だけ扱いたいときは、interface{} という箱に入れてしまえばよいみたいです。

型アサーションを使用すれば参照、更新できました。

例えばエクスポートした Grafana のダッシュボードの json のインポートを自動化することを考えます。対応する API は POST /api/dashboards/db ですが、これはダッシュボード定義の id の値が null なら Create、値が入っていたら Update となります。

https://grafana.com/docs/grafana/latest/http_api/dashboard/#create-update-dashboard

エクスポートした json には id が入った状態なので、新規環境にダッシュボードを追加する場合は何らかの方法で id を null にする必要があります。

id の定義は深い階層にもあるので、正規表現での置換は難しそうです。

この場合は json.Unmarshal して、一番浅い id の値を更新する処理を書くことになります。

下記のような感じです。

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

func main() {
    detailJson := Grafanaからjsonを取得する処理
    var jsonObj interface{}
    err := json.Unmarshal(detailJson, &jsonObj)
    if err != nil {
        log.Fatal(err)
    }
    jsonObj.(map[string]interface{})["id"] = nil

    jsonByteArray, _ := json.Marshal(jsonObj)

    fmt.Println(string(jsonByteArray))
}

これで id を null にできたので、API でダッシュボードを作成することができます。 値の更新が終わったら、json.Marshal をすれば byte 配列にすることができますよ。

まとめ

これだけ押さえておけば json を自在に扱える気になってきました。

ちなみに標準ライブラリ以外のものを使えばもっと楽なようなので、制約がない場合はいろいろ調べてみると良いと思いました。