paint-brush
Go 中的新功能使迭代和操作 JSON 数据变得更容易经过@olvrng
新歷史

Go 中的新功能使迭代和操作 JSON 数据变得更容易

经过 Oliver Nguyen13m2024/12/17
Read on Terminal Reader

太長; 讀書

如果您可以循环遍历 JSON 数据、获取每个项目的路径并动态决定如何处理它,结果会怎样?
featured image - Go 中的新功能使迭代和操作 JSON 数据变得更容易
Oliver Nguyen HackerNoon profile picture

您是否曾经需要在 Go 中修改非结构化的 JSON 数据?也许您必须删除密码和所有列入黑名单的字段,将键从camelCase重命名snake_case ,或者将所有数字 ID 转换为字符串,因为 JavaScript 不喜欢int64 ?如果您的解决方案是使用encoding/json将所有内容解组到map[string]any,然后再将其编组回来……好吧,让我们面对现实吧,这远非高效!


如果您可以循环遍历 JSON 数据、获取每个项目的路径并动态地决定如何处理它,结果会怎样?


是的!我有个好消息!借助 Go 1.23 中的新迭代器功能,可以更好地迭代和操作 JSON。了解ezpkg.io/iter.json — 您在 Go 中使用 JSON 的强大而高效的伙伴。


1. 迭代 JSON

假设我们有一个alice.json文件:

 { "name": "Alice", "age": 24, "scores": [9, 10, 8], "address": { "city": "The Sun", "zip": 10101 } }


首先,我们使用for range Parse()迭代 JSON 文件,然后打印每个项目的路径、键、令牌和级别。请参阅examples/01.iter

 package main import ( "fmt" "ezpkg.io/errorz" iterjson "ezpkg.io/iter.json" ) func main() { data := `{"name": "Alice", "age": 24, "scores": [9, 10, 8], "address": {"city": "The Sun", "zip": 10101}}` // 🎄Example: iterate over json fmt.Printf("| %12v | %10v | %10v |%v|\n", "PATH", "KEY", "TOKEN", "LVL") fmt.Println("| ------------ | ---------- | ---------- | - |") for item, err := range iterjson.Parse([]byte(data)) { errorz.MustZ(err) fmt.Printf("| %12v | %10v | %10v | %v |\n", item.GetPathString(), item.Key, item.Token, item.Level) } }


代码将输出:

 | PATH | KEY | TOKEN |LVL| | ------------ | ---------- | ---------- | - | | | | { | 0 | | name | "name" | "Alice" | 1 | | age | "age" | 24 | 1 | | scores | "scores" | [ | 1 | | scores.0 | | 9 | 2 | | scores.1 | | 10 | 2 | | scores.2 | | 8 | 2 | | scores | | ] | 1 | | address | "address" | { | 1 | | address.city | "city" | "The Sun" | 2 | | address.zip | "zip" | 10101 | 2 | | address | | } | 1 | | | | } | 0 |

2. 构建 JSON

使用Builder构建 JSON 数据。它接受可选的缩进参数。请参阅examples/02.builder

 b := iterjson.NewBuilder("", " ") // open an object b.Add("", iterjson.TokenObjectOpen) // add a few fields b.Add("name", "Alice") b.Add("age", 22) b.Add("email", "[email protected]") b.Add("phone", "(+84) 123-456-789") // open an array b.Add("languages", iterjson.TokenArrayOpen) b.Add("", "English") b.Add("", "Vietnamese") b.Add("", iterjson.TokenArrayClose) // close the array // accept any type that can marshal to json b.Add("address", Address{ HouseNumber: 42, Street: "Ly Thuong Kiet", City: "Ha Noi", Country: "Vietnam", }) // accept []byte as raw json b.Add("pets", []byte(`[{"type":"cat","name":"Kitty","age":2},{"type":"dog","name":"Yummy","age":3}]`)) // close the object b.Add("", iterjson.TokenObjectClose) out := errorz.Must(b.Bytes()) fmt.Printf("\n--- build json ---\n%s\n", out)


这将输出带有缩进的 JSON:

 { "name": "Alice", "age": 22, "email": "[email protected]", "phone": "(+84) 123-456-789", "languages": [ "English", "Vietnamese" ], "address": {"house_number":42,"street":"Ly Thuong Kiet","city":"Ha Noi","country":"Vietnam"}, "pets": [ { "type": "cat", "name": "Kitty", "age": 2 }, { "type": "dog", "name": "Yummy", "age": 3 } ] }

3.格式化 JSON

你可以通过将其键和值发送到Builder来重建或格式化 JSON 数据。请参阅examples/03.reformat

 { // 🐝Example: minify json b := iterjson.NewBuilder("", "") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) b.AddRaw(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- minify ---\n%s\n----------\n", out) } { // 🦋Example: format json b := iterjson.NewBuilder("👉 ", "\t") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) b.AddRaw(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- reformat ---\n%s\n----------\n", out) }


第一个例子缩小了 JSON,而第二个例子在每一行上使用前缀“👉”来格式化它。

 --- minify --- {"name":"Alice","age":24,"scores":[9,10,8],"address":{"city":"The Sun","zip":10101}} ---------- --- reformat --- 👉 { 👉 "name": "Alice", 👉 "age": 24, 👉 "scores": [ 👉 9, 👉 10, 👉 8 👉 ], 👉 "address": { 👉 "city": "The Sun", 👉 "zip": 10101 👉 } 👉 } ----------

4. 添加行号

在此示例中,我们通过在fmt.Fprintf()调用之前添加b.WriteNewline()来向 JSON 输出添加行号。请参阅examples/04.line_number

 // 🐞Example: print with line number i := 0 b := iterjson.NewBuilder("", " ") for item, err := range iterjson.Parse(data) { i++ errorz.MustZ(err) b.WriteNewline(item.Token.Type()) // 👉 add line number fmt.Fprintf(b, "%3d ", i) b.Add(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- line number ---\n%s\n----------\n", out)


这将输出:

 1 { 2 "name": "Alice", 3 "age": 24, 4 "scores": [ 5 9, 6 10, 7 8 8 ], 9 "address": { 10 "city": "The Sun", 11 "zip": 10101 12 } 13 }

5. 添加评论

通过在b.WriteComma()b.WriteNewline()之间放置fmt.Fprintf(comment) ,您可以在每行末尾添加注释。请参阅examples/05.comment

 i, newlineIdx, maxIdx := 0, 0, 30 b := iterjson.NewBuilder("", " ") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) b.WriteComma(item.Token.Type()) // 👉 add comment if i > 0 { length := b.Len() - newlineIdx fmt.Fprint(b, strings.Repeat(" ", maxIdx-length)) fmt.Fprintf(b, "// %2d", i) } i++ b.WriteNewline(item.Token.Type()) newlineIdx = b.Len() // save the newline index b.Add(item.Key, item.Token) } length := b.Len() - newlineIdx fmt.Fprint(b, strings.Repeat(" ", maxIdx-length)) fmt.Fprintf(b, "// %2d", i) out := errorz.Must(b.Bytes()) fmt.Printf("\n--- comment ---\n%s\n----------\n", out)


这将输出:

 { // 1 "name": "Alice", // 2 "age": 24, // 3 "scores": [ // 4 9, // 5 10, // 6 8 // 7 ], // 8 "address": { // 9 "city": "The Sun", // 10 "zip": 10101 // 11 } // 12 } // 13

6. 过滤 JSON 并提取值

item.GetPathString()item.GetRawPath()可以获取当前项目的路径。您可以使用它们来过滤 JSON 数据。请参阅examples/06.filter_print


使用item.GetPathString()regexp的示例:

 fmt.Printf("\n--- filter: GetPathString() ---\n") i := 0 for item, err := range iterjson.Parse(data) { i++ errorz.MustZ(err) path := item.GetPathString() switch { case path == "name", strings.Contains(path, "address"): // continue default: continue } // 👉 print with line number fmt.Printf("%2d %20s . %s\n", i, item.Token, item.GetPath()) }


item.GetRawPath()path.Match()的示例:

 fmt.Printf("\n--- filter: GetRawPath() ---\n") i := 0 for item, err := range iterjson.Parse(data) { i++ errorz.MustZ(err) path := item.GetRawPath() switch { case path.Match("name"), path.Contains("address"): // continue default: continue } // 👉 print with line number fmt.Printf("%2d %20s . %s\n", i, item.Token, item.GetPath()) }


两个示例都会输出:

 2 "Alice" . name 9 { . address 10 "The Sun" . address.city 11 10101 . address.zip 12 } . address

7. 过滤 JSON 并返回新的 JSON

通过将Builder与选项SetSkipEmptyStructures(false)和过滤逻辑相结合,您可以过滤 JSON 数据并返回新的 JSON。请参阅examples/07.filter_json

 // 🦁Example: filter and output json b := iterjson.NewBuilder("", " ") b.SetSkipEmptyStructures(true) // 👉 skip empty [] or {} for item, err := range iterjson.Parse(data) { errorz.MustZ(err) if item.Token.IsOpen() || item.Token.IsClose() { b.Add(item.Key, item.Token) continue } path := item.GetPathString() switch { case path == "name", strings.Contains(path, "address"): // continue default: continue } b.Add(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- filter: output json ---\n%s\n----------\n", out)


此示例将返回仅包含过滤字段的新 JSON:

 { "name": "Alice", "address": { "city": "The Sun", "zip": 10101 } }

8. 编辑值

这是在 JSON 数据中编辑值的示例。假设我们的 API 使用数字 ID。这些 ID 太大,JavaScript 无法处理它们。我们需要将它们转换为字符串。请参阅examples/08.number_idorder.json


迭代 JSON 数据,找到所有_id字段并将数字 ID 转换为字符串:

 b := iterjson.NewBuilder("", " ") for item, err := range iterjson.Parse(data) { errorz.MustZ(err) key, _ := item.GetRawPath().Last().ObjectKey() if strings.HasSuffix(key, "_id") { id, err0 := item.Token.GetInt() if err0 == nil { b.Add(item.Key, strconv.Itoa(id)) continue } } b.Add(item.Key, item.Token) } out := errorz.Must(b.Bytes()) fmt.Printf("\n--- convert number id ---\n%s\n----------\n", out)


这将为数字 ID 添加引号:

 { "order_id": "12345678901234", "number": 12, "customer_id": "12345678905678", "items": [ { "item_id": "12345678901042", "quantity": 1, "price": 123.45 }, { "item_id": "12345678901098", "quantity": 2, "price": 234.56 } ] }

结论

ezpkg.io/iter.json包使 Go 开发人员能够精确高效地处理 JSON 数据。无论您需要遍历复杂的 JSON 结构、动态构建新的 JSON 对象、格式化或缩小数据、过滤特定字段,甚至转换值, iter.json都能提供灵活而强大的解决方案。


我很高兴与社区分享这个包,作为高效 JSON 操作的工具,无需完全解析数据。虽然它仍处于早期开发阶段,还有更多功能的空间,但它已经适用于许多常见用例。


如果您有特定的要求或改进想法,请随时联系我们——我很乐意听到您的反馈并帮助支持您的用例!🥳



作者

我是 Oliver Nguyen。一名使用 Go 和 JS 的软件工程师。我喜欢每天学习并发现自己变得更好。偶尔会衍生出新的开源项目。分享我旅途中的知识和想法。


该帖子还发布于olivernguyen.io