Golang big.Int MarshalJSON() エラーとトラブルシューティング:実践ガイド

2025-06-01

big.Int.MarshalJSON() は、Go言語の math/big パッケージで提供されている Int 型のメソッドの一つです。このメソッドの主な役割は、big.Int 型の値を JSON形式のテキストデータ に変換することです。

具体的には、big.Int 型の大きな整数値を、JSONの文字列として表現できる形式に変換します。JSONでは、数値はそのままの形式で表現されるのが一般的ですが、big.Int は非常に大きな数値を扱うことができるため、そのままJSONの数値として表現すると、JSONパーサーが対応できない場合があります。

そのため、MarshalJSON() メソッドは、big.Int の値を 文字列 としてJSONエンコードします。この文字列は、通常、10進数の表現になります。

メソッドのシグネチャは以下のようになっています。

func (z *Int) MarshalJSON() ([]byte, error)

このメソッドは、以下の値を返します。

  • error: エンコード処理中にエラーが発生した場合に返されます。通常、big.Int のエンコード処理でエラーが発生することは稀です。
  • []byte: JSON形式にエンコードされたバイト列。これは、JSONの文字列値を表します。

どのような場合に big.Int.MarshalJSON() を使うのか?

主に、Goのプログラムで扱っている大きな整数値(big.Int 型)を、JSON形式で外部のシステムやアプリケーションに送信したり、ファイルに保存したりする場合に使われます。例えば、

  • データベースにJSON形式で大きな数値を格納する場合
  • 設定ファイルなどで大きな数値をJSON形式で保存する場合
  • APIレスポンスとして大きなID値をJSONで返す場合

使用例

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

type Data struct {
	ID *big.Int `json:"id"`
	Name string `json:"name"`
}

func main() {
	// 大きな整数値を作成
	largeInt := new(big.Int)
	largeInt.SetString("123456789012345678901234567890", 10)

	data := Data{
		ID:   largeInt,
		Name: "Example Data",
	}

	// JSONエンコード
	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Println("JSONエンコードエラー:", err)
		return
	}

	fmt.Println(string(jsonData))
}

この例では、Data 構造体の ID フィールドが *big.Int 型です。json.Marshal(data) を実行すると、ID フィールドの MarshalJSON() メソッドが自動的に呼び出され、大きな整数値がJSONの文字列としてエンコードされます。

出力されるJSONは以下のようになります。

{"id":"123456789012345678901234567890","name":"Example Data"}

このように、big.Int の値がJSONの数値ではなく、引用符で囲まれた文字列として表現されていることがわかります。



以下に、よくあるエラーとそのトラブルシューティング方法を説明します。

JSONエンコード時のエラー

  • トラブルシューティング
    • 構造体の他のフィールドの型が encoding/json パッケージでサポートされているか確認してください。
    • エラーメッセージをよく確認し、どのフィールドで問題が発生しているかを特定します。
  • 原因
    big.Int.MarshalJSON() 自体は通常エラーを返しませんが、構造体の中に big.Int 型のフィールドが含まれており、その構造体の他のフィールドの型がJSONエンコードをサポートしていない場合に、json.Marshal() がエラーを返すことがあります。
  • エラー内容
    json.Marshal() 関数がエラーを返す。

JSONデコード時のエラー

  • トラブルシューティング

    • JSONデータの形式を確認し、big.Int 型に対応する値が文字列になっているか確認してください。
    • JSONデータで big.Int 型に対応する文字列が、数字のみで構成されているか確認してください。不要な文字や記号が含まれていないか確認します。
    • Goのコードで json.Unmarshal() を使用してデコードする場合、big.Int 型のポインタ (*big.Int) にデコードする必要があります。
    package main
    
    import (
        "encoding/json"
        "fmt"
        "math/big"
    )
    
    type Data struct {
        ID *big.Int `json:"id"`
        Name string `json:"name"`
    }
    
    func main() {
        jsonData := []byte(`{"id":"98765432109876543210","name":"Decoded Data"}`)
    
        var data Data
        err := json.Unmarshal(jsonData, &data)
        if err != nil {
            fmt.Println("JSONデコードエラー:", err)
            return
        }
    
        fmt.Printf("ID: %v, Name: %s\n", data.ID, data.Name)
    }
    
  • 原因

    • JSONデータで big.Int 型に対応する値が文字列(引用符で囲まれている)になっていない場合。big.Int.MarshalJSON() は文字列として出力するため、デコード時も文字列として扱われることを期待します。
    • JSONデータで big.Int 型に対応する文字列が、有効な10進数の整数としてパースできない場合。
  • エラー内容
    JSON文字列を big.Int 型にデコードする際にエラーが発生する (json.Unmarshal() がエラーを返す)。

nil ポインタの扱い

  • トラブルシューティング
    nil ポインタがJSONエンコードされる挙動を理解しておけば、意図しない null 値が出力された場合に、Goのコードで big.Int のポインタが適切に初期化されているかを確認します。
  • エラーの可能性
    big.Int 型のポインタが nil の状態で MarshalJSON() が呼び出されることはありません。json.Marshal()nil のポインタに対して適切に null としてJSONエンコードします。

カスタムJSON処理との競合

  • トラブルシューティング
    カスタムのエンコード処理が正しく実装されているか、encoding/json のインターフェースを正しく満たしているかを確認します。通常は、big.Int のデフォルトの MarshalJSON() の動作で十分な場合が多いです。
  • エラーの可能性
    big.Int 型に対して、MarshalJSON() とは異なるカスタムのJSONエンコード処理を実装しようとした場合に、意図しない動作やエラーが発生する可能性があります。

トラブルシューティングの一般的なヒント

  • Goのドキュメントや標準ライブラリのソースコードを参照する
    encoding/json パッケージや math/big パッケージのドキュメントやソースコードは、理解を深める上で非常に役立ちます。
  • ログ出力を活用する
    JSONエンコード・デコードの前後で、変数の値や型をログ出力することで、処理の流れやデータの状態を確認できます。
  • 最小限のコードで再現させる
    問題が発生するコードの一部を切り出し、最小限のコードで問題を再現させることで、原因を特定しやすくなります。
  • エラーメッセージを注意深く読む
    エラーメッセージは、問題の原因を特定するための重要な情報源です。


例1: 基本的なJSONエンコード

この例では、big.Int 型のフィールドを持つ構造体を定義し、それをJSONにエンコードします。

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

type Item struct {
	ID   int64    `json:"id"`
	Value *big.Int `json:"value"`
	Name string   `json:"name"`
}

func main() {
	// 大きな整数値を作成
	largeInt := new(big.Int)
	largeInt.SetString("99999999999999999999999999999999999999", 10)

	item := Item{
		ID:    1,
		Value: largeInt,
		Name:  "Very Large Number",
	}

	// JSONエンコード
	jsonData, err := json.Marshal(item)
	if err != nil {
		fmt.Println("JSONエンコードエラー:", err)
		return
	}

	fmt.Println(string(jsonData))
}

説明

  1. Item という構造体を定義し、Value フィールドの型を *big.Int にしています。json:"value" は、JSONのキー名を "value" に指定するタグです。
  2. new(big.Int) で新しい big.Int のポインタを作成し、SetString() メソッドを使って大きな整数値を設定しています。
  3. Item 型のインスタンスを作成し、Value フィールドに作成した大きな整数値を代入しています。
  4. json.Marshal(item) 関数を使って、item をJSON形式のバイト列にエンコードします。この際、item.ValueMarshalJSON() メソッドが自動的に呼び出され、big.Int の値が文字列としてJSONに埋め込まれます。
  5. エンコードされたJSONデータ(バイト列)を文字列に変換して出力します。

出力

{"id":1,"value":"99999999999999999999999999999999999999","name":"Very Large Number"}

value の値が引用符で囲まれた文字列としてJSONに表現されていることがわかります。

例2: JSONデコードと big.Int への変換

この例では、JSONデータに big.Int 型の値が文字列として含まれている場合に、それを big.Int 型にデコードする方法を示します。

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

type Item struct {
	ID   int64    `json:"id"`
	Value *big.Int `json:"value"`
	Name string   `json:"name"`
}

func main() {
	jsonData := []byte(`{"id":2,"value":"12345678901234567890","name":"Another Item"}`)

	var item Item
	err := json.Unmarshal(jsonData, &item)
	if err != nil {
		fmt.Println("JSONデコードエラー:", err)
		return
	}

	fmt.Printf("ID: %d\n", item.ID)
	fmt.Printf("Value: %v\n", item.Value)
	fmt.Printf("Name: %s\n", item.Name)

	// デコードされた big.Int の値を使って計算する例
	if item.Value != nil {
		ten := big.NewInt(10)
		multiplied := new(big.Int).Mul(item.Value, ten)
		fmt.Printf("Value * 10: %v\n", multiplied)
	}
}

説明

  1. JSON形式のバイト列 jsonData を定義します。value の値は文字列として表現されています。
  2. Item 型の変数 item を宣言します。
  3. json.Unmarshal(jsonData, &item) 関数を使って、JSONデータを item にデコードします。value の文字列は、Item 構造体の Value フィールド(*big.Int 型)に正しくパースされます。json.Unmarshal は、JSONの文字列を big.Int 型に自動的に変換してくれます。
  4. デコードされた item の各フィールドの値を出力します。
  5. デコードされた big.Int の値を使って簡単な計算(10倍にする)を行う例を示しています。big.Int 型の演算は、math/big パッケージのメソッドを使用します。

出力

ID: 2
Value: 12345678901234567890
Name: Another Item
Value * 10: 123456789012345678900

例3: big.Int のスライスをJSONエンコード・デコードする

複数の大きな整数値を扱う場合に、big.Int のスライスをJSONとしてエンコードおよびデコードする方法を示します。

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

func main() {
	// big.Int のスライスを作成
	largeInts := []*big.Int{
		new(big.Int).SetString("1000000000000000000", 10),
		new(big.Int).SetString("2000000000000000000", 10),
		new(big.Int).SetString("3000000000000000000", 10),
	}

	// JSONエンコード
	jsonData, err := json.Marshal(largeInts)
	if err != nil {
		fmt.Println("JSONエンコードエラー:", err)
		return
	}

	fmt.Println("エンコードされた JSON:", string(jsonData))

	// JSONデコード
	var decodedInts []*big.Int
	err = json.Unmarshal(jsonData, &decodedInts)
	if err != nil {
		fmt.Println("JSONデコードエラー:", err)
		return
	}

	fmt.Println("デコードされた big.Int のスライス:")
	for _, val := range decodedInts {
		fmt.Println(val)
	}
}

説明

  1. big.Int のポインタの要素を持つスライス largeInts を作成します。
  2. json.Marshal(largeInts) を使用してスライスをJSONエンコードします。各 big.Int の値は文字列としてJSON配列に格納されます。
  3. エンコードされたJSONデータを出力します。
  4. var decodedInts []*big.Int で、デコード先の big.Int のポインタのスライスを宣言します。
  5. json.Unmarshal(jsonData, &decodedInts) を使用してJSONデータをデコードします。JSON配列内の文字列は、big.Int 型として正しくパースされ、decodedInts スライスに格納されます。
  6. デコードされた big.Int の値をループで出力します。
エンコードされた JSON: ["1000000000000000000","2000000000000000000","3000000000000000000"]
デコードされた big.Int のスライス:
1000000000000000000
2000000000000000000
3000000000000000000


カスタム MarshalJSON() メソッドの実装

big.Int 型を直接埋め込んだ構造体において、独自の MarshalJSON() メソッドを実装することで、JSON出力形式を完全に制御できます。

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

type CustomInt struct {
	*big.Int
}

// CustomInt 型に対する独自の MarshalJSON() メソッド
func (ci CustomInt) MarshalJSON() ([]byte, error) {
	// 16進数文字列としてJSON出力する例
	hexString := ci.Int.Text(16)
	return json.Marshal(hexString)
}

type Data struct {
	ID    int64     `json:"id"`
	Value CustomInt `json:"value"`
	Name  string    `json:"name"`
}

func main() {
	largeInt := new(big.Int)
	largeInt.SetString("4294967295", 10) // FFFFFFFF in hex

	data := Data{
		ID:    1,
		Value: CustomInt{largeInt},
		Name:  "Custom Integer",
	}

	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Println("JSONエンコードエラー:", err)
		return
	}

	fmt.Println(string(jsonData))
}

説明

  • json.Marshal(data) を実行すると、CustomInt 型の MarshalJSON() メソッドが呼び出され、Value は16進数の文字列としてJSONに出力されます。
  • Data 構造体の Value フィールドの型を CustomInt にしています。
  • CustomInt 型に対して MarshalJSON() メソッドを実装しています。このメソッド内では、big.Int の値を Text(16) を使って16進数の文字列に変換し、それを json.Marshal() でJSON文字列としてエンコードしています。
  • CustomInt という新しい型を定義し、*big.Int を埋め込んでいます。

出力

{"id":1,"value":"ffffffff","name":"Custom Integer"}

構造体内で big.Int を文字列として保持する

big.Int を直接構造体に含めず、JSONのエンコード・デコード時のみ文字列に変換する方法です。

package main

import (
	"encoding/json"
	"fmt"
	"math/big"
)

type Data struct {
	ID    int64  `json:"id"`
	Value string `json:"value"` // 文字列として保持
	Name  string `json:"name"`
}

func main() {
	largeInt := new(big.Int)
	largeInt.SetString("123456789012345", 10)

	data := Data{
		ID:    2,
		Value: largeInt.String(), // big.Int を文字列に変換して格納
		Name:  "String Integer",
	}

	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Println("JSONエンコードエラー:", err)
		return
	}

	fmt.Println("エンコード:", string(jsonData))

	// JSONデコード
	var decodedData Data
	err = json.Unmarshal(jsonData, &decodedData)
	if err != nil {
		fmt.Println("JSONデコードエラー:", err)
		return
	}

	// デコード後の文字列を big.Int に変換
	decodedBigInt := new(big.Int)
	_, ok := decodedBigInt.SetString(decodedData.Value, 10)
	if !ok {
		fmt.Println("文字列から big.Int への変換エラー")
		return
	}

	fmt.Printf("デコード後の Value (string): %s\n", decodedData.Value)
	fmt.Printf("デコード後の Value (big.Int): %v\n", decodedBigInt)
}

説明

  • デコード後、JSONの文字列を big.Int に変換するには、SetString() メソッドを使用します。
  • エンコード前に big.Int の値を .String() メソッドで10進数の文字列に変換し、構造体に格納します。
  • Data 構造体の Value フィールドを string 型として定義します。

出力

エンコード: {"id":2,"value":"123456789012345","name":"String Integer"}
デコード後の Value (string): 123456789012345
デコード後の Value (big.Int): 123456789012345

カスタムエンコーダー/デコーダーの利用 (より高度なケース)

より複雑なシリアライズ形式や、JSON以外の形式(例: XML, Protocol Buffers など)で big.Int を扱いたい場合は、それぞれのライブラリが提供するカスタムエンコーダーやデコーダーの仕組みを利用できます。

  • Protocol Buffers
    Protocol Buffers は独自の型システムを持ちますが、通常は string 型として big.Int の値をエンコード・デコードすることが推奨されます。必要に応じて、カスタムのシリアライズ/デシリアライズロジックを実装することも可能です。
  • XML
    encoding/xml パッケージでは、カスタムの MarshalXML および UnmarshalXML メソッドを実装することで、XML形式での big.Int の表現を制御できます。

Base64 エンコード

バイナリデータとして big.Int を扱いたい場合に、Base64エンコードを利用する方法もあります。

package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"math/big"
)

type Data struct {
	ID    int64  `json:"id"`
	Value string `json:"value_base64"`
	Name  string `json:"name"`
}

func main() {
	largeInt := new(big.Int)
	largeInt.SetString("9876543210", 10)

	// big.Int をバイト列に変換し、Base64エンコード
	bytes := largeInt.Bytes()
	base64String := base64.StdEncoding.EncodeToString(bytes)

	data := Data{
		ID:    3,
		Value: base64String,
		Name:  "Base64 Integer",
	}

	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Println("JSONエンコードエラー:", err)
		return
	}

	fmt.Println("エンコード:", string(jsonData))

	// JSONデコードと Base64デコード
	var decodedData Data
	err = json.Unmarshal(jsonData, &decodedData)
	if err != nil {
		fmt.Println("JSONデコードエラー:", err)
		return
	}

	decodedBytes, err := base64.StdEncoding.DecodeString(decodedData.Value)
	if err != nil {
		fmt.Println("Base64デコードエラー:", err)
		return
	}

	decodedBigInt := new(big.Int).SetBytes(decodedBytes)
	fmt.Printf("デコード後の Value (base64): %s\n", decodedData.Value)
	fmt.Printf("デコード後の Value (big.Int): %v\n", decodedBigInt)
}
  • デコード時には、Base64エンコードされた文字列を base64.StdEncoding.DecodeString() でバイト列に戻し、new(big.Int).SetBytes()big.Int に変換します。
  • エンコード時に big.Int.Bytes() でバイト列を取得し、base64.StdEncoding.EncodeToString() でBase64エンコードします。