¿Alguna vez ha tenido que modificar datos JSON no estructurados en Go? Tal vez haya tenido que eliminar contraseñas y todos los campos de la lista negra, cambiar el nombre de las claves de camelCase
a snake_case
o convertir todos los identificadores de números en cadenas porque a JavaScript no le gusta int64
. Si su solución ha sido desagrupar todo en un map[string]any
usando encoding/json
y luego agruparlo nuevamente... bueno, seamos realistas, ¡eso está lejos de ser eficiente!
¿Qué pasaría si pudieras recorrer los datos JSON, capturar la ruta de cada elemento y decidir exactamente qué hacer con él sobre la marcha?
¡Sí! ¡Tengo buenas noticias! Con la nueva función de iterador en Go 1.23, hay una mejor manera de iterar y manipular JSON. Conozca ezpkg.io/iter.json , su compañero poderoso y eficiente para trabajar con JSON en Go.
Dado que tenemos un archivo alice.json :
{ "name": "Alice", "age": 24, "scores": [9, 10, 8], "address": { "city": "The Sun", "zip": 10101 } }
Primero, usemos for range Parse()
para iterar sobre el archivo JSON y luego imprimamos la ruta, la clave, el token y el nivel de cada elemento. Consulte 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) } }
El código generará:
| 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 |
Utilice Builder
para crear datos JSON. Acepta argumentos opcionales para sangría. Consulte 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)
Que generará el JSON con sangría:
{ "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 } ] }
Puede reconstruir o formatear datos JSON enviando su clave y valores a un Builder
. Consulte 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) }
El primer ejemplo minimiza el JSON mientras que el segundo ejemplo lo formatea con el prefijo "👉" en cada línea.
--- 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 👉 } 👉 } ----------
En este ejemplo, agregamos números de línea a la salida JSON, agregando b.WriteNewline()
antes de la llamada fmt.Fprintf()
. Consulte 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)
Esto dará como resultado:
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 }
Si coloca un fmt.Fprintf(comment)
entre b.WriteComma()
y b.WriteNewline()
, puede agregar un comentario al final de cada línea. Consulte 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)
Esto dará como resultado:
{ // 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
Existen item.GetPathString()
y item.GetRawPath()
para obtener la ruta del elemento actual. Puedes utilizarlos para filtrar los datos JSON. Consulta examples/06.filter_print .
Ejemplo con item.GetPathString()
y 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()) }
Ejemplo con item.GetRawPath()
y 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()) }
Ambos ejemplos tendrán como resultado lo siguiente:
2 "Alice" . name 9 { . address 10 "The Sun" . address.city 11 10101 . address.zip 12 } . address
Al combinar el Builder
con la opción SetSkipEmptyStructures(false)
y la lógica de filtrado, puede filtrar los datos JSON y devolver un nuevo JSON. Consulte ejemplos/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)
Este ejemplo devolverá un nuevo JSON con solo los campos filtrados:
{ "name": "Alice", "address": { "city": "The Sun", "zip": 10101 } }
Este es un ejemplo de edición de valores en datos JSON. Supongamos que estamos usando identificadores numéricos para nuestra API. Los identificadores son demasiado grandes y JavaScript no puede manejarlos. Necesitamos convertirlos en cadenas. Consulte examples/08.number_id y order.json .
Itere sobre los datos JSON, busque todos los campos _id
y convierta los identificadores numéricos en cadenas:
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)
Esto agregará comillas a los identificadores de números:
{ "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 } ] }
El paquete ezpkg.io/iter.json permite a los desarrolladores de Go manejar datos JSON con precisión y eficiencia. Ya sea que necesite iterar a través de estructuras JSON complejas, crear nuevos objetos JSON de forma dinámica, formatear o minimizar datos, filtrar campos específicos o incluso transformar valores, iter.json ofrece una solución flexible y poderosa.
Me entusiasma compartir este paquete con la comunidad como una herramienta para la manipulación eficaz de JSON sin la necesidad de analizar por completo los datos. Si bien aún se encuentra en una etapa temprana de desarrollo y hay espacio para más funciones, ya funciona bien para muchos casos de uso comunes.
Si tienes requisitos específicos o ideas para mejorar, no dudes en comunicarte conmigo. ¡Me encantaría conocer tus comentarios y ayudarte a respaldar tus casos de uso! 🥳
Soy Oliver Nguyen, ingeniero de software que trabaja con Go y JS. Disfruto aprendiendo y viendo una mejor versión de mí mismo cada día. Ocasionalmente, desarrollo nuevos proyectos de código abierto. Comparto conocimientos y pensamientos durante mi recorrido.
La publicación también se publica en