Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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.Marshalerjson.UnmarshalerはJSON形式との間でデータを変換するために使用されます。しかし、JSONだけでなく、XMLやその他のテキストベースのフォーマットでデータを扱う場合、それぞれのフォーマットに対応するマーシャラー/アンマーシャラーを個別に実装する必要がありました。

math/big.Intのような任意精度の整数型は、その性質上、非常に大きな値を扱うことができ、通常の整数型(int, int64など)では表現できない数値を扱います。これらの値をテキスト形式で表現する際には、通常、その数値の文字列表現が用いられます。

このコミットが行われる前、big.Int型は既にjson.Marshalerjson.Unmarshalerを実装していました。しかし、これらの実装は内部的にString()メソッドで数値を文字列に変換し、それをバイトスライスに変換していました。同様に、デコード時にはバイトスライスを文字列に変換し、SetString()big.Intに設定していました。

Go 1.2で導入されたencoding.TextMarshalerencoding.TextUnmarshalerインターフェースは、任意の型が自身をテキスト形式のバイトスライスとしてエンコード/デコードする方法を標準化するためのものです。これにより、JSONやXMLなど、テキストベースのエンコーディングを扱うパッケージが、これらの共通インターフェースを利用して型を処理できるようになります。

このコミットの背景には、big.IntがJSONだけでなく、XMLなど他のテキストベースのエンコーディングでも一貫した方法で扱われるようにするという目的がありました。特に、Issue #7329では、big.IntがXMLエンコーディングで正しく機能しないという問題が報告されており、このコミットはその問題を解決するために、encoding.TextMarshalerencoding.TextUnmarshalerbig.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.Marshaljson.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.Marshalgob.NewEncoderなどがこれにあたります。
  • デシリアライゼーション (Deserialization): シリアライズされたデータを元のオブジェクトやデータ構造に再構築するプロセスです。Goでは、json.Unmarshalgob.NewDecoderなどがこれにあたります。

このコミットは、big.Int型がこれらのシリアライゼーション/デシリアライゼーションのメカニズムに、より標準的かつ柔軟に統合されることを目的としています。

技術的詳細

このコミットの主要な変更点は、math/big.Int型にencoding.TextMarshalerencoding.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の値を再構築します。

  1. 入力されたtextバイトスライスをstringに変換します。
  2. z.SetString(string(text), 0)を呼び出します。ここで、基数0SetStringが文字列のプレフィックス(例: "0x"は16進数、"0"は8進数)に基づいて基数を自動的に推測することを意味します。
  3. SetStringが成功したかどうか(oktrueかどうか)をチェックします。
  4. もしSetStringが失敗した場合(例: 無効な数値文字列が渡された場合)、fmt.Errorfを使ってエラーメッセージを生成し、返します。
  5. 成功した場合はnilを返します。

既存のMarshalJSON()UnmarshalJSON()の変更

このコミットでは、既存のMarshalJSON()UnmarshalJSON()メソッドも微修正されています。

  • MarshalJSON(): レシーバ変数の名前がxからzに変更されました。機能的な変更はありません。
  • UnmarshalJSON():
    • 引数名がxからtextに変更されました。
    • エラーメッセージのフォーマットが%sから%qに変更され、引用符で囲まれた文字列として表示されるようになりました。
    • SetStringの呼び出しとエラーチェックがより簡潔な形式に書き換えられました。
    • これらの変更は、主にコードの一貫性とエラーメッセージの改善を目的としており、機能的な大きな変更ではありません。UnmarshalJSONは内部的にSetStringを使用しており、UnmarshalTextとほぼ同じロジックを共有しています。

テストの追加

src/pkg/math/big/int_test.goには、TestIntJSONEncodingTextMarshallerTestIntXMLEncodingTextMarshallerという2つの新しいテスト関数が追加されました。 これらのテストは、intValsという様々なbig.Intの文字列表現の配列を使用し、以下のことを確認します。

  • TestIntJSONEncodingTextMarshaller:
    • big.Intの値をJSONにマーシャルし、その後アンマーシャルして、元の値と一致するかどうかを確認します。これは、json.Marshalencoding.TextMarshalerインターフェースを介してbig.Intを正しく処理することを確認します。
  • TestIntXMLEncodingTextMarshaller:
    • big.Intの値をXMLにマーシャルし、その後アンマーシャルして、元の値と一致するかどうかを確認します。これは、xml.Marshalencoding.TextMarshalerインターフェースを介してbig.Intを正しく処理することを確認します。

これらのテストは、encoding.TextMarshalerencoding.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.TextMarshalerencoding.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などのテキストベースのフォーマットで、その文字列表現を介して簡単にシリアライズ・デシリアライズできるようになりました。これは、特に異なるシステム間で任意精度の数値を交換する際に非常に有用です。

既存のMarshalJSONUnmarshalJSONの変更は、主にコードの整合性を保つためのものであり、TextMarshalerインターフェースの追加が、big.Intのシリアライゼーションの汎用性を高める主要な機能追加です。

テストコードの追加は、この新しい機能がJSONとXMLの両方で正しく動作することを検証しており、堅牢性を保証しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (encodingパッケージ, math/bigパッケージ)
  • Go言語のGitHubリポジトリ (コミット履歴、Issueトラッカー)
  • Go言語のコードレビューシステム (Gerrit)
  • Go言語のブログやコミュニティの議論 (Go 1.2の変更点に関する情報)