gophers in Gotham
CSV para JSON com linguagem GO é uma grande aventura para o Gopher!
Ao trabalho!
(link original da imagem)

No último texto, escrevi sobre testes na linguagem GO, nesse texto, será apresentado um exemplo de como como converter um arquivo CSV para JSON em linguagem GO.

Dados reais para testes

Para projetos que estão no meu backlog pessoal  (e que ainda não vou contar), preciso de uma certa quantidade de dados para fazer uns testes. Dessa forma, uma boa ideia é trabalhar com uma amostra dos arquivos do IMDb, conhecida como IMDB-5000. Ela tem uma quantidade razoável de registros, com dados que possibilitam trabalhar bem tanto em bancos de dados relacionais como NoSQL (meu foco agora são os NoSQL).

Assim, quando se precisa trabalhar com uma maior diversidade e quantidade de dados, um local fácil de achar coleções é o kaggle, plataforma dedicada a desafios e competições no campo da ciência de dados, na qual estão disponíveis, além dessa amostra, muitos outros conjuntos.

Lendo o arquivo

Antes de mais nada, o primeiro desafio nessa jornada maior é pegar esses dados, que estão em arquivos CSV, e transformá-los em objetos JSON.

Antes de mais nada, deve-se ler o arquivo CSV:

package main

import (
    "encoding/csv"
    "fmt"
    "io"
    "os"
)

func main() {
    f, _ := os.Open("fixture/movie_metadata.csv")
    r := csv.NewReader(f)

    for {
        record, err := r.Read()
		if err == io.EOF {
			break
		}

        fmt.Println(record[11])
    }
}

Primeiramente, o arquivo é aberto e o leitor de CSV é inicializado, então o arquivo é lido até o fim, imprimindo o campo correspondente ao título.

Módulos padrão da GOLANG

De acordo com o KISS, foram usados apenas módulos padrão:


encodig/csv

Com esse módulo, lê-se conteúdo CSV.

fmt

O bom e velho amigo responsável pelo output formatado.

io

Aqui se encontra a interface io.Reader.

os

Nesse módulo está o necessário para lidar com o sistema operacional. É desse pacote que vem a função Open, que implementa os métodos para a leitura do arquivo.


Inicialmente, Nessa primeira versão fiz o mínimo possível, deixando de lado coisas como passagem do nome do arquivo, verificar se ele existe e verificar se a entrada lida é válida.

A execução desse código gera uma saída que começa com:

go run loadscv.go | head
movie_title
Avatar 
Pirates of the Caribbean: At World's End 
Spectre 
The Dark Knight Rises 
Star Wars: Episode VII - The Force Awakens             
John Carter 
Spider-Man 3 
Tangled 
Avengers: Age of Ultron 
Harry Potter and the Half-Blood Prince 
Batman v Superman: Dawn of Justice 
Superman Returns 
signal: broken pipe

Perceberam o “broken pipe” na última linha? Falta um tratamento para erro na saída, mas isso é algo que irei trabalhar posteriormente.

Os dados

E o arquivo tem essa “cara”:

➤ head -2 fixture/movie_metadata.csv 
color,director_name,num_critic_for_reviews,duration,director_facebook_likes,actor_3_facebook_likes,actor_2_name,actor_1_facebook_likes,gross,genres,actor_1_name,movie_title,num_voted_users,cast_total_facebook_likes,actor_3_name,facenumber_in_poster,plot_keywords,movie_imdb_link,num_user_for_reviews,language,country,content_rating,budget,title_year,actor_2_facebook_likes,imdb_score,aspect_ratio,movie_facebook_likes
Color,James Cameron,723,178,0,855,Joel David Moore,1000,760505847,Action|Adventure|Fantasy|Sci-Fi,CCH Pounder,Avatar ,886204,4834,Wes Studi,0,avatar|future|marine|native|paraplegic,http://www.imdb.com/title/tt0499549/?ref_=fn_tt_tt_1,3054,English,USA,PG-13,237000000,2009,936,7.9,1.78,33000

Os campos são:

  1. color
  2. director_name
  3. num_critic_for_reviews
  4. duration
  5. director_facebook_likes
  6. actor_3_facebook_likes
  7. actor_2_name,
  8. actor_1_facebook_likes
  9. gross
  10. genres
  11. actor_1_name
  12. movie_title
  13. num_voted_users
  14. cast_total_facebook_likes
  15. actor_3_name
  16. facenumber_in_poster
  17. plot_keywords
  18. movie_imdb_link
  19. num_user_for_reviews
  20. language
  21. country
  22. content_rating
  23. budget
  24. title_year
  25. actor_2_facebook_likes
  26. imdb_score
  27. aspect_ratio
  28. movie_facebook_likes

CSV para JSON

Agora é pegar esses dados e meter em um JSON bonitão. O módulo, padrão para isso é o encoding/json. Há até, no blog da linguagem GO, um texto de introdução ao uso de JSON.

Fazendo algo bem básico, só pra testar o novo módulo:

package main

import (
    "encoding/csv"
    "encoding/json"
    "fmt"
    "io"
    "os"
    "strconv"
)

type Movie struct {
    Title string
    Year int
}

func main() {
    f, _ := os.Open("fixture/movie_metadata.csv")
    r := csv.NewReader(f)

    for {
        record, err := r.Read()
		if err == io.EOF {
			break
		}

        year, _ := strconv.Atoi(record[23])
        movieData := Movie {record[11], year}
        movieJson, _ := json.Marshal(movieData)
        fmt.Println(string(movieJson))
    }
}

Executando:

➤ go run loadscv.go|head
{"Title":"movie_title","Year":0}
{"Title":"Avatar ","Year":2009}
{"Title":"Pirates of the Caribbean: At World's End ","Year":2007}
{"Title":"Spectre ","Year":2015}
{"Title":"The Dark Knight Rises ","Year":2012}
{"Title":"Star Wars: Episode VII - The Force Awakens             ","Year":0}
{"Title":"John Carter ","Year":2012}
{"Title":"Spider-Man 3 ","Year":2007}
{"Title":"Tangled ","Year":2010}
{"Title":"Avengers: Age of Ultron ","Year":2015}
signal: broken pipe

Ao trocar o json.Marshal por json.MarshalIndent, tem-se uma saída mais amigável. Alterei, no código anteirior, o Marshal para movieJson, _ := json.MarshalIndent(movieData, “”, ” “), e obtive essa saída (a primeira linha do CSV é formado pelos nomes dos campos):

➤ go run loadscv.go |head -20
{
   "Title": "movie_title",
   "Year": 0,
   "Actors": [
      {
         "Name": "actor_1_name",
         "FacebookLikes": 0
      },
      {
         "Name": "actor_2_name",
         "FacebookLikes": 0
      },
      {
         "Name": "actor_3_name",
         "FacebookLikes": 0
      }
   ]
}
{
   "Title": "Avatar ",
signal: broken pipe

Alterando o comportamento

É possível configurar outros aspectos sa saída JSON, com metadados na definição do objeto. Por exemplo, se quisermos alterar o nome das chaves (os nomes são obtidos por reflexão, a partir do nome definido na estrutura), dado que os nomes públicos devem começar com letra maiúscula, o que não fica legal em uma saída JSON, basta informar esse nome na própria estrutura:

type Person struct {
	Name          string `json:"name"`
	FacebookLikes int    `json:"facebook_likes"`
}

type Movie struct {
	Title  string   `json:"title"`
	Year   int      `json:"year"`
	Actors []Person `json:"actors"`
}

Obtendo, por exemplo, essa saída (peguei apenas a segunda entrada):

{
   "title": "Avatar ",
   "year": 2009,
   "actors": [
      {
         "name": "CCH Pounder",
         "facebook_likes": 1000
      },
      {
         "name": "Joel David Moore",
         "facebook_likes": 936
      },
      {
         "name": "Wes Studi",
         "facebook_likes": 855
      }
   ]
}

Um outro modificador interessante é o omitempty, que não gera saída para campos considerados nulos (zero, falso, nil, entre outros). Usando ele para ano, deixaria a declaração para Year assim:

Year   int      `json:"year,omitempty"`

Aprimorando

As tarefas restantes para resolver esse problema, ainda temos, em cada linha do CSV,  gêneros, com uma lista separada por `|` contendo palavras que tipificam o filme. Esses campos podem ter tratamentos diferentes em relações à sua normalização, e são especialmente interessantes por isso.

Como o que precisa ser feito ainda tem mais a ver com outros aspectos do GO, que não especificamente JSON, podem ser tratados normalmente, e para você, caro leitor, ficam como um desafio, especialmente para os que estamos iniciando nessa linguagem.

Outros materiais

Esses textos me ajudaram muito nessa empreitada:

Já se vão pouco mais de trinta anos de carreira, vinte dos quais usando ferramentas Open Source para solucionar os mais diversos problemas, nos mais variados setores econômicos, trabalhando agora principalmente com Python e Linux, também alimento, eventualmente, meu próprio blog.

Leave comment

Your email address will not be published. Required fields are marked with *.

%d blogueiros gostam disto: