Goで JSONをパースしたい時のやつ

taka
8 min readApr 3, 2019

--

はじめに

JSONを静的型付けで動作させる場合、少し困る時があると思います。半年前まで動的型付けでプログラムをしていて、Goをやり始めてから何回か検索したことがあったのでまとめます。

GoでJSONをパースするには、JSONのオブジェクトをGoの構造体に適合させる必要があります。
Goの標準ライブラリで提供されている/ jsonパッケージは、JSONの操作に必要なすべての機能を提供しています。

どんな形のJSONでも、それをパースするためのスタンダードな方法は次のとおりになります。

import "encoding/json"myJSON:= `{"name":"hoge"}`// `&result`は解析結果を格納する変数のアドレスです
json.Unmarshal([]byte(myJSON)、&result)

JSONをパースする時、以下のような2つのパターンに遭遇すると思います1.構造化データ
これは、あなたが既にJSONの構造を知っているパターンです。

2.非構造化データ
これは、あなたがどんな構造のJSONなのかを知らないパターンです。

まずは簡単な構造化データのパターン

例えば、魚についての情報を記載したJSONがあるとします。
このJSONには、名前を表すnameプロパティと説明を表すdescriptionプロパティが存在します。

{
"name": "Tuna",
"description": "Swimming speed is fast"
}

このJSONをパースする場合、Go側でNameとDescriptionのフィールドを持つFish構造体を作成してください。

type Fish struct {
Name string
Description string
}

この時、フィールド名の先頭文字は大文字にしてください。
json.Unmarshal()がリフレクションを使用するので、エクスポートされていないの場合、読み書きすることができなくなります。

そしたら関数内に次のコードを追加してください。

myJSON := `{
"name": "Tuna",
"description": "Swimming speed is fast"
}`
var fish Fish
json.Unmarshal([]byte(myJSON), &fish)
fmt.Printf("Name: %s, Description: %s", fish.Name, fish.Description)

配列の場合

[
{
"name": "Tuna",
"description": "Swimming speed is fast"
},
{
"name": "Red snapper",
"description": "Auspicious fish"
}
]

配列の各要素は実際にはFish構造体と同じなので、Fish構造体の配列を作成するだけでパースできます。

myJSON := `[
{
"name": "Tuna",
"description": "Swimming speed is fast"
},
{
"name": "Red snapper",
"Auspicious fish": "auspicious"
}
]`
var fishes []Fish
json.Unmarshal([]byte(myJSON), &fishes)
fmt.Printf("Fishes : %+v", fishes)

埋め込みオブジェクトの場合

ここでJSONにdimensions という寸法を表した埋め込みオブジェクトを追加します。

{
"name": "Tuna",
"description": "Swimming speed is fast",
"dimensions": {
"size": 80,
"weight": 10
}
}

埋め込みdimensionsオブジェクトをGoでパースするには、新しくDimensions構造体を作成し、それをFish構造体に埋め込みます。

type Dimensions struct {
Size int
Weight int
}
type Fish struct {
Name string
Description string
Dimensions Dimensions
}

そしたら、前回と同じようにパースします。

myJSON := `{
"name": "Tuna",
"description": "Swimming speed is fast",
"dimensions": {
"size": 80,
"weight": 10
}
}`
var fish Fish
json.Unmarshal([]byte(myJSON), &fish)
fmt.Printf("Fishes : %+v", fish)

カスタムフィールド名の場合

パースするJSONのプロパティ名とGoの構造体のフィールド名が異なる場合、又は異なるようにしたい場合があると思います。

Go側でfishTypeをName、featureをDescriptionとしてマッピングしたい場合、struct fieldタグを利用します。

{
“fishType”: “Tuna”,
“feature”: “Swimming speed is fast”
}
type Fish struct {
Name string `json:"fishType"`
Description string `json:"feature"`
}

これで、JSONプロパティをどのフィールドにマップするかを明示的にコードに指示できます。

ちなみにstruct fieldタグはGoを学習していると結構遭遇します。(ORMとか)

非構造化データのパターン

JSONの構造やプロパティ名が不明な場合は、構造体を使用してデータをパースすることはできません。代わりにmapとinterface{}を使うことでこの問題を解決できます。

次のような形式のJSONがあったとします。

{
"fishs": {
// 魚が増減する可能性がある
"tuna":"Swimming speed is fast",
"redSnapper":"Auspicious fish",
},
"animals": "none"
}

魚に対するプロパティ名やプロパティ数が変化する可能性があり、それに伴ってJSONの構造も変わる可能性があるため、上記のJSONに対応することのできる構造体はありません。

これに対処するには、map[string]interface{}を使います。

myJSON := `{
"fishes": {
"tuna": "Swimming speed is fast",
"redSnapper": "Auspicious fish"
},
"animals": "none"
}`

var result map[string]interface{}
json.Unmarshal([]byte(myJSON), &result)
fish := result["fishes"].(map[string]interface{})for key, value := range fish {
fmt.Println(key, value.(string))
}

mapのkeyはJSONのプロパティに対応し、マッピングされたinterface {}はvalueに対応するため、値は任意の型にすることができます。mapは繰り返し処理することができるため、シンプルなforループでさまざまな数のkey、valueを捌けます。

まとめ

JSONデータを表すために構造体を使用できる場合は、それを使用したほうがいいと思います。なぜなら、構造体のほうが直感的に値を操作できるためバグを発生させにくいです。
マップを使用する唯一の理由は、JSONデータ内のキーとしての値の不確実な性質ゆえに構造体を使用できない場合です。

--

--