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))