GoとPerlでJSONを扱う時に数値型が文字列型になる場合の対処
いつもライブ配信プラットフォーム『SHOWROOM』をご利用いただきありがとうございます。開発部の横山です。SHOROOMでは引き続きバックエンドシステムのプログラムをPerlからGo言語に移行しております(計画の詳細)。
今回はPerlからGo言語に移行するにあたり躓きポイントを紹介します。Go言語での細かい実装上の内容になります。
数値型のつもりが文字列型に!?
PerlとGo言語でJSONなどのシリアライズされたデータを共有するときに注意するときがあります。その一つにPerlでセットしたInt型の値が油断しているとString型の数値文字列になるという現象です。
{
"num": 3
}
{
"num": "3"
}
もちろんPerl側で型を明示した上でセットすれば問題ないかと思いますが、Perlで書き込んでPerlで読み込む場合は型をよしなに推論してくれるのでどこで発生しているのか分かりにくい側面もあります。
GO言語で文字列型数値にも対応したJSONシリアライズ
文字列数値型の場合、下記のような通常の型宣言ではデシリアライズに失敗しますね。
type ExampleType struct {
Num int `json:"num"`
}
func decode() {
var val ExampleType
err := json.UnMarshal([]byte(jsonString), &val)
}
文字列型数値のデシリアライズにも対応した型を導入する
下記のようにint型を基にした`StringInt`という型を導入します。StringInt型に`UnmarshalJSON([]byte) error`という関数を定義します。この関数の中で、Int型の場合とString型の場合の双方に対応した処理を書きます。
type StringInt int
func (s *StringInt) UnmarshalJSON(data []byte) error {
// 通常の数値型としての処理
var num int
if err := json.Unmarshal(data, &num); err == nil {
*s = StringInt(num)
return nil
}
// もしダメだったら、文字列型数値としての処理
var str string
err := json.Unmarshal(data, &str)
if err == nil {
n, err := strconv.Atoi(str)
if err != nil {
return err
}
*s = StringInt(n)
return nil
}
return err
}
これを利用することで、JSON内の値が数値の文字列のどちらであっても対応できます。
type ExampleType struct {
Num StringInt `json:"num"`
}
func decode() {
var val ExampleType
err := json.UnMarshal([]byte(jsonString), &val)
}
MsgPackの対応も
JSONだけでなくmessage packのデシリアライズも同様に対応できます。下記の`UnmarshalMsgpack([]byte) error `がそれにあたります。これでStringInt型はJSONでもMsgpackのデシリアライズにも対応できるようになりました。
func (s *StringInt) UnmarshalMsgpack(data []byte) error {
var num int
if err := msgpack.Unmarshal(data, &num); err == nil {
*s = StringInt(num)
return nil
}
var str string
err := msgpack.Unmarshal(data, &str)
if err == nil {
n, err := strconv.Atoi(str)
if err != nil {
return err
}
*s = StringInt(n)
return nil
}
return err
}
まとめ
JSON(とmsgpack)のデシリアライズをする際に、数値型がいつのまにか文字列型数値に変わってしまった場合に、Go言語でどちらの型であってもデシリアライズする方法を紹介しました。