Dev (Back & Front)ARTIGO

JSON: criando seu próprio Marshal e Unmarshal

Continuando a conversa sobre interfaces e sobre manipulação de JSON, o autor apresenta um recurso para melhorar a comunicação de sistemas com o PostgresQL e pREST.

Continuando a conversa sobre interfaces e sobre manipulação de JSON, tem um recurso muito útil que usamos para ajudar o nosso sistema a falar melhor com o PostgresQL e pREST.

O formato de data padrão do Postgres é incompatível com o formato padrão do Go e o nosso sistema usa muito JSON tanto para mandar como receber informações do banco de dados.

Ter que lembrar toda hora de fazer o parser da data para o formato correto simplesmente não é pratico; é muito melhor ensinar o Go a lidar com data e hora no bom e velho formato ISO 8601.

Aliás, recomendo muito sempre usar ISO 8601 UTC para tudo no back-end e apenas mudar para o formato e timezone local quando for exibir para o usuário, mas isso é uma história para outro dia.

O código fonte do package Go está disponível no GitHub.

MarshalJSON

Agora veremos como o código funciona. No exemplo abaixo criamos uma struct Time e implementamos uma função MarshalJSON para ela. Na função main instanciamos uma struct com alguns dados, em seguida usamos a função json.MarshalIndent que percorre a struct e quando ela encontrar o campo Time vai usar a função que definimos e não a default do sistema.

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Time struct {
	time.Time
}

const layout = "2006-01-02T15:04:05.999999"

func (t Time) MarshalJSON() ([]byte, error) {
	return []byte(fmt.Sprintf(`"%s"`, t.Time.Format(layout))), nil
}

func main() {
	data := struct {
		Name string
		Time Time
	}{
		Name: "teste",
		Time: Time{time.Now().Add(time.Millisecond * time.Duration(54321))},
	}

	json, err := json.MarshalIndent(data, "", "\t")
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(string(json))
}

Exemplo de retorno usando The Go Playground – veja que o formato da data obedece a nossa função.

{
	"Name": "teste",
	"Time": "2009-11-10T23:00:54.321"
}

UnmarshalJSON

No próximo exemplo faremos o contrário: agora definiremos uma função UnmarshalJSON para nossa struct. Veja o exemplo:

package main

import (
	"encoding/json"
	"fmt"
	"time"
)

type Time struct {
	time.Time
}

const layout = "2006-01-02T15:04:05.999999"

func (t *Time) UnmarshalJSON(b []byte) (err error) {
	if b[0] == '"' && b[len(b)-1] == '"' {
		b = b[1 : len(b)-1]
	}
	if string(b) == `null` {
		*t = Time{}
		return
	}
	t.Time, err = time.Parse(layout, string(b))
	return
}

func main() {
	data := struct {
		Name string
		Time Time
	}

	b := []byte(`{"Name": "teste", "Time": "2009-11-10T23:00:54.321"}`)

	err := json.Unmarshal(b, &data)
	if err != nil {
		fmt.Println(err)
	}

	fmt.Println(data.Time.String())
}

Quando o sistema recebe o array de bytes para fazer parse e popular a struct, ele percorre os dados e ao encontrar o campo Time o pacote usa a nossa função UnmarshalJSON no lugar da default do sistema, assim conseguimos ler corretamente os dados mesmo não sendo o padrão do sistema.

Além de formatar os dados corretamente, também podemos usar esse recurso para outras tarefas. Por exemplo, é possível percorrer os campos verificando as informações de um determinado tipo e retornar um erro caso encontre algum dado inválido; é uma forma de validação de dados que trabalha internamente no parser do tipo e pode ser muito útil.

Só devemos tomar cuidado porque pode acabar ocultando de onde o erro está vindo, então escreva boas mensagens de erro. No exemplo do manual do próprio pacote json, o exemplo é um contador que conta animais em um zoológico.

Links úteis

Código fonte dos exemplos de hoje:

Cesar GimenesAutor

Trabalha com tecnologia desde a década de 90. Já atuou na área de educação e participou de projetos de mobilidade de grande volume para laboratórios farmacêuticos. Criou games tanto para PC, como para iOS. Hoje está direcionando seus esforços em plataformas de Sistemas Embarcados, IoT, microservices e cloud computing. É um entusiasta de tecnologias como Golang e Docker.

Ver perfil