jsonex.go
概要
この記事は Go2 Advent Calendar 2019 の 19 日目の記事です。
Go 言語の標準パッケージ encoding/json の機能を拡張した jsonex.go というモジュールを作りましたので紹介します。
これはもともとは msgraph.go の一部である jsonx というパッケージでしたが、 他で使うこともあるかと思い、識別しやすい名前に変更し独立したモジュールとして作り直しました。
jsonex.go の使い方
jsonex.go に含まれる json パッケージは Go 1.13 の encoding/json パッケージをフォークして作られており、 次のようにインポートすれば encoding/json とまったく同じように利用できます。
import json "github.com/yaegashi/jsonex.go/v1"
この json パッケージを使うと、JSON オブジェクトの Marshal/Unmarshal において Go の構造体のフィールドで定義されていない key-value を失うことなく扱うことができます。
たとえば次のような Go の構造体 Base でフィールド A B を定義していたとして、
type Base struct {
A string
B int
}
Base 型の変数に対して、次のように構造体のフィールドで定義されていない key-value C D を含む JSON オブジェクトを encoding/json で json.Unmarshal すると、それらは失われてしまいます。
{"A":"123","B":123,"C":"123","D":123}
Go Playground で次のコードを実行してみてください (Run ボタンを押す)。
これに対して jsonex.go の json パッケージでは、 map[string]interface{} 型のフィールドを構造体に追加することで、 このような未定義の key-value をすべてキャッチできます。
未定義キャッチのマップは次の Extra 構造体のフィールド X のように jsonex:"true"
タグで指定します。
encoding/json との互換性も維持するため json:"-"
タグも同時に指定していることに注意してください。
type Extra struct {
A string
B int
X map[string]interface{} `json:"-" jsonex:"true"`
}
Extra 型の変数に対して、さきほどの JSON オブジェクトを jsonex.go で json.Unmarshal すると、 未定義フィールド key-value がマップ X に入った結果が得られます。
逆に、マップ X に key-value が入った Extra 型変数を json.Marshal すると、 他のフィールドと合わせて適切な JSON オブジェクトを出力します。
jsonex.go は構造体の埋め込みと組み合わせて使うこともできます。 例えば最初の構造体 Base に余計なフィールドを追加したくないが、未定義の key-value を失うことなく扱いたいときには、 次のように Base のポインタをラップする構造体 Wrap を使うことができます。
jsonex.go の実装
jsonex.go が解決しようとする未定義の key-value 問題については、これまで様々な解法が提案されてきました。 少し古いのですが、次の Stack Overflow の質問に対する回答にいくつかのアイディアがまとまっています。
msgraph.go の開発時に調査したかぎりでは、encoding/json との互換性を維持したければ 2 回 Unmarshal するか、 それがいやなら encoding/json の利用をあきらめて別のパッケージを使うか、といった状況に変わりはないようです。
結局、パフォーマンスのペナルティを避けつつ encoding/json 互換にしたかったのと、 Unmarshal だけでなく Marshal も正しく動くようにしたかったことから、 encoding/json をフォーク・改良して jsonx パッケージを作るという選択をしたのでした。
jsonex.go のリポジトリ をご覧になるとわかりますが、 encondig/json の encode.go と decode.go に対して結構な量の改造を加えています。
このような標準パッケージのフォークには当初抵抗があったのですが、 godoc.org などを検索をしてみるとけっこうフォークしている人が多かったので、まあいいかということにしました。 あまり複雑な改造をすると encoding/json のアップデートやバグ・セキュリティの事案発生が恐ろしいのですが、 がんばって変更に追随していきたいと思います。
まとめ
encoding/json を拡張する jsonex.go モジュールを紹介しました。
この記事でお見せしたように Go Playground で手軽に試すことができますので、 いろいろ Marshal/Unmarshal して遊んでみてください。そこでバグと思われる挙動を見つけたら、 ぜひ GitHub の Issues に報告をお願いします。