Dev (Back & Front)ARTIGO

Um JSON lint em Golang

Neste artigo, o autor mostra como ele usou o retorno de erro do json.Unmarshal para gerar uma mensagem de erro mais útil e completa com direito a indicar o erro com uma setinha.

Usamos o retorno de erro do json.Unmarshal para gerar uma mensagem de erro mais útil e completa, com direito a indicar o erro com uma setinha e tudo.

Um JSON lint em Golang

Esse é um pequeno utilitário de linha de comando para validar e formatar JSON, que também pode ser usado como pacote. A ideia inicial era criar um parser para validar o JSON, mas não foi necessário.

O próprio Go traz todas as informações que precisamos no erro, inclusive o offset do erro, e daí foi simples mostrar os erros de forma mais completa, inclusive indicando com uma seta no local exato do erro.

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"

	"github.com/crgimenes/goconfig"
	"github.com/gosidekick/jsonlint"
)

func printError(a ...interface{}) {
	_, err := fmt.Fprintf(os.Stderr, "\x1b[91m%v\033[0;00m\n", a...)
	if err != nil {
		fmt.Println(err)
	}
}

func printIndicator(a ...interface{}) {
	_, err := fmt.Fprintf(os.Stderr, "\x1b[96m%v\033[0;00m\n", a...)
	if err != nil {
		fmt.Println(err)
	}
}

func main() {
	type configFlags struct {
		Input  string `json:"i" cfg:"i" cfgDefault:"stdin" cfgHelper:"input from"`
		Output string `json:"o" cfg:"o" cfgDefault:"stdout" cfgHelper:"output to"`
	}

	cfg := configFlags{}
	goconfig.PrefixEnv = "JSON_LINT"
	err := goconfig.Parse(&cfg)
	if err != nil {
		printError(err)
		os.Exit(-1)
	}
	var j []byte

	if cfg.Input == "stdin" {
		j, err = ioutil.ReadAll(os.Stdin)
		if err != nil {
			printError(err)
			os.Exit(-1)
		}
	} else {
		j, err = ioutil.ReadFile(cfg.Input)
		if err != nil {
			printError(err)
			os.Exit(-1)
		}
	}
	var m interface{}
	err = json.Unmarshal(j, &m)
	if err != nil {
		out, offset := jsonlint.ParseJSONError(j, err)
		printError(out)
		if offset > 0 {
			out = jsonlint.GetErrorJSONSource(j, offset)
			printIndicator(out)
		}
		os.Exit(-1)
	}
	j, err = json.MarshalIndent(m, "", "\t")
	if err != nil {
		printError(err)
		os.Exit(-1)
	}
	if cfg.Output == "stdout" {
		fmt.Println(string(j))
		return
	}
	err = ioutil.WriteFile(cfg.Output, j, 0644)
	if err != nil {
		printError(err)
	}
}

Aqui temos o código do nosso utilitário, que também serve de exemplo de como utilizar nosso pacote de tratamento de erro. Basicamente, basta passar o erro da função json.Unmarshal para a função jsonlint.ParseJSONError e o retorno vai ser uma string contendo uma descrição mais detalhada do erro e o offset de onde esse erro ocorreu.

Você pode passar o JSON que originou o erro e esse offset para a função jsonlint.GetErrorJSONSource, e ela vai gerar uma string contendo a parte do código com problemas, junto com uma seta apontando para o primeiro caractere que o parser não conseguiu processar.

Esse utilitário, assim como muitos outros, está sendo construído no nosso repositório do GoSidekick. A ideia é criar um conjunto de utilitários que usamos no nosso dia a dia desenvolvendo código Go e também outras linguagens de programação.

Nossos encontros ocorrem todas as quintas-feiras, às 22h00. Para participar, entre no canal de Go no Slack e procure por #brazil.

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