Golang Go语言中結構體格式化打印工具

发布于 1周前 作者 sinazl 来自 Go语言

很多時候我需要格式化打印輸出一些複雜的結構體,但是 Golang 自帶的 "%#v" 格式化不是很好讀,可定製性也不高,而且會把很多無關的私有值都打印出來,比如 time.Time

因此我寫了個可以完全自定義,能方便地控制輸出格式,管理縮進,而且有簡單接口的結構體格式化模塊:

gopkg.in/structformat.v0

比如說你有這樣一個結構體定義:

type Encryption struct {
	Key        []byte
	IV         []byte
	Tag        []byte
	Mode       string
	Plaintext  string
	Ciphertext []byte
}

你想格式化打印一份這樣的數據:

enc := &Encryption{
    Key:  []byte{0xf8,0x0f,0xe1,0xef,0x1b,0x28,0x92,0xe8,0x12,0x0f,0x0d,0x75,0xa1,0xb0,0x67,0xce},
    IV:   []byte{0xdf,0x3a,0xb2,0x8c,0x39,0xb9,0xa4,0x52,0xce,0xfd,0xd2,0x9d,0x0a,0x02,0xd4,0x1d},
    Tag:  nil,
    Mode: "CTR",
    Plaintext: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et \n" +
        "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex \n" +
        "ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu \n" +
        "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \n" +
        "mollit anim id est laborum",
    Ciphertext: []byte{
        0xa3,0xb1,0x24,0x93,0x30,0x88,0x31,0xdc,0x3d,0x32,0xea,0x7b,0x06,0xbe,0x86,0xba,
        0x31,0xe7,0x72,0x5a,0x9c,0x72,0x9c,0xa4,0xea,0x4b,0xa1,0x5f,0x95,0x47,0xad,0xfc,
        0x5e,0x40,0x15,0x62,0x4e,0x35,0xe4,0xe5,0xfe,0x53,0x24,0x68,0x8a,0x77,0x0c,0xb7,
        0xf9,0x8c,0xac,0x4d,0xdf,0x66,0x64,0x86,0x5a,0xba,0xcc,0xeb,0x68,0x16,0x43,0xd2,
        0x84,0x37,0xaf,0x4b,0xe4,0x47,0x2a,0x84,0x1e,0x0a,0x1d,0xff,0x7d,0x76,0x00,0x47,
        0xe6,0x51,0x96,0xfa,0x19,0x96,0x1b,0xa9,0x04,0x42,0xcf,0xbc,0xc0,0x88,0x71,0x03,
        0xe7,0x49,0x4c,0x0e,0xf5,0x0f,0x1f,0x8c,0x69,0x3e,0x65,0x07,0x90,0x56,0xc7,0xa7,
        0xf1,0x95,0xd6,0xb2,0xbb,0xb8,0x3a,0x8e,0x66,0xbc,0x0a,0xbc,0x20,0x79,0xcc,0xa8,
        0xa8,0x65,0x23,0x5c,0xcf,0xe9,0x4d,0x76,0x6e,0x13,0xf0,0x64,0x13,0x5a,0x08,0xab,
        0xdf,0x90,0x83,0xce,0x01,0x5a,0x5e,0xaa,0x3d,0x0e,0x46,0x76,0x04,0x99,0xe0,0x16,
        0xb2,0x76,0xbe,0x28,0x4b,0xe2,0x12,0xf5,0xdc,0x60,0x5b,0x67,0xe6,0xaa,0x2c,0x70,
        0x49,0x7e,0x55,0x5c,0x1c,0x0a,0x60,0x36,0x69,0x97,0xf3,0x29,0x04,0x90,0x5f,0x60,
        0xd7,0x65,0x60,0xa8,0xc0,0x76,0xd1,0xe3,0x42,0xc1,0x0e,0x63,0xf9,0xe0,0x72,0xec,
        0x95,0x97,0x44,0x9c,0xea,0xf3,0x21,0x50,0x5b,0xa7,0x3e,0xde,0xe3,0x8c,0x7d,0xee,
        0xcc,0x64,0x74,0x77,0xb5,0xcc,0xdc,0x8e,0xe5,0x97,0x63,0x6b,0x7f,0x56,0x5c,0x5d,
        0x7f,0x6f,0x68,0x8b,0x5b,0xa6,0x37,0xe8,0xf5,0xb7,0xfc,0xf9,0x96,0xb6,0xc0,0xb7,
        0x17,0x3d,0xd1,0xfe,0x11,0x6d,0x39,0x39,0x37,0xbe,0x1f,0xe7,0x1f,0x13,0x99,0x9f,
        0xdb,0x2f,0xcc,0xc8,0x46,0x92,0x85,0x12,0xb0,0x41,0x0a,0x14,0xd1,0x8f,0x46,0xf4,
        0x5e,0xac,0x1c,0xf5,0xa7,0x40,0xe1,0xfe,0x51,0xc4,0xe0,0x1a,0x12,0x37,0xee,0x97,
        0x63,0x47,0x50,0x4c,0xfa,0x87,0x4b,0xc6,0xe7,0xda,0x31,0x4d,0x13,0x62,0x39,0xb2,
        0xfe,0x3a,0xb5,0x96,0x53,0x90,0xd5,0x06,0x8d,0x57,0x22,0x7f,0xc3,0xcd,0x75,0x99,
        0xff,0x45,0x51,0x10,0xbc,0x03,0x0b,0xb8,0x82,0x5d,0x77,0xd6,0xb5,0xa2,0x81,0x00,
        0xb4,0x78,0xe3,0xd3,0xcd,0xad,0x37,0xf3,0x76,0xf8,0x40,0x2d,0x5e,0xf1,0x17,0x01,
        0xd0,0x13,0x4b,0xbe,0x4c,0x7e,0x1d,0x67,0x54,0xd0,0xc6,0x19,0x7a,0x91,0x91,0xc7,
        0x5f,0xad,0xd5,0x99,0x4e,0x03,0x79,0x7e,0x86,0x9a,0x38,0xbf,0x12,0x1a,0xa0,0x6e,
        0x32,0xb2,0x41,0xda,0x11,0x4e,0xfc,0x3c,0x61,0x5a,0x02,0x95,0x2b,0x6b,0x15,0xc6,
        0xa9,0xcd,0xc2,0x56,0xc1,0xc3,0x2a,0x43,0xc6,0xbf,0xfc,0x0b,0x46,0xe8,0xc7,0xee,
        0xea,0x64,0x9b,0x08,0x42,0x6a,0x47,0x9d,0x4c,0xce,0xe7,0x95,
    },
}

利用這個模塊,你需要給這個結構體類型實現一個構建結構體打印樣式的接口。這個模塊提供了很多構建樣式的基本函數,通過拼接組合這些函數就能以聲明式的方式定義樣式了。這種模式非常類似現在格式化日誌的做法:

func (enc *Encryption) StructFormat(w sfmt.Writer) (n int, err error) {
	return sfmt.Struct(
		`Encryption`,
		sfmt.KV(`Key`, sfmt.HexBytes(enc.Key)),
		sfmt.KV(`IV`, sfmt.HexBytes(enc.IV)),
		sfmt.KV(`Tag`, sfmt.HexBytes(enc.Tag)),
		sfmt.KV(`Mode`, sfmt.String(enc.Mode)),
		sfmt.KV(`Plaintext`, sfmt.String(enc.Plaintext), sfmt.KVNewLine()),
		sfmt.KV(`Ciphertext`, sfmt.Hexdump(enc.Ciphertext), sfmt.KVNewLine()),
	).StructFormat(w)
}

然後在主程序中打印這個結構體只要這樣:

    sfmt.Format(os.Stdout, enc)

最終的打印輸出如下:

Encryption {
    Key: 0xf80fe1ef1b2892e8120f0d75a1b067ce
    IV: 0xdf3ab28c39b9a452cefdd29d0a02d41d
    Tag: ( EMPTY )
    Mode: CTR
    Plaintext:
        Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et 
        dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex 
        ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 
        fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt 
        mollit anim id est laborum
    Ciphertext:
        00000000  a3 b1 24 93 30 88 31 dc  3d 32 ea 7b 06 be 86 ba  |..$.0.1.=2.{....|
        00000010  31 e7 72 5a 9c 72 9c a4  ea 4b a1 5f 95 47 ad fc  |1.rZ.r...K._.G..|
        00000020  5e 40 15 62 4e 35 e4 e5  fe 53 24 68 8a 77 0c b7  |^@.bN5...S$h.w..|
        00000030  f9 8c ac 4d df 66 64 86  5a ba cc eb 68 16 43 d2  |...M.fd.Z...h.C.|
        00000040  84 37 af 4b e4 47 2a 84  1e 0a 1d ff 7d 76 00 47  |.7.K.G*.....}v.G|
        00000050  e6 51 96 fa 19 96 1b a9  04 42 cf bc c0 88 71 03  |.Q.......B....q.|
        00000060  e7 49 4c 0e f5 0f 1f 8c  69 3e 65 07 90 56 c7 a7  |.IL.....i>e..V..|
        00000070  f1 95 d6 b2 bb b8 3a 8e  66 bc 0a bc 20 79 cc a8  |......:.f... y..|
        00000080  a8 65 23 5c cf e9 4d 76  6e 13 f0 64 13 5a 08 ab  |.e#\..Mvn..d.Z..|
        00000090  df 90 83 ce 01 5a 5e aa  3d 0e 46 76 04 99 e0 16  |.....Z^.=.Fv....|
        000000a0  b2 76 be 28 4b e2 12 f5  dc 60 5b 67 e6 aa 2c 70  |.v.(K....`[g..,p|
        000000b0  49 7e 55 5c 1c 0a 60 36  69 97 f3 29 04 90 5f 60  |I~U\..`6i..).._`|
        000000c0  d7 65 60 a8 c0 76 d1 e3  42 c1 0e 63 f9 e0 72 ec  |.e`..v..B..c..r.|
        000000d0  95 97 44 9c ea f3 21 50  5b a7 3e de e3 8c 7d ee  |..D...!P[.>...}.|
        000000e0  cc 64 74 77 b5 cc dc 8e  e5 97 63 6b 7f 56 5c 5d  |.dtw......ck.V\]|
        000000f0  7f 6f 68 8b 5b a6 37 e8  f5 b7 fc f9 96 b6 c0 b7  |.oh.[.7.........|
        00000100  17 3d d1 fe 11 6d 39 39  37 be 1f e7 1f 13 99 9f  |.=...m997.......|
        00000110  db 2f cc c8 46 92 85 12  b0 41 0a 14 d1 8f 46 f4  |./..F....A....F.|
        00000120  5e ac 1c f5 a7 40 e1 fe  51 c4 e0 1a 12 37 ee 97  |^[email protected]..|
        00000130  63 47 50 4c fa 87 4b c6  e7 da 31 4d 13 62 39 b2  |cGPL..K...1M.b9.|
        00000140  fe 3a b5 96 53 90 d5 06  8d 57 22 7f c3 cd 75 99  |.:..S....W"...u.|
        00000150  ff 45 51 10 bc 03 0b b8  82 5d 77 d6 b5 a2 81 00  |.EQ......]w.....|
        00000160  b4 78 e3 d3 cd ad 37 f3  76 f8 40 2d 5e f1 17 01  |.x....7.v.@-^...|
        00000170  d0 13 4b be 4c 7e 1d 67  54 d0 c6 19 7a 91 91 c7  |..K.L~.gT...z...|
        00000180  5f ad d5 99 4e 03 79 7e  86 9a 38 bf 12 1a a0 6e  |_...N.y~..8....n|
        00000190  32 b2 41 da 11 4e fc 3c  61 5a 02 95 2b 6b 15 c6  |2.A..N.<aZ..+k..|
        000001a0  a9 cd c2 56 c1 c3 2a 43  c6 bf fc 0b 46 e8 c7 ee  |...V..*C....F...|
        000001b0  ea 64 9b 08 42 6a 47 9d  4c ce e7 95 
}

可以看到結構體內部的鍵值都被縮進處理了,增加了可讀性。如果有更多層級的結構體嵌套,縮進也會自動增加。二進制內容也可以有更豐富的輸出樣式比如 Hexdump 。模塊還提供了一個 Writer 實現可以方便地創造更多新的樣式構建函數。

範例代碼: https://go.dev/play/p/bPjR5OdA_et


Golang Go语言中結構體格式化打印工具

更多关于Golang Go语言中結構體格式化打印工具的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

有意思

更多关于Golang Go语言中結構體格式化打印工具的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


GoStringer 不帶縮進管理,雖然可以自己實現一個 helper 專門處理縮進。
但是如果數據量大返回 String 會很慢而且耗內存。Writer 實現走流數據可以更好處理大量的輸出。

不错,可以自定义打印,我比较常用 github.com/kr/pretty 主要是地址比较好记

我也是之前一直在用 github.com/kr/pretty 但是當需求稍微複雜一點就感覺到不夠用了

好东西,开发的时候比较方便

在Golang中,结构体格式化打印是日常开发和调试中的重要任务。以下是一些常用的结构体格式化打印工具和方法:

1. 使用fmt包

Golang内置的fmt包提供了强大的格式化打印功能。常用的格式化占位符包括:

  • %v:打印结构体的值,不包含字段名。
  • %+v:打印结构体的值,包含字段名。
  • %#v:打印结构体的值,包含字段名和包路径(通常不建议在生产代码中使用,因为暴露包路径不利于代码的可读性和维护性)。

2. 实现Stringer接口

通过为结构体实现fmt.Stringer接口,可以自定义结构体的字符串表示形式。这种方法在需要多次打印结构体时特别有用,因为它避免了重复的格式化代码。

3. 第三方工具:godebug/pretty

对于复杂的数据结构,godebug/pretty是一个非常有用的美化打印工具。它提供了紧凑和详细两种输出格式,并支持高度可定制的输出选项。使用godebug/pretty可以更加直观地理解和分析数据结构,显著提升调试和测试的效率。

综上所述,Golang提供了多种方法和工具来实现结构体的格式化打印。根据具体需求选择合适的方法和工具,可以更加高效地进行开发和调试工作。

回到顶部