GoでJSONの数字/数値を扱う
JSONはデータの表現形式です。表現はできますが、項目があるかや、型が何であるかを検査する仕組みはありません。*1
そのため、システムや実装が分かれている場合に、数値を期待しているが、JSONの表現上、数値か数字かがずれていることが起き得ます。どういうことかというと、"numeric":1234
(数値)なのか、"numeric":"1234"
(数字)なのかが違う、または不定ということ。仕様の齟齬や、実装ミスなどで発生します。
動的型付け言語の場合、よしなに扱ってくれたりしますが、Goにそれをやられると型エラーが発生してしまいます。GoでJSONを読み込む場合、encoding/jsonパッケージのjson.Unmarshal()を使って、structに読み込む方法があります。しかし、stringで定義していたら、数値がNG、int系で定義していたら、数字がNGです。
type Num struct { Number string } num := Num{} err := json.Unmarshal([]byte(`{"number":1234}`), &num) // json: cannot unmarshal number into Go struct field Num.Number of type string
type Num struct { Number int64 } num := Num{} err := json.Unmarshal([]byte(`{"number":"1234"}`), &num) // json: cannot unmarshal string into Go struct field Num.Number of type int64
Goの場合、Unmarshalで読み込めないと、黒魔術的にJSONを解析する必要がある*2ので、Unmarshalで済ませたいのです。その際に、json.Numberを使います。
使い方は簡単で、数値数字不定になっている属性の型をjson.Numberにして、そのstructを使ってUnmarshalします。そうすると数字でも数値でもabcde
などの文字列でも、Unmarshal時点ではエラーになりません。型の不一致で、JSON全体の読み込みを止めなくて済みます。もちろん使う時に数値変換は必要で、Int64()
を使いますが、数字以外の文字列の場合はエラーになります。(ParseInt失敗するのと同様)
type Num struct { Number1 json.Number Number2 json.Number Number3 json.Number } num := Num{} // Unmarshalではエラーにならない err := json.Unmarshal([]byte(`{"number1":1111,"number2":"2222","number3":"abcde"}`), &num)
// 数値はエラーにならない num1, err := num.Number1.Int64() // 数字はエラーにならない num2, err := num.Number2.Int64() // 数字以外の文字はエラー // strconv.ParseInt: parsing "abcde": invalid syntax _, err = num.Number3.Int64()
json.Number使ったUnmarshal(playground)
補足:JSON出力する時のjson.Number
json.Numberは、Marshalすると、数値として出力されます。数字、数値どちらで読み込んでいても数値になります。数以外の文字列の場合は、Marshal時にエラーになります。このjson.Numberを使って読み込んで、そのままMarshalする場合は、この数値になる動きを意識しておく必要があります。
type Num struct { Number1 json.Number `json:"number1"` Number2 json.Number `json:"number2"` } num := Num{} // 数値、数字を読み込む err := json.Unmarshal([]byte(`{"number1":1111,"number2":"2222"}`), &num) // JSONに出力 j, err := json.Marshal(num) // 数値として出力される // {"number1":1111,"number2":2222} fmt.Println(string(j))