[インデックス 13400] ファイルの概要
このコミットは、Go言語の標準ライブラリ encoding/json
パッケージに Number
型を追加するものです。これにより、JSONの数値リテラルを float64
としてパースする代わりに、元の文字列形式(精度と書式を保持したまま)で扱うことが可能になります。これは、特に金融データやIDなど、数値の精度が重要となる場面で、浮動小数点数変換による誤差を避けたい場合に有用です。
コミット
commit b7bb1e32d84f45794b2106daa5c908bcb390461e
Author: Jonathan Gold <jgold.bg@gmail.com>
Date: Mon Jun 25 17:36:09 2012 -0400
encoding/json: add Number type
Number represents the actual JSON text,
preserving the precision and
formatting of the original input.
R=rsc, iant
CC=golang-dev
https://golang.org/cl/6202068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b7bb1e32d84f45794b2106daa5c908bcb390461e
元コミット内容
このコミットは、Goの encoding/json
パッケージに Number
型を導入することを目的としています。この新しい型は、JSONドキュメント内の数値を、float64
のような浮動小数点型に変換するのではなく、元のテキスト表現のまま保持します。これにより、数値の精度や書式(例: "1.0" と "1"、"1e+10" と "10000000000" の違い)が維持されます。
変更の背景
JSON (JavaScript Object Notation) は、データ交換のための軽量なデータ形式であり、数値型をサポートしています。しかし、多くのプログラミング言語では、JSONの数値をネイティブの浮動小数点型(Goでは float64
)にマッピングします。この変換は、特に大きな整数や小数点以下の桁数が多い数値の場合、浮動小数点数の精度限界により、元の数値が持つ精度や書式が失われる可能性があります。
例えば、JavaScriptの Number
型はIEEE 754倍精度浮動小数点数で表現されるため、9007199254740991
(2^53 - 1) を超える整数や、非常に小さい/大きい浮動小数点数は正確に表現できません。Goの float64
も同様の制約を持ちます。
この問題に対処するため、このコミットでは Number
型を導入しました。これにより、開発者はJSONの数値を文字列として読み込み、必要に応じて float64
や int64
に変換するか、あるいは元の文字列形式をそのまま利用するかを選択できるようになります。これは、特に以下のようなシナリオで重要です。
- 金融アプリケーション: 厳密な精度が求められる通貨や取引量。
- IDやハッシュ値: 非常に大きな整数値で、浮動小数点数に変換すると精度が失われる可能性があるもの。
- データの一貫性: JSONドキュメントの元の書式を保持する必要がある場合。
前提知識の解説
JSONの数値表現
JSONでは、数値は整数または浮動小数点数として表現されます。JSONの仕様では、数値の精度や範囲については特に規定されていませんが、通常はIEEE 754倍精度浮動小数点数にマッピングされることが多いです。
Go言語の数値型
float64
: Goにおける倍精度浮動小数点数型です。JSONの数値をデフォルトでこの型にデコードします。int64
: Goにおける64ビット符号付き整数型です。
encoding/json
パッケージのデフォルトの挙動
Goの encoding/json
パッケージは、デフォルトではJSONの数値を float64
型にデコードします。例えば、json.Unmarshal
を使用して interface{}
にデコードした場合、数値は float64
として扱われます。
package main
import (
"encoding/json"
"fmt"
)
func main() {
data := []byte(`{"value": 12345678901234567890.12345}`)
var result map[string]interface{}
json.Unmarshal(data, &result)
fmt.Printf("Type: %T, Value: %v\n", result["value"], result["value"])
// 出力: Type: float64, Value: 1.2345678901234568e+19
// 精度が失われていることがわかる
}
strconv
パッケージ
strconv
パッケージは、文字列と基本的なデータ型(数値、真偽値など)との間の変換を提供します。
strconv.ParseFloat(s string, bitSize int)
: 文字列s
を浮動小数点数にパースします。bitSize
は結果の浮動小数点数のビット幅(32または64)を指定します。strconv.ParseInt(s string, base int, bitSize int)
: 文字列s
を整数にパースします。base
は基数(例: 10進数なら10)、bitSize
は結果の整数のビット幅を指定します。
技術的詳細
このコミットの主要な変更点は以下の通りです。
-
Number
型の導入:src/pkg/encoding/json/decode.go
にtype Number string
が追加されました。これは、JSONの数値リテラルを文字列として保持するための型です。 -
Number
型のメソッド:Number
型には以下のメソッドが追加されました。String() string
:Number
の元の文字列リテラルを返します。Float64() (float64, error)
:Number
をfloat64
に変換します。内部でstrconv.ParseFloat
を使用します。Int64() (int64, error)
:Number
をint64
に変換します。内部でstrconv.ParseInt
を使用します。
-
decodeState
構造体へのuseNumber
フィールドの追加:src/pkg/encoding/json/decode.go
のdecodeState
構造体にuseNumber bool
フィールドが追加されました。このフラグがtrue
の場合、JSONの数値はfloat64
ではなくNumber
型としてデコードされます。 -
Decoder.UseNumber()
メソッドの追加:src/pkg/encoding/json/stream.go
にfunc (dec *Decoder) UseNumber()
メソッドが追加されました。このメソッドを呼び出すことで、Decoder
が数値をNumber
型としてデコードするように設定できます。 -
convertNumber
関数の導入:src/pkg/encoding/json/decode.go
にconvertNumber(s string) (interface{}, error)
関数が追加されました。この関数はdecodeState.useNumber
の設定に基づいて、与えられた数値文字列s
をNumber
型またはfloat64
型に変換して返します。 -
デコードロジックの変更:
decodeState.literalStore
およびdecodeState.literalInterface
関数内で、数値のデコード処理がstrconv.ParseFloat
から新しく導入されたd.convertNumber
を使用するように変更されました。これにより、useNumber
フラグの状態に応じてfloat64
またはNumber
が返されるようになります。literalStore
関数では、reflect.Value
の型がNumber
型(numberType
)である場合に、直接文字列をセットするロジックが追加されました。
-
エンコードロジックの変更:
src/pkg/encoding/json/encode.go
のencodeState.reflectValueQuoted
関数内で、reflect.String
型の処理にNumber
型の特別なハンドリングが追加されました。これにより、Number
型の値は、その文字列リテラルがそのままJSONの数値としてエンコードされるようになります。空のNumber
の場合は "0" としてエンコードされます。 -
テストケースの追加:
src/pkg/encoding/json/decode_test.go
に、Number
型のデコード、エンコード、およびアクセサメソッドの動作を検証するための新しいテストケースが多数追加されました。特に、useNumber
フラグの有無によるデコード結果の違いや、Number
型のInt64()
およびFloat64()
メソッドの挙動が詳細にテストされています。
コアとなるコードの変更箇所
src/pkg/encoding/json/decode.go
- L139-147:
Number
型の定義と、String()
,Float64()
,Int64()
メソッドの追加。// A Number represents a JSON number literal. type Number string // String returns the literal text of the number. func (n Number) String() string { return string(n) } // Float64 returns the number as a float64. func (n Number) Float64() (float64, error) { return strconv.ParseFloat(string(n), 64) } // Int64 returns the number as an int64. func (n Number) Int64() (int64, error) { return strconv.ParseInt(string(n), 10, 64) }
- L163:
decodeState
構造体にuseNumber bool
フィールドを追加。useNumber bool
- L596-605:
convertNumber
関数の追加。// convertNumber converts the number literal s to a float64 or a Number // depending on the setting of d.useNumber. func (d *decodeState) convertNumber(s string) (interface{}, error) { if d.useNumber { return Number(s), nil } f, err := strconv.ParseFloat(s, 64) if err != nil { return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0)} } return f, nil }
- L607:
numberType
変数の追加。var numberType = reflect.TypeOf(Number(""))
- L666-670:
literalStore
関数内でNumber
型へのデコードをサポート。case reflect.String: if v.Type() == numberType { v.SetString(s) break }
- L674-678, L828-832:
literalStore
およびliteralInterface
関数内でd.convertNumber
を使用するように変更。// literalStore - n, err := strconv.ParseFloat(s, 64) + n, err := d.convertNumber(s) if err != nil { - d.saveError(&UnmarshalTypeError{"number " + s, v.Type()}) + d.saveError(err) break } // literalInterface - n, err := strconv.ParseFloat(string(item), 64) + n, err := d.convertNumber(string(item)) if err != nil { - d.saveError(&UnmarshalTypeError{"number " + string(item), reflect.TypeOf(0.0)}) + d.saveError(err) }
src/pkg/encoding/json/encode.go
- L39: コメントの更新。
// Floating point, integer, and Number values encode as JSON numbers.
- L314-320:
reflectValueQuoted
関数内でNumber
型のエンコードをサポート。case reflect.String: if v.Type() == numberType { numStr := v.String() if numStr == "" { numStr = "0" // Number's zero-val } e.WriteString(numStr) break }
src/pkg/encoding/json/stream.go
- L29-30:
Decoder.UseNumber()
メソッドの追加。// UseNumber causes the Decoder to unmarshal a number into an interface{} as a // Number instead of as a float64. func (dec *Decoder) UseNumber() { dec.d.useNumber = true }
src/pkg/encoding/json/decode_test.go
- L24-40:
V
構造体、ifaceNumAsFloat64
、ifaceNumAsNumber
の追加。 - L56:
unmarshalTest
構造体にuseNumber bool
フィールドを追加。 - L68-71, L80-81:
unmarshalTests
にNumber
型およびuseNumber
を使用したテストケースを追加。 - L145-150:
TestMarshalNumberZeroVal
テストの追加。 - L161-164, L174-177:
TestUnmarshal
関数内でDecoder.UseNumber()
を呼び出すロジックを追加。 - L208-240:
numberTests
とTestNumberAccessors
テストの追加。
コアとなるコードの解説
Number
型とそのメソッド
Number
型は単なる string
のエイリアスですが、JSONの数値リテラルをその文字列形式で保持するというセマンティクスを持ちます。追加された Float64()
と Int64()
メソッドは、必要に応じてこの文字列を数値型に変換するための便利なユーティリティを提供します。これにより、元の精度を保持しつつ、数値としての操作も可能になります。エラーハンドリングも含まれており、変換に失敗した場合にはエラーが返されます。
decodeState.useNumber
と Decoder.UseNumber()
decodeState
はJSONデコード処理の状態を管理する内部構造体です。useNumber
フィールドは、デコード時に数値を float64
として扱うか、Number
型として扱うかを制御するフラグです。
Decoder.UseNumber()
メソッドは、ユーザーがこの useNumber
フラグを true
に設定するための公開APIです。これにより、json.NewDecoder
を使用してストリームからJSONをデコードする際に、数値の扱いをカスタマイズできるようになります。
convertNumber
関数
この関数は、デコード中に数値リテラル(文字列として取得される)を適切なGoの型に変換する中心的なロジックをカプセル化しています。useNumber
が true
であれば Number(s)
を返し、そうでなければ strconv.ParseFloat
を使って float64
に変換します。これにより、デコード処理の複数の場所で同じロジックを再利用し、コードの重複を避けています。
デコードロジックの変更 (literalStore
, literalInterface
)
これらの関数は、JSONのプリミティブ値(文字列、数値、真偽値、null)をGoの対応する型にデコードする役割を担っています。変更点では、数値のデコード部分が d.convertNumber
を呼び出すように統一されました。これにより、useNumber
の設定がデコード結果に反映されるようになります。
また、reflect.String
型のデコード処理において、ターゲットの型が Number
型である場合に、直接文字列をセットする特殊なケースが追加されました。これは、Number
が string
のエイリアスであるため、string
としてデコードされる可能性があるためです。
エンコードロジックの変更 (reflectValueQuoted
)
この関数はGoの値をJSONにエンコードする役割を担っています。Number
型のエンコード処理が追加されたことで、Number
型の値は、その内部の文字列がそのままJSONの数値リテラルとして出力されるようになります。これにより、デコード時に保持された精度と書式が、再エンコード時にも維持されることが保証されます。空の Number
が "0" としてエンコードされるのは、JSONの数値として有効な表現を提供するためです。
関連リンク
- Go CL (Code Review) 6202068: https://golang.org/cl/6202068
参考にした情報源リンク
- Go言語
encoding/json
パッケージ公式ドキュメント: https://pkg.go.dev/encoding/json - Go言語
strconv
パッケージ公式ドキュメント: https://pkg.go.dev/strconv - JSON (JavaScript Object Notation) 公式サイト: https://www.json.org/json-en.html
- IEEE 754 浮動小数点数標準 (Wikipedia): https://ja.wikipedia.org/wiki/IEEE_754
- Go言語におけるJSONの数値の扱いに関する議論 (Stack Overflowなど): https://stackoverflow.com/questions/tagged/go+json+number (一般的な情報源として)
- Go言語の
encoding/json
パッケージのソースコード (GitHub): https://github.com/golang/go/tree/master/src/encoding/json (変更点の詳細な確認のため)