Вам когда-нибудь приходилось изменять неструктурированные данные JSON в Go? Возможно, вам приходилось удалять пароль и все поля из черного списка, переименовывать ключи из camelCase
в snake_case
или преобразовывать все числовые идентификаторы в строки, потому что JavaScript не любит int64
? Если вашим решением было демаршалировать все в map[string]any
с помощью encoding/json
, а затем маршалировать обратно... ну, давайте посмотрим правде в глаза, это далеко не эффективно!
Что, если бы вы могли перебирать данные JSON, захватывать путь каждого элемента и решать, что именно с ним делать, на лету?
Да! У меня хорошие новости! С новой функцией итератора в Go 1.23 появился лучший способ итерации и манипулирования JSON. Встречайте ezpkg.io/iter.json — вашего мощного и эффективного помощника для работы с JSON в Go.
Учитывая, что у нас есть файл 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 |
Используйте 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 } ] }
Вы можете реконструировать или отформатировать данные JSON, отправив их ключ и значения в Builder
. См. 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 👉 } 👉 } ----------
В этом примере мы добавляем номера строк в вывод JSON, добавляя b.WriteNewline()
перед вызовом fmt.Fprintf()
. См. 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 }
Поместив fmt.Fprintf(comment)
между b.WriteComma()
и b.WriteNewline()
, вы можете добавить комментарий в конец каждой строки. См. 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
Существуют 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
Объединив 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 } }
Это пример редактирования значений в данных JSON. Предположим, что мы используем числовые идентификаторы для нашего API. Идентификаторы слишком большие, и JavaScript не может их обработать. Нам нужно преобразовать их в строки. См. examples/08.number_id и order.json .
Выполните итерацию по данным JSON, найдите все поля _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)
Это добавит кавычки к идентификаторам номеров:
{ "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 без необходимости полного анализа данных. Хотя он все еще находится на ранней стадии разработки и есть место для большего количества функций, он уже хорошо работает для многих распространенных случаев использования.
Если у вас есть особые требования или идеи по улучшению, не стесняйтесь обращаться ко мне — я буду рад услышать ваши отзывы и помочь в реализации ваших вариантов использования! 🥳
Меня зовут Оливер Нгуен. Я инженер-программист, работающий с Go и JS. Мне нравится учиться и видеть лучшую версию себя каждый день. Иногда я запускаю новые проекты с открытым исходным кодом. Делюсь знаниями и мыслями во время своего путешествия.
Пост также опубликован на