El objetivo de esta pequeña saga de vídeos es mostrar las funciones que tiene la biblioteca estandar del lenguaje de programación Go para serializar y deserializar.
Serializar será el proceso por el cual convertiremos un valor de Go, como un tipo primitivo, un array o un objeto, a una cadena de caracteres que codifica lo mismo en JSON. Por ejemplo, convertimos la cadena de caracteres "hola mundo" o la estructura Go {Pais: "Argentina", Capital: "Buenos Aires"} en el objeto JSON {"pais": "Argentina", "capital": "Buenos Aires"}.
Deserializar será el procedimiento inverso. Tomaremos una cadena de caracteres JSON que puede haber venido desde un cliente HTTP, como puede ser la cadena de caracteres [{"lenguaje": "Go"}, {"lenguaje": "Python"}] y lo convertiremos en un array Go como []{Lenguaje: "Go"}, {Lenguaje: "Python"}].
Todo esto lo podemos hacer mediante funciones que forman parte de la biblioteca estandar de Go. Utilizando el paquete encoding/json podremos hacer uso de toda esta funcionalidad. No necesariamente será necesario instalar bibliotecas adicionales para poder hacer esto.
Codificar o convertir elementos de Go a JSON
Mediante la función json.Marshal() podemos convertir un valor de Go a JSON. Esta función acepta un parámetro de tipo any, es decir, cualquier cosa, sea del tipo que sea. En la siguiente lección os mostraré cómo podéis utilizar esta función para codificar arrays y estructuras de Go.
La función json.Marshal() devuelve un par de dos elementos ([]byte, error). Si el error que hay en la segunda posición es nulo, significa que la conversión se ha hecho correctamente. En ese caso, el primer elemento contiene la representación JSON de la variable que le hayamos proporcionado, codificado como un array de bytes en vez de como una cadena de caracteres UTF-8. Esto es conveniente si luego lo queremos mandar a algún tipo de implementación de io.Writer, como puede ser una conexión de red o un archivo, ya que el método Write de esta interfaz acepta un []byte.
Por ahora es importante aclarar que cualquier tipo que sea codificable a JSON puede ser proporcionado como parámetro, y esto incluye cadenas de caracteres, valores lógicos y numéricos. Con el siguiente ejemplo os lo voy a mostrar. Supongamos que tengo una variable llamada valor que codifica algo que se quiera convertir a JSON, y que en este caso, es de tipo string. Se puede codificar igualmente usando la función json.Marshal:
valor := "hola"
cod, err := json.Marshal(valor)
Tendré que revisar lo que vale el error, y sólo me puedo fiar de la codificación si no hay error. Dicho sea de otro modo, si err no es nulo, entonces es que ha habido un fallo de codificación. Los errores de codificación se pueden producir, por ejemplo, cuando se trata de codificar un valor de un tipo no representable (como puede ser un channel).
En el siguiente ejemplo y por ir más rápido, lo que voy a hacer es simplemente lanzar un panic:
valor := "hola"
cod, err := json.Marshal(valor)
if err != nil {
panic(err)
}
str := string(cod)
fmt.Println("Se ha codificado lo siguiente", str)
Una vez más, recuerda que la codificación a JSON también es apta para primitivos. Es decir, tanto "\"hola mundo\"" (nótese la doble comilla), como "5" como "true" son cadenas de caracteres válidas que codifican una representación JSON de algo. Por lo tanto, los siguientes ejemplos también son válidos:
bool_json, _ := json.Marshal(true)
int_json, _ := json.Marshal(42)
str_json, _ := json.Marshal("Buenos días")
// Esto también vale para referencias.
real_string := "hola mundo"
ref_string := &real_string
ref_json, _ := json.Marshal(ref_string)
// Además, si la referencia vale nil, lo codifica a null.
// Ten en cuenta que no puedes usar el operador := para
// asignar un valor nulo porque impide a Go inferir el
// tipo de datos correctamente.
var input *string = nil
null_json, _ := json.Marshal(string)
Decodificar valores desde JSON
El proceso inverso nos permite transformar una representación JSON, como puede ser una cadena de caracteres o un slice de bytes que lo codifique en UTF-8, a un valor de Go. Para ello, podemos usar la función json.Unmarshal(). Esta función aceptará dos parámetros:
El primer parámetro será un slice de bytes que contiene lo que queremos representar. Al igual que pasaba en el caso anterior, es posible que tengamos el valor JSON codificado como una cadena de caracteres. Sin embargo, este método será más útil para procesar algo devuelto por el método
Readde la interfazio.Readerde Go. Dado que este método trabaja con slices de byte, la funciónUnmarshaltambién lo hace.El segundo parámetro va a ser una referencia a la variable donde queramos que la función vuelque lo que ha leído. Por ejemplo, si vamos a leer una cadena de caracteres, le pasaríamos un puntero a la cadena, para que tras la llamada, el valor de esa cadena sea lo leído. Si vamos a leer un objeto, será un puntero al struct donde queremos que se vuelque.
La función Unmarshal devuelve un error. Este error será nulo si se ha leído bien. Cuando sea nulo, podemos fiarnos de lo leído y por lo tanto podemos usar la variable que previamente hemos pasado como referencia para poder utilizar su valor. Si esta función devuelve un error (algo que puede ocurrir, por ejemplo, si la cadena JSON estaba corrupta, como puede ser que falte una comilla), entonces lo más probable es que no podamos fiarnos de lo que vale la variable pasada como referencia ni consumir la información leída en general.
En el siguiente ejemplo, uso la función Unmarshal para leer un valor JSON que, de nuevo, es primitivo. Una vez más, una cadena de caracteres como "42" es un valor JSON válido, que codifica el entero 42; o una cadena de caracteres como "\"hola mundo\" codifica el string "hola mundo" en formato JSON.
Además, hay que tener en cuenta que si no le quiero asignar un valor inicial a la variable donde voy a volcar el valor (algo respetable si no voy a usar el valor preasignado para nada), entonces debería usar var y darle un tipo. Por ejemplo:
var res int
var data = []byte("42")
if err := json.Unmarshal(data, &res) {
panic(err)
}
fmt.Println("He leído el valor", res)
Un ejemplo en el que no se haría bien la conversión sería si el tipo no cuadra. Si yo uso el siguiente trozo de código,
var res int
var data = []byte("true")
if err := json.Unmarshal(data, &res) {
panic(err)
}
fmt.Println("He leído el valor", res)
Lo que voy a obtener es un panic, y un error que no es nulo, ya que estoy pretendiendo que vuelque la representación del valor true en una variable, res, cuyo tipo es numérico. El error dirá algo como Cannot unmarshal bool into Go value of type int, y con razón.
Manipulación de slices y structs
En las siguientes lecciones de esta lista te mostraré cómo puedes codificar y decodificar tipos avanzados como structs o slices. Échale un vistazo si quieres saber más.