バグ解消法、お役立ち情報など

GoでJSONのキー存在チェックを行う方法

logo

はじめに

GoでJSONを扱うときに、以下のようなケースがあります。

  • キーが存在するかチェックし、しない場合はエラーにしたい

具体的には以下のようなケースです。

  • viewsキーが存在しないJSONの場合、エラーにする
  • viewsキーさえ存在していれば、バリューは null でもよい
// ↓ OK { "id": 1 "views": 1, } // ↓ OK { "id": 1 "views": null, } // ↓ エラーにしたい! {"id": null}

こちらのブログを参考にさせていただきました。

How to Check JSON Key not-set or Null in Golang Unmarshalling

どうやるの?

下記のように実装します。

Go
package main import ( "encoding/json" "fmt" ) // json unmarshal の際に仕様する構造体 type OptionalInt struct { Value int // JSONのバリュー isValid bool // JSONの形式および型が定義通りの場合は true isSet bool // キーが存在する場合は true } // views キーをもつ構造体 type User struct { Views OptionalInt `json:"views"` } func main() { // サンプルJSON body1 := `{"views": 5}` body2 := `{"views": null}` body3 := `{"age": 5}` // views キーが存在しない! // User構造体を生成 var currentUser1 User // JSONをUser構造体に注入 if err := json.Unmarshal([]byte(body1), &currentUser1); err != nil { fmt.Println(err) } fmt.Printf("isSet: %v, isValid: %v, value: %v\n", currentUser1.Views.isSet, currentUser1.Views.isValid, currentUser1.Views.Value) var currentUser2 User if err := json.Unmarshal([]byte(body2), &currentUser2); err != nil { fmt.Println(err) } fmt.Printf("isSet: %v, isValid: %v, value: %v\n", currentUser2.Views.isSet, currentUser2.Views.isValid, currentUser2.Views.Value) var currentUser3 User if err := json.Unmarshal([]byte(body3), &currentUser3); err != nil { fmt.Println(err) } fmt.Printf("isSet: %v, isValid: %v, value: %v\n", currentUser3.Views.isSet, currentUser3.Views.isValid, currentUser3.Views.Value) } // json.Unmarshal をカスタマイズ func (i *OptionalInt) UnmarshalJSON(bytes []byte) error { i.isSet = true if string(bytes) == "null" { i.isValid = false return nil } var value int if err := json.Unmarshal(bytes, &value); err != nil { return err } i.isValid = true i.Value = value return nil }

Go Playground - The Go Programming Language

上記のプログラムを実行すると下記のような結果になります。

isSet: true, isValid: true, value: 5 isSet: true, isValid: false, value: 0 isSet: false, isValid: false, value: 0

isSet は、キーが存在する場合に true になります。

isValid は、JSONの形式および型が定義通りの場合に true になります。

value は、JSONのバリューが格納されます。

それぞれの行が、サンプルJSONの1 から 3行目に対応しています。

ここで注目すべきは3行目です。

isSetfalse が格納されています!

3行目のJSONには views キーが存在しないので、想定通りの存在チェックができていることがわかります。

あとは、isSet が false の場合はエラーを返すような処理を記述すれば完成です!

例えば下記のように実装できます。

go
if currentUser3.Views.isSet == false { // もし views キーが存在しない場合は 500 エラーにする http.Error(w, "views キーが存在しません", http.StatusInternalServerError) }

おわりに

今回はGoでJSONのキー存在チェックを行う方法をお伝えしました。

最後に、そもそもなぜこんな面倒な実装をする必要があるのかについてお伝えしておきます。

Goは静的型付き言語なので、JSONのキーが存在しない場合は、構造体の初期値でバリューを設定してしまう、という挙動になります。

例えば先の例で使用したJSONをそのまま処理すると、下記のような結果になります。

json
{5} {0} {0}

こうなると、例えばLaravelでいうところの「present」のようなチェックができなくなります。

present

フィールドが存在していることをバリデートしますが、存在していれば空を許します。

バリデーション 8.x Laravel

キーが存在していない場合は、Go側で値をセットしてしまう、ということです。

というわけで、キーの存在チェックをするためには、わざわざ Unmarshal のカスタマイズをする必要があった、というわけです。

参考になれば幸いです!

バグ解消法、お役立ち情報など