Json no R - Parte 1

Este é o primeiro tutorial de dois sobre JSON no R. Nesta primeira parte, mostro como usar o pacote jsonlite para converter de JSON para R e vice-versa. Na segunda parte, trato de como trabalhar com JSON como JSON mesmo no R, sem convertê-lo para lista ou dataframe.

José de Jesus Filho https://github.com/jjesusfilho
2022-12-26

Um dos formatos de arquivos que eu mais gosto de trabalhar é JSON (JavaScript Object Notation). A graça de JSON é ser um formato amigável para seres humanos e para máquinas. Coloco abaixo o exemplo de parte de um currículo.

{
    "id": 2,
    "data": "2022-01-01",
    "nome": {
        "primeiro_nome": "José",
        "sobrenome": "de Jesus Filho"
    },
    "cpf": "123.456.789-34",
    "disponivel": true,
    "educacao": [
        {
            "escola": "EMPG Milton Campos",
            "ensino": "Fundamental",
            "inicio": 1982,
            "fim": 1989
        },
        {
            "escola": "Derville Allegretti",
            "ensino": "medio",
            "inicio": 1990,
            "fim": 1992
        }
    ],
    "experiencia_profissional": [
        {
            "empresa": "Companhia Brasileria de Distribuição",
            "cargo": "empacotador",
            "inicio": 1986,
            "fim": 1988
        },
        {
            "empresa": "Compneus",
            "cargo": "gerente",
            "inicio": 1990,
            "fim": 1992
        },
        {
            "empresa": "Varias",
            "cargo": "muitos",
            "inicio": 1992,
            "fim": 2021
        },
        {
            "empresa": "MPSP",
            "cargo": "Jurimetrista",
            "inicio": 2022,
            "fim": null
        }
    ]
}

Diferentemente de XML, JSON é simples e fácil de manipular. No entanto, essa simplicidade vem com um custo. JSON carece de namespace, o que facilmente pode gerar conflitos e perda de integridade dos dados. Se você quer entender para que serve namespace, carregue o pacote dplyr e busque a ajuda para a função filter, você notará que há duas funções distintas com o mesmo nome, porém uma no namespace dplyr e outra no stats. Pacotes são namespaces, schemas em SQL são namespaces e XML também usa namespaces. Namespaces asseguram unicidade nos nomes dos objetos.

Ademais, JSON só reconhece quatro (4) tipos/classes de dados (texto, número, boleano e nulo). Datas e dados categóricos (fatores ou enum) não são reconhecidos. Além disso, JSON têm duas estruturas universais:

JSON objeto: Uma coleção desordenada de pares chave/valor. Esses valores podem ser aqueles acima mencionados: texto (string), número (number), boleano (boolean) e nulo, bem como os próprios objetos JSON e arrays.

JSON array: Uma coleção ordenada de valores.

Note acima que o JSON está dentro de chaves {…}, indicando que se trata de um objeto JSON. Nele há vários objetos: id, data, nome, cpf, disponivel, educacao e experiencia_profissional. O id é numérico, data, nome e cpf são textos e disponível é lógico (boleano). Por sua vez, educacao e experiencia_profissional são arrays. Arrays estão sempre entre colchetes.

Perceba também que as chaves ou nomes (keys) estão sempre entre aspas e os valores, quando textos também entre aspas, números, boleanos e nulos (último ítem “fim” da experiência profissional), sem aspas.

O exemplo acima é simples e fácil de entender, mas JSON pode ser complexo, com profundos aninhamentos de objetos e arrays, o que torna a conversão de um JSON para um objeto nativo do R e vice-versa um desafio e sujeita a erros de toda sorte.

Neste tutorial, mostraremos como trabalhar como pacote jsonlite, o qual converte JSON em objetos do R e vice-versa. No próximo, falaremos do pacote jqr, o qual manuseia JSON como JSON mesmo, sem convertê-lo para lista ou data.frame.

O pacote jsonlite

O pacote jsonlite permite ler, escrever e validar JSONs. Vamos tratar do exemplo acima:

x <- '{
    "id": 2,
    "data": "2022-01-01",
    "nome": {
        "primeiro_nome": "José",
        "sobrenome": "de Jesus Filho"
    },
    "cpf": "123.456.789-34",
    "disponivel": true,
    "educacao": [
        {
            "escola": "EMPG Milton Campos",
            "ensino": "Fundamental",
            "inicio": 1982,
            "fim": 1989
        },
        {
            "escola": "Derville Allegretti",
            "ensino": "medio",
            "inicio": 1990,
            "fim": 1992
        }
    ],
    "experiencia_profissional": [
        {
            "empresa": "Companhia Brasileria de Distribuição",
            "cargo": "empacotador",
            "inicio": 1986,
            "fim": 1988
        },
        {
            "empresa": "Compneus",
            "cargo": "gerente",
            "inicio": 1990,
            "fim": 1992
        },
        {
            "empresa": "Varias",
            "cargo": "muitos",
            "inicio": 1992,
            "fim": 2021
        },
        {
            "empresa": "MPSP",
            "cargo": "Jurimetrista",
            "inicio": 2022,
            "fim": null
        }
    ]
}'

o pacote jsonlite possui um função para converter JSON para objeto do R chamada fromJSON:

library(jsonlite)
obj <- fromJSON(x)

obj
$id
[1] 2

$data
[1] "2022-01-01"

$nome
$nome$primeiro_nome
[1] "José"

$nome$sobrenome
[1] "de Jesus Filho"


$cpf
[1] "123.456.789-34"

$disponivel
[1] TRUE

$educacao
               escola      ensino inicio  fim
1  EMPG Milton Campos Fundamental   1982 1989
2 Derville Allegretti       medio   1990 1992

$experiencia_profissional
                               empresa        cargo inicio  fim
1 Companhia Brasileria de Distribuição  empacotador   1986 1988
2                             Compneus      gerente   1990 1992
3                               Varias       muitos   1992 2021
4                                 MPSP Jurimetrista   2022   NA

A vantagem do jsonlite é que ele opera um excelente mapeamento entre JSON e objetos do R. O JSON foi convertido numa lista de sete elementos, dos quais alguns vetores de um único elemento, nome é uma lista, e educacao e experiencia_profissional são dataframes. Além disso, os textos foram convertidos para characters, números tornaram-se integers, true foi propriamente convertido para TRUE e null para NA.

A data foi mantida como character porque JSON não tem especificação para datas.

O pacote jsonlite possibilita o caminho reverso por meio da função toJSON:

toJSON(obj)

{“id”:[2],“data”:[“2022-01-01”],“nome”:{“primeiro_nome”:[“José”],“sobrenome”:[“de Jesus Filho”]},“cpf”:[“123.456.789-34”],“disponivel”:[true],“educacao”:[{“escola”:“EMPG Milton Campos”,“ensino”:“Fundamental”,“inicio”:1982,“fim”:1989},{“escola”:“Derville Allegretti”,“ensino”:“medio”,“inicio”:1990,“fim”:1992}],“experiencia_profissional”:[{“empresa”:“Companhia Brasileria de Distribuição”,“cargo”:“empacotador”,“inicio”:1986,“fim”:1988},{“empresa”:“Compneus”,“cargo”:“gerente”,“inicio”:1990,“fim”:1992},{“empresa”:“Varias”,“cargo”:“muitos”,“inicio”:1992,“fim”:2021},{“empresa”:“MPSP”,“cargo”:“Jurimetrista”,“inicio”:2022}]}

A primeira observação é que JSON não está nada elegante nem formatado para facilitar a leitura humana. Igualmente, percebe-se que os vetores de um único elemento foram todos convertidos em arrays. Por fim, a chave-valor fim do último cargo, que era NA no R simplesmente desapareceu.

A função toJSON possui argumentos para reverter isso.

toJSON(obj, 
      pretty = T, ## Tornar amigável ao usuário
      na = "null", ## converte NA para nulo
      auto_unbox = TRUE) ## retira os colchetes dos elementos únicos.
{
  "id": 2,
  "data": "2022-01-01",
  "nome": {
    "primeiro_nome": "José",
    "sobrenome": "de Jesus Filho"
  },
  "cpf": "123.456.789-34",
  "disponivel": true,
  "educacao": [
    {
      "escola": "EMPG Milton Campos",
      "ensino": "Fundamental",
      "inicio": 1982,
      "fim": 1989
    },
    {
      "escola": "Derville Allegretti",
      "ensino": "medio",
      "inicio": 1990,
      "fim": 1992
    }
  ],
  "experiencia_profissional": [
    {
      "empresa": "Companhia Brasileria de Distribuição",
      "cargo": "empacotador",
      "inicio": 1986,
      "fim": 1988
    },
    {
      "empresa": "Compneus",
      "cargo": "gerente",
      "inicio": 1990,
      "fim": 1992
    },
    {
      "empresa": "Varias",
      "cargo": "muitos",
      "inicio": 1992,
      "fim": 2021
    },
    {
      "empresa": "MPSP",
      "cargo": "Jurimetrista",
      "inicio": 2022,
      "fim": null
    }
  ]
} 

Se você quiser salvar um objeto do R como JSON, use a função write_JSON com os mesmos argumentos da toJSON adicionados do argumento path:

write_JSON(obj,
          path = "curriculo.JSON",
          pretty = TRUE,
          na = "JSON",
          auto_unbox = TRUE)

Preservando atributos de objetos R

Por vezes, você quer converter um objeto R para JSON e revertê-lo posteriormente para R. A título de exemplo, vamos criar simples tibble e convertê-la para JSON.

df <- tibble::tibble(a = 1:5, b = letters[1:5])

JSON <- toJSON(df, pretty = TRUE)

JSON
[
  {
    "a": 1,
    "b": "a"
  },
  {
    "a": 2,
    "b": "b"
  },
  {
    "a": 3,
    "b": "c"
  },
  {
    "a": 4,
    "b": "d"
  },
  {
    "a": 5,
    "b": "e"
  }
] 

Se quisermos nossa tibble de volta, o resultado pode ser frustrante:

tb <- fromJSON(JSON)

class(tb)
[1] "data.frame"

O pacote jsonlite oferece duas alternativas a toJSON e fromJSON, as quais preservam os atributos originais:

sJSON <- serializeJSON(df, pretty = T)
sJSON
{
  "type": "list",
  "attributes": {
    "class": {
      "type": "character",
      "attributes": {},
      "value": ["tbl_df", "tbl", "data.frame"]
    },
    "row.names": {
      "type": "integer",
      "attributes": {},
      "value": [1, 2, 3, 4, 5]
    },
    "names": {
      "type": "character",
      "attributes": {},
      "value": ["a", "b"]
    }
  },
  "value": [
    {
      "type": "integer",
      "attributes": {},
      "value": [1, 2, 3, 4, 5]
    },
    {
      "type": "character",
      "attributes": {},
      "value": ["a", "b", "c", "d", "e"]
    }
  ]
} 

O JSON resultante é mais verboso, mas assegura o retorno ao objeto R original:

utb <- unserializeJSON(sJSON)
class(utb)
[1] "tbl_df"     "tbl"        "data.frame"

Se quisermos imprimir a tibble:

utb
# A tibble: 5 × 2
      a b    
* <int> <chr>
1     1 a    
2     2 b    
3     3 c    
4     4 d    
5     5 e    

Há outros recursos no pacote, como imprimir objetos json de forma bonita (prettify), mas creio que o essencial foi falado. Não se esqueça de ler o tutorial 2, nele mostramos uma infinidade de recursos para trabalhar com JSON.

Citation

For attribution, please cite this work as

Filho (2022, Dec. 26). Jurimetria: Json no R - Parte 1. Retrieved from https://direitoemdados.consudata.com.br/posts/2022-12-26-json-no-rparte1/

BibTeX citation

@misc{filho2022json,
  author = {Filho, José de Jesus},
  title = {Jurimetria: Json no R - Parte 1},
  url = {https://direitoemdados.consudata.com.br/posts/2022-12-26-json-no-rparte1/},
  year = {2022}
}