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

Вам когда-нибудь приходилось изменять неструктурированные данные JSON в Go? Возможно, вам приходилось удалять пароль и все поля из черного списка, переименовывать ключи из camelCase в snake_case или преобразовывать все числовые идентификаторы в строки, потому что JavaScript не любит int64 ? Если вашим решением было демаршалировать все в map[string]any с помощью encoding/json , а затем маршалировать обратно... ну, давайте посмотрим правде в глаза, это далеко не эффективно!


Что, если бы вы могли перебирать данные JSON, захватывать путь каждого элемента и решать, что именно с ним делать, на лету?


Да! У меня хорошие новости! С новой функцией итератора в Go 1.23 появился лучший способ итерации и манипулирования JSON. Встречайте ezpkg.io/iter.json — вашего мощного и эффективного помощника для работы с JSON в Go.


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

Вы можете реконструировать или отформатировать данные 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 👉 } 👉 } ----------

4. Добавление номеров строк

В этом примере мы добавляем номера строк в вывод 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 }

5. Добавление комментариев

Поместив 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

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. Идентификаторы слишком большие, и 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. Мне нравится учиться и видеть лучшую версию себя каждый день. Иногда я запускаю новые проекты с открытым исходным кодом. Делюсь знаниями и мыслями во время своего путешествия.


Пост также опубликован на olivernguyen.io .