l0w.dev

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 に報告をお願いします。