Go语言如何序列化以太坊Header结构体 无需遍历字段得到正确JSON输出
Go以太坊Header结构体序列化格式问题解决方案
问题场景
我在Go开发过程中遇到一个此前未接触过的场景,和Stack Overflow上《嵌入带自定义MarshalJSON()方法的结构体的惯用写法》问题场景相似。
测试代码
package main import ( "context" "fmt" "os" "github.com/ethereum/go-ethereum/ethclient" ) func main() { icpPath, _ := os.LookupEnv("GETH_ICP_PATH") client, _ := ethclient.Dial(icpPath) ctx := context.Background() header, _ := client.HeaderByNumber(ctx, nil) data, _ := header.MarshalJSON() fmt.Println(string(data)) }
运行结果
整体序列化输出
{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0x400000000","number":"0x0","gasLimit":"0x1388","gasUsed":"0x0","timestamp":"0x0","extraData":"0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000042","baseFeePerGas":null,"hash":"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"}
单独序列化difficulty字段输出
单独执行以下代码序列化difficulty字段:
difficulty, _ := header.Difficulty.MarshalJSON() fmt.Println(string(difficulty))
得到十进制数值输出:
17179869184
相关类型定义
Header是go-ethereum中代表以太坊区块头的结构体,定义如下:
// Header represents a block header in the Ethereum blockchain. type Header struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Difficulty *big.Int `json:"difficulty" gencodec:"required"` Number *big.Int `json:"number" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` Time uint64 `json:"timestamp" gencodec:"required"` Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` }
问题描述
无需遍历所有字段,就能将整个Header结构体完整序列化为所有字段数值格式正确的JSON的正确方案是什么?
解决方案
问题原因
出现格式差异的核心原因是:Header结构体本身实现了自定义的MarshalJSON方法(由go-ethereum的gencodec工具自动生成),该实现为了符合以太坊RPC规范,会自动把所有*big.Int类型的字段序列化为带0x前缀的十六进制字符串。而单独调用big.Int的MarshalJSON方法时,走的是标准库big.Int的默认序列化逻辑,输出十进制格式。
最优方案:类型别名绕开自定义序列化
Go的类型别名不会继承原类型的方法,因此可以通过定义Header的别名类型,绕开原结构体的自定义MarshalJSON逻辑,直接走标准库的默认序列化规则,自动使用每个字段自身的序列化实现,无需手动遍历处理任何字段。
代码示例:
import ( "encoding/json" "github.com/ethereum/go-ethereum/core/types" ) // 定义Header类型别名,不会继承原类型的MarshalJSON方法 type CustomHeader types.Header // 转换后直接序列化 func marshalHeaderCorrectly(header *types.Header) ([]byte, error) { ch := CustomHeader(*header) return json.Marshal(ch) }
使用该方案序列化后,difficulty、number、baseFeePerGas等*big.Int类型字段都会输出十进制格式,其余字段的序列化结果和各自类型的默认实现保持一致,完全符合需求。
内容的提问来源于stack exchange,提问作者Kingindanord
相关产品推荐
相关产品推荐

