dip Engineer Blog

Engineer Blog
ディップ株式会社のエンジニアによる技術ブログです。
弊社はバイトル・はたらこねっとなど様々なサービスを運営しています。

go-json/optパッケージを公開しました

皆さんはじめまして、ディップ株式会社の町田です。 R&D推進室でGoを使った開発とその支援をしています。

今回はとあるGoのパッケージを公開しましたのでその紹介をしたいと思います。 Goの経験者向けの内容になってしまっていますが、最後までお付き合いいただけると幸いです。

go-json/opt

今回公開したパッケージは以下になります。 github.com

こんなことができます

JSONをUnmarshalした構造体から、元のJSONのフィールド有無を判定できるようになります。

例えば、以下のリクエストをJSONで受けるユーザー更新APIがあったとします。

項目名 必須
user_id 数値
user_name 文字列 -
age 数値 -

リクエストを受ける構造体を基本の型で定義すると

type Request struct {
    UserID   int32   `json:"user_id"`
    UserName *string `json:"user_name"`
    Age      *int32  `json:"age"`
}

JSONフィールドの有無を判別することができなくなります。

var req Request
// user_nameフィールド有り、値がnullの場合
_ = json.Unmarshal([]byte(`{"user_id": 1, "user_name": null, "age": 25}`), &req)
fmt.Println(req.UserID)   // 1
fmt.Println(req.UserName) // nil
fmt.Println(req.Age)      // 25

// user_nameフィールド無しの場合
    _ = json.Unmarshal([]byte(`{"user_id": 1, "age": 25}`), &req)
fmt.Println(req.UserID)   // 1
fmt.Println(req.UserName) // nil
fmt.Println(req.Age)      // 25

更新対象の項目がJSONにフィールドが設定されているものに限定する(差分のみが渡ってくる)仕様の場合、困ったことになります。 前者はuser_nameをnullに更新し、後者はuser_nameは更新対象外とする必要がありますが、 どちらもUserName=nilになってしまうため、更新対象か否かが判断できません。

optパッケージで解決できます

リクエストを受ける構造体でgo-json/optパッケージを利用します

import "github.com/dip-dev/go-json/opt"

type Request struct {
    UserID   int32      `json:"user_id"`
    UserName opt.String `json:"user_name"`
    Age      opt.Int32  `json:"age"`
}

optパッケージではHasKeyメソッドを参照することでフィールドの有無を判定することができます。

var req Request
// user_nameフィールド有り、値がnullの場合
_ = json.Unmarshal([]byte(`{"user_id": 1, "user_name": null, "age": 25}`), &req)
fmt.Println(req.UserID)            // 1
fmt.Println(req.UserName.Value())  // nil
fmt.Println(req.UserName.HasKey()) // true
fmt.Println(req.Age.Value())       // 25
fmt.Println(req.Age.HasKey())      // true

// user_nameフィールド無しの場合
_ = json.Unmarshal([]byte(`{"user_id": 1, "age": 25}`), &req)
fmt.Println(req.UserID)            // 1
fmt.Println(req.UserName.Value())  // nil
fmt.Println(req.UserName.HasKey()) // false
fmt.Println(req.Age.Value())       // 25
fmt.Println(req.Age.HasKey())      // true

フィールドが無い項目はHasKey()がfalseとなるため、処理分岐が可能となります。

おわりに

基本的な型は全て対応していますが、rune等の一部の型には対応していません。 詳細な使い方についてはREADMEを参照ください。

こちらのパッケージが少しでも皆さんの開発のお役に立てれば嬉しいです。