[インデックス 18532] ファイルの概要
このコミットは、Go言語のmath/big
パッケージにあるInt
型に、encoding.TextMarshaler
およびencoding.TextUnmarshaler
インターフェースの実装を追加するものです。これにより、big.Int
型の値がテキスト形式(例: JSON文字列、XML要素のテキストコンテンツ)でエンコードおよびデコードされる際の挙動が標準化され、より柔軟なデータシリアライゼーションが可能になります。
コミット
commit 1dc82d25632fc402edb36b0d50ecf4d681d77286
Author: Michael T. Jones <mtj@google.com>
Date: Fri Feb 14 12:57:03 2014 -0800
math/big: Add text marshaller interface to Int
Fixes #7329
LGTM=gri
R=gri, bradfitz, mtj
CC=golang-codereviews
https://golang.org/cl/63710043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1dc82d25632fc402edb36b0d50ecf4d681d77286
元コミット内容
math/big: Add text marshaller interface to Int
このコミットは、math/big
パッケージのInt
型にテキストマーシャラーインターフェースを追加します。
これはIssue #7329を修正します。
変更の背景
Go言語の標準ライブラリには、データのシリアライゼーションとデシリアライゼーションのための様々なインターフェースが用意されています。例えば、json.Marshaler
とjson.Unmarshaler
はJSON形式との間でデータを変換するために使用されます。しかし、JSONだけでなく、XMLやその他のテキストベースのフォーマットでデータを扱う場合、それぞれのフォーマットに対応するマーシャラー/アンマーシャラーを個別に実装する必要がありました。
math/big.Int
のような任意精度の整数型は、その性質上、非常に大きな値を扱うことができ、通常の整数型(int
, int64
など)では表現できない数値を扱います。これらの値をテキスト形式で表現する際には、通常、その数値の文字列表現が用いられます。
このコミットが行われる前、big.Int
型は既にjson.Marshaler
とjson.Unmarshaler
を実装していました。しかし、これらの実装は内部的にString()
メソッドで数値を文字列に変換し、それをバイトスライスに変換していました。同様に、デコード時にはバイトスライスを文字列に変換し、SetString()
でbig.Int
に設定していました。
Go 1.2で導入されたencoding.TextMarshaler
とencoding.TextUnmarshaler
インターフェースは、任意の型が自身をテキスト形式のバイトスライスとしてエンコード/デコードする方法を標準化するためのものです。これにより、JSONやXMLなど、テキストベースのエンコーディングを扱うパッケージが、これらの共通インターフェースを利用して型を処理できるようになります。
このコミットの背景には、big.Int
がJSONだけでなく、XMLなど他のテキストベースのエンコーディングでも一貫した方法で扱われるようにするという目的がありました。特に、Issue #7329では、big.Int
がXMLエンコーディングで正しく機能しないという問題が報告されており、このコミットはその問題を解決するために、encoding.TextMarshaler
とencoding.TextUnmarshaler
をbig.Int
に実装することで対応しました。これにより、big.Int
はJSONやXMLといった様々なテキストベースのフォーマットで、その文字列表現を介してシリアライズ・デシリアライズされるようになります。
前提知識の解説
Go言語のencoding
パッケージとインターフェース
Go言語のencoding
パッケージは、様々なデータ形式(JSON, XML, Gobなど)との間でデータを変換するためのインターフェースとユーティリティを提供します。このコミットで重要なのは以下のインターフェースです。
-
json.Marshaler
/json.Unmarshaler
:MarshalJSON() ([]byte, error)
: 型自身をJSON形式のバイトスライスに変換する方法を定義します。UnmarshalJSON(data []byte) error
: JSON形式のバイトスライスから型自身を再構築する方法を定義します。- これらのインターフェースを実装することで、
json.Marshal
やjson.Unmarshal
関数がその型を特別に扱うようになります。
-
encoding.TextMarshaler
/encoding.TextUnmarshaler
:MarshalText() (text []byte, err error)
: 型自身をテキスト形式のバイトスライスに変換する方法を定義します。UnmarshalText(text []byte) error
: テキスト形式のバイトスライスから型自身を再構築する方法を定義します。- これらのインターフェースは、JSONやXMLなど、テキストベースのエンコーディングを扱うパッケージが共通して利用できる汎用的なインターフェースです。例えば、
encoding/json
パッケージは、MarshalJSON
が実装されていない型に対しては、次にMarshalText
が実装されているかをチェックし、もし実装されていればそれを利用してJSON文字列としてエンコードします。同様に、encoding/xml
パッケージもこれらのインターフェースを利用します。
math/big
パッケージとInt
型
math/big
パッケージは、任意精度の算術演算をサポートする型を提供します。
big.Int
: 任意精度の符号付き整数を表す型です。通常のGoの整数型(int
,int64
など)では扱えない非常に大きな整数や、計算結果が通常の整数型の範囲を超える可能性がある場合に使用されます。String()
メソッド:big.Int
の値を10進数文字列として返します。SetString(s string, base int)
メソッド: 指定された文字列s
を基数base
で解析し、その値をbig.Int
に設定します。
シリアライゼーションとデシリアライゼーション
- シリアライゼーション (Serialization): オブジェクトやデータ構造を、ストレージやネットワーク転送に適した形式(バイトストリームなど)に変換するプロセスです。Goでは、
json.Marshal
やgob.NewEncoder
などがこれにあたります。 - デシリアライゼーション (Deserialization): シリアライズされたデータを元のオブジェクトやデータ構造に再構築するプロセスです。Goでは、
json.Unmarshal
やgob.NewDecoder
などがこれにあたります。
このコミットは、big.Int
型がこれらのシリアライゼーション/デシリアライゼーションのメカニズムに、より標準的かつ柔軟に統合されることを目的としています。
技術的詳細
このコミットの主要な変更点は、math/big.Int
型にencoding.TextMarshaler
とencoding.TextUnmarshaler
インターフェースの実装を追加したことです。
MarshalText()
の実装
// MarshalText implements the encoding.TextMarshaler interface
func (z *Int) MarshalText() (text []byte, err error) {
return []byte(z.String()), nil
}
このメソッドは、big.Int
の値をテキスト形式のバイトスライスに変換します。実装は非常にシンプルで、z.String()
メソッド(big.Int
の10進数文字列表現を返す)の結果をバイトスライスに変換して返しています。エラーは発生しないため、nil
が返されます。
UnmarshalText()
の実装
// UnmarshalText implements the encoding.TextUnmarshaler interface
func (z *Int) UnmarshalText(text []byte) error {
if _, ok := z.SetString(string(text), 0); !ok {
return fmt.Errorf("math/big: cannot unmarshal %q into a *big.Int", text)
}
return nil
}
このメソッドは、テキスト形式のバイトスライスからbig.Int
の値を再構築します。
- 入力された
text
バイトスライスをstring
に変換します。 z.SetString(string(text), 0)
を呼び出します。ここで、基数0
はSetString
が文字列のプレフィックス(例: "0x"は16進数、"0"は8進数)に基づいて基数を自動的に推測することを意味します。SetString
が成功したかどうか(ok
がtrue
かどうか)をチェックします。- もし
SetString
が失敗した場合(例: 無効な数値文字列が渡された場合)、fmt.Errorf
を使ってエラーメッセージを生成し、返します。 - 成功した場合は
nil
を返します。
既存のMarshalJSON()
とUnmarshalJSON()
の変更
このコミットでは、既存のMarshalJSON()
とUnmarshalJSON()
メソッドも微修正されています。
MarshalJSON()
: レシーバ変数の名前がx
からz
に変更されました。機能的な変更はありません。UnmarshalJSON()
:- 引数名が
x
からtext
に変更されました。 - エラーメッセージのフォーマットが
%s
から%q
に変更され、引用符で囲まれた文字列として表示されるようになりました。 SetString
の呼び出しとエラーチェックがより簡潔な形式に書き換えられました。- これらの変更は、主にコードの一貫性とエラーメッセージの改善を目的としており、機能的な大きな変更ではありません。
UnmarshalJSON
は内部的にSetString
を使用しており、UnmarshalText
とほぼ同じロジックを共有しています。
- 引数名が
テストの追加
src/pkg/math/big/int_test.go
には、TestIntJSONEncodingTextMarshaller
とTestIntXMLEncodingTextMarshaller
という2つの新しいテスト関数が追加されました。
これらのテストは、intVals
という様々なbig.Int
の文字列表現の配列を使用し、以下のことを確認します。
TestIntJSONEncodingTextMarshaller
:big.Int
の値をJSONにマーシャルし、その後アンマーシャルして、元の値と一致するかどうかを確認します。これは、json.Marshal
がencoding.TextMarshaler
インターフェースを介してbig.Int
を正しく処理することを確認します。
TestIntXMLEncodingTextMarshaller
:big.Int
の値をXMLにマーシャルし、その後アンマーシャルして、元の値と一致するかどうかを確認します。これは、xml.Marshal
がencoding.TextMarshaler
インターフェースを介してbig.Int
を正しく処理することを確認します。
これらのテストは、encoding.TextMarshaler
とencoding.TextUnmarshaler
の実装が、JSONとXMLの両方のコンテキストで期待通りに機能することを保証します。
コアとなるコードの変更箇所
src/pkg/math/big/int.go
--- a/src/pkg/math/big/int.go
+++ b/src/pkg/math/big/int.go
@@ -982,17 +982,29 @@ func (z *Int) GobDecode(buf []byte) error {
}
// MarshalJSON implements the json.Marshaler interface.
-func (x *Int) MarshalJSON() ([]byte, error) {
+func (z *Int) MarshalJSON() ([]byte, error) {
// TODO(gri): get rid of the []byte/string conversions
-\treturn []byte(x.String()), nil
+\treturn []byte(z.String()), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
-func (z *Int) UnmarshalJSON(x []byte) error {
+func (z *Int) UnmarshalJSON(text []byte) error {
// TODO(gri): get rid of the []byte/string conversions
-\t_, ok := z.SetString(string(x), 0)
-\tif !ok {
-\t\treturn fmt.Errorf("math/big: cannot unmarshal %s into a *big.Int", x)
+\tif _, ok := z.SetString(string(text), 0); !ok {
+\t\treturn fmt.Errorf("math/big: cannot unmarshal %q into a *big.Int", text)
+\t}
+\treturn nil
+}
+
+// MarshalText implements the encoding.TextMarshaler interface
+func (z *Int) MarshalText() (text []byte, err error) {
+\treturn []byte(z.String()), nil
+}
+
+// UnmarshalText implements the encoding.TextUnmarshaler interface
+func (z *Int) UnmarshalText(text []byte) error {
+\tif _, ok := z.SetString(string(text), 0); !ok {
+\t\treturn fmt.Errorf("math/big: cannot unmarshal %q into a *big.Int", text)
\t}
\treturn nil
}
src/pkg/math/big/int_test.go
--- a/src/pkg/math/big/int_test.go
+++ b/src/pkg/math/big/int_test.go
@@ -9,6 +9,7 @@ import (
"encoding/gob"
"encoding/hex"
"encoding/json"
+ "encoding/xml"
"fmt"
"math/rand"
"testing"
@@ -1528,6 +1529,58 @@ func TestIntJSONEncoding(t *testing.T) {
}
}
+var intVals = []string{
+\t"-141592653589793238462643383279502884197169399375105820974944592307816406286",
+\t"-1415926535897932384626433832795028841971",
+\t"-141592653589793",
+\t"-1",
+\t"0",
+\t"1",
+\t"141592653589793",
+\t"1415926535897932384626433832795028841971",
+\t"141592653589793238462643383279502884197169399375105820974944592307816406286",
+}\n+\n+func TestIntJSONEncodingTextMarshaller(t *testing.T) {
+\tfor _, num := range intVals {
+\t\tvar tx Int
+\t\ttx.SetString(num, 0)
+\t\tb, err := json.Marshal(&tx)
+\t\tif err != nil {
+\t\t\tt.Errorf("marshaling of %s failed: %s", &tx, err)
+\t\t\tcontinue
+\t\t}\n+\t\tvar rx Int
+\t\tif err := json.Unmarshal(b, &rx); err != nil {
+\t\t\tt.Errorf("unmarshaling of %s failed: %s", &tx, err)
+\t\t\tcontinue
+\t\t}\n+\t\tif rx.Cmp(&tx) != 0 {
+\t\t\tt.Errorf("JSON encoding of %s failed: got %s want %s", &tx, &rx, &tx)
+\t\t}\n+\t}\n+}\n+\n+func TestIntXMLEncodingTextMarshaller(t *testing.T) {
+\tfor _, num := range intVals {
+\t\tvar tx Int
+\t\ttx.SetString(num, 0)
+\t\tb, err := xml.Marshal(&tx)
+\t\tif err != nil {
+\t\t\tt.Errorf("marshaling of %s failed: %s", &tx, err)
+\t\t\tcontinue
+\t\t}\n+\t\tvar rx Int
+\t\tif err := xml.Unmarshal(b, &rx); err != nil {
+\t\t\tt.Errorf("unmarshaling of %s failed: %s", &tx, err)
+\t\t\tcontinue
+\t\t}\n+\t\tif rx.Cmp(&tx) != 0 {
+\t\t\tt.Errorf("XML encoding of %s failed: got %s want %s", &tx, &rx, &tx)
+\t\t}\n+\t}\n+}\n+\n func TestIssue2607(t *testing.T) {
// This code sequence used to hang.
n := NewInt(10)
コアとなるコードの解説
このコミットの核心は、math/big.Int
型がencoding.TextMarshaler
とencoding.TextUnmarshaler
インターフェースに準拠するようにした点です。
MarshalText()
:big.Int
の値をその文字列表現(z.String()
の結果)としてバイトスライスに変換します。これにより、big.Int
はJSONの文字列フィールドやXMLのテキストコンテンツとして自然にシリアライズされるようになります。例えば、json.Marshal
は、型がjson.Marshaler
を実装していない場合でも、encoding.TextMarshaler
を実装していればそれを利用して文字列としてエンコードします。UnmarshalText()
: テキスト形式のバイトスライスを受け取り、それをbig.Int
の値にパースします。内部的にはSetString
メソッドを使用しており、文字列からbig.Int
への変換を行います。これにより、JSONの文字列フィールドやXMLのテキストコンテンツからbig.Int
をデシリアライズできるようになります。無効な入力文字列が与えられた場合にはエラーを返します。
これらの変更により、big.Int
はGoの標準的なエンコーディングメカニズムとより密接に統合され、開発者はbig.Int
を扱う際に、JSONやXMLなどのテキストベースのフォーマットで、その文字列表現を介して簡単にシリアライズ・デシリアライズできるようになりました。これは、特に異なるシステム間で任意精度の数値を交換する際に非常に有用です。
既存のMarshalJSON
とUnmarshalJSON
の変更は、主にコードの整合性を保つためのものであり、TextMarshaler
インターフェースの追加が、big.Int
のシリアライゼーションの汎用性を高める主要な機能追加です。
テストコードの追加は、この新しい機能がJSONとXMLの両方で正しく動作することを検証しており、堅牢性を保証しています。
関連リンク
- Go Issue #7329: https://github.com/golang/go/issues/7329
- Go CL 63710043: https://golang.org/cl/63710043
encoding.TextMarshaler
documentation: https://pkg.go.dev/encoding#TextMarshalerencoding.TextUnmarshaler
documentation: https://pkg.go.dev/encoding#TextUnmarshalermath/big
package documentation: https://pkg.go.dev/math/big
参考にした情報源リンク
- Go言語の公式ドキュメント (
encoding
パッケージ,math/big
パッケージ) - Go言語のGitHubリポジトリ (コミット履歴、Issueトラッカー)
- Go言語のコードレビューシステム (Gerrit)
- Go言語のブログやコミュニティの議論 (Go 1.2の変更点に関する情報)