Вам коли-небудь доводилося змінювати неструктуровані дані 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. Мені подобається навчатися та бачити кращу версію себе щодня. Час від часу створюйте нові проекти з відкритим кодом. Поділіться знаннями та думками під час моєї подорожі.
Пост також опубліковано на