平常運転

アニソンが好き

過去記事とかは記事一覧で見れます

Go で JSON を扱うにあたっての個人的なメモ

個人的なメモです。というのは全てにおいて予防線として扱われる。

omitempty

JSON tag につける omitempty は、 JSON の Marshal 時に参照される。 empty value だった際にそのフィールドがまるごとスキップされる。

The "omitempty" option specifies that the field should be omitted from the encoding if the field has an empty value, defined as false, 0, a nil pointer, a nil interface value, and any empty array, slice, map, or string.

https://golang.org/pkg/encoding/json/#Marshal

(なので、 UnMarshal にしか使わないような構造体の JSON tag につけても意味はない)

Unmarshal 時のフィールド名の対応

JSON のあるフィールドが JSON のどのフィールドに対応づけられるかのルール:

How does Unmarshal identify the fields in which to store the decoded data? For a given JSON key "Foo", Unmarshal will look through the destination struct's fields to find (in order of preference):

  • An exported field with a tag of "Foo" (see the Go spec for more on struct tags),
  • An exported field named "Foo", or
  • An exported field named "FOO" or "FoO" or some other case-insensitive match of "Foo".
https://blog.golang.org/json-and-go

つまり、(Unmarshal する都合においてのみ言うと)、JSON 上で hogeHoge というフィールドのものを構造体の HogeHoge や Hogehoge などにマッピングしたい場合、 JSON タグはつけなくてもマッピングされる。
逆に、この Case-Insensitive match を無効にするオプション今のところないようなので、 Case-sensitive な厳格な JSON デコーダは実現できないことになる。今触ってるコードだとこれが結構キツいんだけどどうしようか悩んでいる。諦める???

未知のフィールドを除外する

func (*Decoder) DisallowUnknownFields() を使う。

DisallowUnknownFields causes the Decoder to return an error when the destination is a struct and the input contains object keys which do not match any non-ignored, exported fields in the destination.

https://golang.org/pkg/encoding/json/#Decoder.DisallowUnknownFields

ちなみに前述したような Case-insensitive match が無効になる訳ではない。

missing key

package main

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

func main() {
	var data struct {
		Name      string   `json:"name"`
		Memo      string   `json:"memo"`
		Nicknames []string `json:"nicknames"`
	}
	buffer := bytes.NewBufferString("{\"name\":\"tom\"}")
	decoder := json.NewDecoder(buffer)
	if err := decoder.Decode(&data); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(data)
}

https://play.golang.org/p/PtPrJd0TIE0

構造体の期待するフィールドが JSON 側になかったとき、デコーダは特にエラーにならずに empty value を自然と埋める。緩く扱うにはメリットだけれど、ユーザ入力をきちんとハンドリングしたい時にはちょっと困る気もする。

null as array

package main

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

func main() {
	var data struct {
		Name      string   `json:"name"`
		Nicknames []string `json:"nicknames"`
	}
	buffer := bytes.NewBufferString("{\"name\":\"tom\", \"nicknames\": null}")
	decoder := json.NewDecoder(buffer)
	if err := decoder.Decode(&data); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(data)
}

https://play.golang.org/p/go69_sBuVZ1

リストっぽいところに null を渡すと空のリストになる。上記の missing key と組み合わせると、ふつうにやると以下の3つは Unmarshal された構造体からは区別できない

  • null
  • []
  • (そもそもフィールドが渡されてこなかった)

2018/09/04 01:19: 複数のフィールドが同じタグを持っている

package main

import (
	"encoding/json"
	"fmt"
)

type Single struct {
	Name1 string `json:"name"`
	Name2 string `json:"name"`
}

type Inner1 struct {
	Name1 string `json:"name"`
}

type Inner2 struct {
	Name2 string `json:"name"`
}

type Inner3 struct {
	Name3 string `json:"name3"`
}

type OuterA struct {
	*Inner1
	*Inner2
}

type OuterB struct {
	*Inner1
	*Inner3
}

func main() {
	data := &Single{}
	if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &data); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(data)

	dataA := &OuterA{}
	if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &dataA); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(dataA.Inner1, dataA.Inner2)

	dataB := &OuterB{}
	if err := json.Unmarshal([]byte("{\"name\":\"tom\"}"), &dataB); err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(dataB.Inner1, dataB.Inner3)
}

https://play.golang.org/p/2Ovn3J7ChfQ

複数のフィールドにうっかり同じタグをつけてしまった場合、どちらにも Unmarshal されない。ちなみに playground つけてないけど Marshal もされない気がする。
上記の Playground の `Single` のように単一の struct にタグが複数ついてると go vet が怒ってくれるけれど、 Outer~ のように embedding だと go vet でも気付かないので難しい。(過去にこれでミスったことがある)