Goのjson.Unmarshalについてのメモ
jsonを文字列をgoの構造体にするときにお世話になるjson.Unmaarshal
を使い、json文字列ではネストした構造のデータを基本型だけをフィールドとしてもつ構造体にunmarshalする方法についてのメモ。
結論
先に結論を書いておくと、以下の例のように変換先の構造体でUnmarshalメソッドを実装すればいいです。
package main import ( "encoding/json" "fmt" "log" ) type Person struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` Age int64 `json:"age"` } func (s *Person) UnmarshalJSON(data []byte) error { type Name struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } type person struct { Name Name `json:"name"` Age int64 `json:"age"` } var tmp person if err := json.Unmarshal(data, &tmp); err != nil { log.Fatal(err) } *s = Person{ FirstName: tmp.Name.FirstName, LastName: tmp.Name.LastName, Age: tmp.Age, } return nil } func main() { data := []byte(`{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 20 }`) var p Person json.Unmarshal(data, &p) bytes, _ := json.Marshal(p) fmt.Printf("%s", bytes) }
https://play.golang.org/p/m5dH2gp8OGU
実行結果はこちら。
{"firstname":"first","lastname":"last","age":20}
Unmarshalの仕組み
Unmarshal関数でJSONをGoの構造体に変換するときは、以下のようなルールで変換が行われます。
詳しいルールはドキュメントを参照してください。
https://golang.org/pkg/encoding/json/#Unmarshal
- Unmarshalに引数として渡した構造体のフィールド名またはjsonタグがJSONのキー名と一致した場合に、フィールドの値としてJSONの値を格納する。
- 構造体のフィールド名またはjsonタグに一致しないフィールドは無視される。
シンプルなJSONをunmarshalする
ネストがないシンプルなJSONをUnmarshlするのはとっても簡単。
JSONのキー名に対応するフィールド名を持つ構造体を定義してあげて、構造体のポインターをjson.Unmarshal
に渡して上げればいいです。
import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name"` Age int64 } func main() { data := []byte(`{ "name" : "first last", "age" : 20 }`) var person *Person if err := json.Unmarshal(data, &person); err != nil { panic(err) } fmt.Printf("%+v", person) }
実行結果
&{Name:first last Age:20}
すごく簡単!!
ネストがあるJSONをunmarshlする
JSONの中にネストがある場合には少しだけ複雑で、jsonタグやフィールド名ではネストが表現できないので、
ネストの中身に対応した構造体を定義して(下記の例だとName型)、その型をフィールドとしてもつ構造体のポインターをjson.unmarshal
に渡す必要があります。
import ( "encoding/json" "fmt" ) type ( Name struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } Person struct { Name `json:"name"` Age int64 `json:"age"` } ) func main() { data := []byte(`{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 27 }`) var p Person json.Unmarshal(data, &p) bytes, _ := json.Marshal(p) fmt.Printf("%s", bytes) }
実行結果
{"name":{"firstname":"first","lastname":"last"},"age":27}
ネストされたデータがあるJSONをネストがないようにunmarshalする(flatten)
unmarshalを行うだけなら上記の方法で特に困ることはないのですが、unmarshalした後の処理でデータにネストがあると扱いづらい時もあると思います。
要はこれを
{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 27 }
この構造体にしたい。
type Person struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` Age int64 `json:"age"` }
JSONの中にネストがある場合には、ネストしたデータに対応する構造体を定義する必要があるので、先程の例のような変換を行ったあとに、基本型だけをフィールドとして持つ構造体にマッピングさせます。
import ( "encoding/json" "fmt" ) type ( Name struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` } Person struct { Name `json:"name"` Age int64 `json:"age"` } FlatPerson struct { FirstName string `json:"firstname"` LastName string `json:"lastname"` Age int64 `json:"age"` } ) func main() { data := []byte(`{ "name": { "firstname" : "first", "lastname" : "last" }, "age": 27 }`) var p Person json.Unmarshal(data, &p) // unmarshal後に別の構造体にマッピングしている f := FlatPerson{ FirstName: p.FirstName, LastName: p.LastName, Age: p.Age, } bytes, _ := json.Marshal(f) fmt.Printf("%s", bytes) }
実行結果
{"firstname":"first","lastname":"last","age":27}
やりたかったことはできたが、unmarshal後に毎回マッピングするのは面倒、unmarshal時にやってしまいたいです。
json.Unmarshal
関数は第二引数として渡された型がUnmarshaler
インターフェイスを実装している場合には、その型のUnmarshalJSONを呼び出します。
Unmarshaler
インターフェイスはUnmarshalJSON
だけを持つシンプルなインターフェイスなので、json.Unmarshalの第二引数として渡したい型にUnmarshalJSON
を実装してあげるだけでいいです。
type Unmarshaler interface { UnmarshalJSON([]byte) error }
ということで、第二引数に渡している型にUnmarshalJSON
を実装したものが、一番最初に結論として記載しているコードになります。