Introducción al Trabajo con JSON en Go
JSON (Notación de Objetos JavaScript) es un formato ligero de intercambio de datos ampliamente utilizado en aplicaciones web, APIs y microservicios. El lenguaje Go (Golang) proporciona una potente biblioteca integrada encoding/json que facilita el análisis (deserialización) y la serialización de datos. En este artículo, examinaremos a fondo cómo trabajar con JSON en Go, cubriendo las funciones principales, estructuras y mejores prácticas.
Fundamentos de Serialización y Deserialización
La serialización es el proceso de convertir una estructura de datos de Go (por ejemplo, un struct o slice) en una cadena JSON. La deserialización es el proceso inverso: convertir JSON en una estructura de Go.
Serialización (Marshal)
Para la serialización se utiliza la función json.Marshal(). Toma un valor de Go y devuelve un segmento de bytes ([]byte) con la representación JSON.
package main
import ( "encoding/json" "fmt")
type User struct { Name string `json:"name"` Age int `json:"age"` Email string `json:"email,omitempty"`}
func main() { user := User{Name: "Alexey", Age: 30, Email: "alex@example.com"} jsonData, err := json.Marshal(user) if err != nil { fmt.Println("Serialization error:", err) return } fmt.Println(string(jsonData)) // Output: {"name":"Alexey","age":30,"email":"alex@example.com"}Observe las etiquetas json:"field_name". Permiten controlar los nombres de las claves en JSON, así como agregar opciones como omitempty (omitir el campo si está vacío).
Deserialización (Unmarshal)
Para analizar JSON en una estructura de Go se utiliza la función json.Unmarshal(). Toma un segmento de bytes y un puntero a la estructura donde se deben escribir los datos.
func main() { jsonString := `{"name":"Maria","age":25,"email":"maria@example.com"}` var user User err := json.Unmarshal([]byte(jsonString), &user) if err != nil { fmt.Println("Parsing error:", err) return } fmt.Printf("Name: %s, Age: %d, Email: %s", user.Name, user.Age, user.Email)}Importante: la estructura debe estar predefinida y sus campos deben corresponder a las claves JSON (teniendo en cuenta las etiquetas).
Trabajo con Objetos JSON Arbitrarios
A veces la estructura JSON no se conoce de antemano o es demasiado compleja. En tales casos, se puede usar map[string]interface{} o interface{}.
Uso de map[string]interface{}
func main() { jsonString := `{"name":"Ivan","age":35,"address":{"city":"Moscow","street":"Lenina"}}` var result map[string]interface{} err := json.Unmarshal([]byte(jsonString), &result) if err != nil { fmt.Println("Error:", err) return } fmt.Println(result["name"]) // Ivan fmt.Println(result["age"]) // 35 (type float64) // For nested objects, type assertion is needed address := result["address"].(map[string]interface{}) fmt.Println(address["city"]) // Moscow}La desventaja de este enfoque es la necesidad de una aserción de tipo manual (.(map[string]interface{})), lo que puede causar un pánico si los tipos no coinciden.
Uso de json.RawMessage
Si necesita diferir el análisis de parte del JSON, utilice json.RawMessage. Esto le permite primero analizar la estructura principal y luego procesar los datos anidados por separado.
type Response struct { Status string `json:"status"` Data json.RawMessage `json:"data"`}
func main() { jsonString := `{"status":"ok","data":{"user":"Peter","score":100}}` var resp Response json.Unmarshal([]byte(jsonString), &resp)
// Now Data can be parsed separately var userData map[string]interface{} json.Unm