Ejercicio: Uso de structs

Completado

Hay ocasiones en las que es necesario representar una colección de campos en una estructura. Por ejemplo, si necesita escribir un programa de nóminas, debe usar una estructura de datos de empleados. En Go, puede usar structs para agrupar distintos campos que podrían formar un registro.

Un struct de Go es otro tipo de datos que puede contener cero o más campos de tipos arbitrarios y representarlos como una sola entidad.

En esta sección vamos a ver por qué los structs son esenciales y cómo usarlos.

Declaración e inicialización de un struct

Para declarar un struct, debe usar la palabra clave struct junto con la lista de campos y los tipos que quiere que tenga el nuevo tipo de datos. Por ejemplo, para definir un struct de empleados, podría usar el código siguiente:

type Employee struct {
    ID        int
    FirstName string
    LastName  string
    Address   string
}

Luego puede declarar una variable con el nuevo tipo como haría normalmente con otros tipos, así:

var john Employee

Si quiere declarar e inicializar una variable al mismo tiempo, puede hacerlo así:

employee := Employee{1001, "John", "Doe", "Doe's Street"}

Observe que debe especificar un valor para cada uno de los campos del struct. Pero eso podría ser problemático a veces. Como alternativa, puede ser más específico sobre los campos que quiere inicializar en un struct:

employee := Employee{LastName: "Doe", FirstName: "John"}

Observe que, desde la instrucción anterior, el orden en que se asignan los valores a cada campo no importa. Además, no importa si no se especifica ningún valor para ningún otro campo. Go asigna un valor predeterminado en función del tipo de datos del campo.

Para acceder a campos individuales de un struct, puede usar la notación de punto (.), como en este ejemplo:

employee.ID = 1001
fmt.Println(employee.FirstName)

Por último, puede usar el operador & para generar un puntero al struct, como se muestra en el código siguiente:

package main

import "fmt"

type Employee struct {
    ID        int
    FirstName string
    LastName  string
    Address   string
}

func main() {
    employee := Employee{LastName: "Doe", FirstName: "John"}
    fmt.Println(employee)
    employeeCopy := &employee
    employeeCopy.FirstName = "David"
    fmt.Println(employee)
}

Al ejecutar el código anterior se ve el siguiente resultado:

{0 John Doe }
{0 David Doe }

Observe cómo el struct se vuelve mutable cuando se usan punteros.

Inserción de structs

Los structs de Go permiten insertar un struct dentro de otro. Hay ocasiones en las que se quiere reducir la repetición y reutilizar un struct común. Por ejemplo, imagine que quiere refactorizar el código anterior para tener un tipo de datos para un empleado y otro para un contratista. Puede tener un struct Person con campos comunes, como en este ejemplo:

type Person struct {
    ID        int
    FirstName string
    LastName  string
    Address   string
}

Luego puede declarar otros tipos que insertan un tipo Person, como Employee y Contractor. Para insertar otro struct, cree un nuevo campo, como en este ejemplo:

type Employee struct {
    Information Person
    ManagerID   int
}

Pero para hacer referencia a un campo del struct Person, debe incluir el campo Information de una variable de empleado, como en este ejemplo:

var employee Employee
employee.Information.FirstName = "John"

Si va a refactorizar código como estamos haciendo ahora, eso interrumpiría el código. Como alternativa, basta con incluir un nuevo campo con el mismo nombre que el struct que está insertando, como en este ejemplo:

type Employee struct {
    Person
    ManagerID int
}

Como demostración, puede usar el código siguiente:

package main

import "fmt"

type Person struct {
    ID        int
    FirstName string
    LastName  string
    Address   string
}

type Employee struct {
    Person
    ManagerID int
}

type Contractor struct {
    Person
    CompanyID int
}

func main() {
    employee := Employee{
        Person: Person{
            FirstName: "John",
        },
    }
    employee.LastName = "Doe"
    fmt.Println(employee.FirstName)
}

Observe cómo se accede al campo FirstName desde un struct Employee sin tener que especificar el campo Person, ya que está insertando todos sus campos automáticamente. Pero, al inicializar un struct, debe ser específico con respecto al campo al que quiere asignar un valor.

Codificación y descodificación de structs con JSON

Por último, puede usar structs para codificar y descodificar datos en JSON. Go tiene una excelente compatibilidad con el formato JSON y ya está incluido en los paquetes de biblioteca estándar.

También puede hacer cosas como cambiar el nombre de un campo del struct. Por ejemplo, imagine que no quiere que el resultado JSON muestre FirstName, sino simplemente name, o que omita los campos vacíos. Puede usar etiquetas de campo como se muestra en este ejemplo:

type Person struct {
    ID        int    
    FirstName string `json:"name"`
    LastName  string
    Address   string `json:"address,omitempty"`
}

Luego, para codificar un struct en JSON, use la función json.Marshal. Para descodificar una cadena JSON en una estructura de datos, use la función json.Unmarshal. En este ejemplo se une todo: se codifica una matriz de empleados en JSON y se descodifica el resultado en una nueva variable:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    ID        int
    FirstName string `json:"name"`
    LastName  string
    Address   string `json:"address,omitempty"`
}

type Employee struct {
    Person
    ManagerID int
}

type Contractor struct {
    Person
    CompanyID int
}

func main() {
    employees := []Employee{
        Employee{
            Person: Person{
                LastName: "Doe", FirstName: "John",
            },
        },
        Employee{
            Person: Person{
                LastName: "Campbell", FirstName: "David",
            },
        },
    }

    data, _ := json.Marshal(employees)
    fmt.Printf("%s\n", data)

    var decoded []Employee
    json.Unmarshal(data, &decoded)
    fmt.Printf("%v", decoded)
}

Al ejecutar el código anterior se ve el siguiente resultado:

[{"ID":0,"name":"John","LastName":"Doe","ManagerID":0},{"ID":0,"name":"David","LastName":"Campbell","ManagerID":0}]
[{{0 John Doe } 0} {{0 David Campbell } 0}]