Cómo convertir structs y arrays a JSON

Las funciones del módulo encoding/json también permiten transformar estructuras arbitrarias y slices a JSON, generando objetos y arrays JSON donde los campos se corresponden con los valores almacenados en los objetos de Go. Podemos hacer uso de las anotaciones específicas del paquete json para alterar la forma en la que se asignan los nombres de los campos.

Como ya contaba en otra lección, la función Marshal del paquete encoding/json de Go permite convertir a JSON valores. Anteriormente contaba cómo codificar tipos primitivos, como cadenas o números, pero en la mayoría de ocasiones, el poder de esta función lo tendremos para codificar estructuras y tipos de datos avanzados.

Codificar estructuras a JSON es fácil

Directamente la función Marshal permitirá recibir como parámetro estructuras. Esto es porque esta función usará reflexión, una característica del lenguaje de programación Go que permite sacar información sobre la forma en la que está escrito el programa desde el propio programa. Por ejemplo, sacar el nombre y el tipo de cada uno de los campos desde el propio programa.

Si le pasamos una estructura de datos o una referencia a una estructura de datos, esta función usará la reflexión para ver como se llama cada campo y de ahí sacará el nombre de cada key del JSON. Por ejemplo, si tuviese declarada una estructura como esta:

type Persona struct {
  Name string
  Surname string
  Age int
}

Entonces podría tratar de codificar un valor de este tipo:

var persona := Persona{Nombre: "Patricia", Surname: "González", Age: 24}
value, err := json.Marshal(&persona)

Suponiendo que err valga nulo, entonces value será la codificación a bytestring de la cadena JSON {"Nombre": "Patricia", "Surname": "González", "Age": 24}. El nombre de los campos se infiere automáticamente.

Condiciones importantes para codificar estructuras a JSON

Es importante que los campos sean públicos. En el lenguaje de programación Go, cuando el nombre de un campo comienza con minúscula, se considera privado; y cuando comienza con mayúsculas, se considera público.

La función Marshal no va a poder sacar ni el valor ni información sobre un campo que no tenga visibilidad abierta, así que si tu estructura tiene campos que empiezan por minúscuals y que son, por lo tanto, de visibilidad privada, no van a ser codificados, ya que no serán accesibles desde fuera.

type Persona struct {
  name string
  age  int
}

func main() {
  p := Persona{name: "Diego", age: 48}
  value, _ := json.Marshal(&p)
  fmt.Println(string(value))
}

Contrariamente a lo que podría parecer, este programa escribirá la cadena {} por pantalla, en vez de la información de esta persona. Esto es porque el campo es privado al ser name y no Name.

Renombrar un campo en un JSON

Pero claro, no todo el mundo querrá que los nombres de sus campos empiecen por mayúscula en el JSON. Además, si se trata de un JSON en un formato que no decide la aplicación Go sino que ya está pactado, por ejemplo, porque sea un payload a mandar mediante HTTP POST a una API ya existente, tendremos que establecer cuidadosamente el nombre de esos campos.

Para modificar el nombre que la función Marshal le asigna a un campo JSON podemos usar una anotación. Las anotaciones son cadenas de caracteres que se escriben junto a cada uno de los campos de la estructura. Estas cadenas de caracteres tienen que tener la siguiente forma:

  • Empiezan por json:
  • A continuación especifican entre comillas el nombre del campo JSON.
  • Como es una cadena de caracteres que llevan comillas, lo típico será que usemos backticks para declarar la anotación.

Por ejemplo:

type Persona struct {
  Name string    `json:"first_name"`
  Surname string `json:"last_name"`
  Age int        `json:"age"`
}

Al hacer esto, le estamos indicando que el nombre del campo Name en JSON deberá ser "first_name", el campo Surname debe ser codificado como "last_name" y el campo Age debe ser codificado como "age".

Codificar ahora otro valor de este tipo,

var persona := Persona{Nombre: "Patricia", Surname: "González", Age: 24}
value, err := json.Marshal(&persona)

Generará un JSON en este ejemplo como {"first_name": "Patricia", "last_name": "González", "age": 24}.

Codificar arrays y slices

Los arrays y los slices se codifican a JSON del mismo modo. Si le pasamos a la función Marshal un identificador de este tipo, lo que generaremos será un array JSON. Tanto si es un array de un primitivo como puede ser un []int o si es un array de estructuras como un []Persona, el comportamiento será transparente.

El array []int{2, 4, 5} será codificado a JSON como [2, 4, 5]. El array []string{"invierno", "primavera", "verano", "otoño"} es codificado a JSON como ["invierno", "primavera", "verano", "otoño"].

Si se tratan de arrays o slices a estructuras, se serializan con el mismo procedimiento que el que hemos visto antes, así que queda como [{"estacion": "invierno"}, {"estacion": "verano"}].

Codificar una estructura dentro de otra

Como nota al pie, aclarar también que una estructura puede tener un campo de otro tipo que también sea una estructura. Lo que ocurrirá será que habrá un objeto JSON como valor de otro.

En este ejemplo completo codifico una estructura donde uno de los campos es un array de otra estructura como tipo.

type Occupation struct {
  Employed bool   `json:"employed"`
  Position string `json:"position"`
}

type Persona struct {
  Name    string       `json:"first_name"`
  Surname string       `json:"last_name"`
  Age     int          `json:"age"`
  Job     []Occupation `json:"job"`
}

func main() {
  p := Persona{
    Name:    "Juan",
    Surname: "Perez",
    Age:     30,
    Job: []Occupation{
      {Employed: true, Position: "Engineer"},
      {Employed: true, Position: "Contable"},
    },
  }

  convers, err := json.Marshal(p)
  if err != nil {
    panic(err)
  }

  str := string(convers)
  fmt.Println(str)
}

Esto genera el siguiente JSON:

{
  "first_name": "Juan",
  "last_name": "Perez",
  "age": 30,
  "job": [
    {"employed": true, "position": "Engineer"},
    {"employed": true, "position": "Contable"}
  ]
}
Lista de reproducción
  1. 1
    Cómo serializar y deserializar valores entre Go y JSON
    10 minutos
  2. 2
    Cómo convertir structs y arrays a JSON
    9 minutos