新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

Go言語でJSONを読み書きする方法メモ

Go言語は、シンプルな関数を組み合わせてプログラミングをすることが多く、また、関数毎にエラーチェックが発生するので、全般的にコードが長くなりがちです。

ちょっとした事をしたい時でも、コードをそれなりに書く必要があるのですが、毎回ゼロから書くのは面倒なので、よくやる処理を自分コピペ用にメモしておこうと思います。

ここでは、Go言語で、JSONを読み書きする方法の自分用メモを記載しました。

構造体の要素名は大文字

JSONを扱う際、構造体からJSONに変換したり、JSONから構造体に変換したり、構造体を介してやり取りすることが多いです。

Go言語では、構造体の要素名が大文字で始まらないと、他のパッケージにその構造体の要素が公開されません。

なので、jsonパッケージを使ってJSONを処理する際、やり取りする構造体の要素名は、大文字で始まらないといけません。

しかし、JSONデータは、必ずしも要素名が大文字で始まってるとは限りません。

そこで、構造体の要素名は大文字で始まるようにしておきながら、それがJSONのどの要素名を示すかを、構造体のタグで示すようにします。

type colStruct struct {
    Name string    `json: "name"`
    Count int      `json: "count"`
}

JSON読み込み(構造体)

データを受け取る構造体を用意して、json.Unmarshal()に、JSONデータと構造体のポインターを渡すと、構造体にデータが格納されます。

data := `{"x":123, "y":"ABC"}`

var a struct {
    X int    `json:"x"`
    Y string `json:"y"`
}

err := json.Unmarshal([]byte(data), &a)
if err != nil {
    return err
}

fmt.Printf("%+v\n", a) // {X:123 Y:ABC}

JSON読み込み(インターフェース)

JSONデータの内容が未知の場合、構造体ではなく、インターフェースのポインターを渡すことにより、任意のJSONデータをインターフェースで取得できます。

インターフェースからデータを取り出すには、型アサーションを使用します。

取り出したデータもインターフェースなので、入れ子になったデータを取り出すには、型アサーションを繰り返していくことになります。

data := `{"x":123, "y":"ABC", "z":{"i":123, "j":456}}`

var a interface{}

err := json.Unmarshal([]byte(data), &a)
if err != nil {
    return err
}

fmt.Printf("%#v\n", a)
// map[string]interface {}{"Z":map[string]interface {}{"a":123, "b":456}, "x":123, "y":"ABC"}

b := a.(map[string]interface{})["z"]
z := b.(map[string]interface{})

fmt.Println(z["i"]) // 123
fmt.Println(z["j"]) // ABC

JSON出力(構造体)

json.Marshal()に出力したい構造体を渡すと、JSONにしてくれます。

a := struct {
    X int    `json:"x"`
    Y string `json:"y"`
}{
    X: 123,
    Y: "ABC",
}

json, err := json.Marshal(a)
if err != nil {
    return err
}

fmt.Printf("%+v\n", string(json)) // {"x":123,"y":"ABC"}

JSON出力(スライス・マップ・インターフェース)

json.Marshal()は内部的には全てインターフェースで処理しているので、構造体に限らず、スライス・マップ・インターフェースがごちゃまぜになったデータでも、 良しなにJSONにしてくれます。

z := struct {
    I int `json:"i"`
    J int `json:"j"`
}{
    I: 123,
    J: 456,
}

a := map[string]interface{}{
    "x": 123,
    "y": "ABC",
    "z": z,
}

json, err := json.Marshal(a)
if err != nil {
    return err
}

fmt.Printf("%+v\n", string(json)) // {"x":123,"y":"ABC","z":{"i":123,"j":456}}

ストリーム処理

jsonio.Readerio.Writerに直接読み書きできるので、一旦メモリにデータを取り込む必要はありません。

ストリームJSON読み込み

var a struct {
    X int    `json:"x"`
    Y string `json:"y"`
}

f, err := os.Open("data_input.json") // {"x":123 "y":"ABC"}
if err != nil {
    return err
}
defer f.Close()

err = json.NewDecoder(f).Decode(&a)
if err != nil {
    return err
}

fmt.Printf("%+v\n", a) // {X:123 Y:ABC}

ストリームJSON出力

a := map[string]interface{}{
    "x": 123,
    "y": "ABC",
}

f, err := os.Create("data_output.json")
if err != nil {
    return err
}
defer f.Close()

err = json.NewEncoder(f).Encode(a)
if err != nil {
    return err
}

感想など

記事を入力して読み直してみたのですが、エラー処理コードが思考を中断させるため、コードの可読性が良くないですね。

Go言語には「例外」がないので、エラー処理は必須になり省略できないので、Go言語にも「例外」があった方がいいみたいな話を聞いたりします。

Go言語は、本当は行末にセミコロンが必要な言語なのですが、書かなくてもコンパイラが良しなに補完してくれます。

そのように、Go言語には、言語仕様ではなく、コンパイラ解釈で言語を書きやすく、読みやすくするといったことがあります。

なので、Go言語に「例外」を導入しなくてもいいので

「関数の戻り値をerr変数で受けると、コンパイラが自動で次行にif err != nil { return err }を挿入する」

みたいなコーディングが楽になる機能が入るといいのになぁ。

関連カテゴリー記事

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com

www.kwbtblog.com