5.4. Documentos JSON

JSON é a abreviação de Java Script Object Notation. Trata-se de um formato de dados que ficou muito popular nas aplicações Web, principalmente entre as APIs de serviços, pela facilidade de estruturar dados para intercâmbio entre as aplicações. Conforme veremos nas seções seguintes, um documento JSON é de fácil leitura e escrita, possibilitando as aplicações manipularem dados codificados neste formato.

O documento apresentado abaixo, obtido através do serviço Google Elevation API, ilustra a notação empregada em documentos JSON:

{
    "results": [
        {
            "elevation": 18.1,

            "location": {
                "lat": 30.0,
                "lng": -73.0
            },

            "resolution": 76.0
        }
    ],

    "status": "OK"
}

Esse documento se parece muito com um dicionário na linguagem de programação Python, onde temos um par de chaves ({ e }) delimitando os pares de chave-valor do dicionário. Os pares de chave-valor, por sua vez, correspondem a:

  • strings e arrays;

  • strings e novos objetos (ou dicionarios);

  • string e valores numéricos;

  • string e valores textuais.

5.4.1. Sintaxe de Documentos JSON

Um documento JSON contém um valor que pode ser representado por um dos seguintes tipos: object, array, number, string, true, false, ou null. Cada um desses valores possui uma notação específica.

O tipo number representa valores numéricos, não diferenciando entre valores inteiros e números em ponto-flutuante:

1234
8.9

O tipo string é usado para representação de valores textuais, sendo delimitado por aspas duplas ("), suportando caracteres Unicode e sequências de escape utilizando o caractere \:

"Uma string deve\n aparecer entre aspas duplas"

Os valores true e false, associados ao tipo lógico são escritos da seguinte forma:

true
false

O tipo array permite representar uma sequência de valores, inclusive de tipos diferentes. Utiliza-se um par de colchetes ([ e ]) como delimitador dos elementos pertencentes à sequência, sendo os elementos separados por vírgula (,):

["CAP-419", "SER-347", 2021, true ]

O tipo object é representado por um conjunto de pares chave-valor, delimitados por um par de chaves ({ e }):

{
    "nome": "Gilberto",

    "sobrenome": "Queiroz",

    "idade": 30,

    "pesquisador": true,

    "pais": [ "Gilberto Queiroz", "Telma Queiroz" ],

    "endereco": {
        "rua": "Av. dos Astronautas",
        "numero": 1758,
        "bairro": "Jardim da Granja",
        "cidade": "São José dos Campos",
        "uf": "SP",
        "cep": "12227-010"
    }
}

Repare que o tipo object permite estruturar dados que possuam certa complexidade. No exemplo acima, a chave endereco corresponde a um valor que também é um objeto, e a chave pais corresponde a uma sequência de valores do tipo string.

Nota

Para mais detalhes sobre o formato JSON, consulte [11] e [41].

5.4.2. Validadores de Documentos JSON

Existem na Internet vários aplicativos online que possibilitam validar a estrutura de um documento JSON, isto é, permite sabermos se o documento respeita a sintaxe (ou notação) JSON.

Um bom aplicativo é o JSONLint - The JSON Validator. Acesse o endereço desse aplicativo e verifique os erros no fragmento abaixo:

{
    "nome": Gilberto,

    "sobrenome": 'Queiroz',

    "idade": 30,

    "pesquisador": true,

    "pais": [ "Gilberto Queiroz", "Telma Queiroz" ]

    "endereco": {
        "rua": "Av. dos Astronautas",
        "numero": 1758,
        "bairro": "Jardim da Granja",
        "cidade": "São José dos Campos",
        "uf": "SP",
        "cep": "12227-010"
    }
}

Exercício: Tente corrigir o fragmento acima para que ele se torne um documento válido JSON.

5.4.3. Leitura e Escrita de Arquivos JSON

A biblioteca padrão do Python possui um módulo denominado json que pode ser utilizado para manipulação de documentos JSON. A forma mais simples de trabalhar este formato é através da representação de dicionários do Python, que possui uma mapeamento direto com o tipo object da notação JSON. Para começar nosso exercício com a manipulação de textos no formato JSON, vamos importar a biblioteca JSON e transformar um dicionário em um documento no formato JSON:

Trecho de Código 5.1 - Transformando um dicionário Python em um documento JSON.
 1import json
 2
 3endereco = {
 4    'rua': 'Av. dos Astronautas',
 5    'numero': 1758,
 6    'bairro': 'Jardim da Granja',
 7    'cidade': 'São José dos Campos',
 8    'UF': 'SP',
 9    'CEP': '12227-010'
10}
11
12endereco_json = json.dumps(endereco)
13
14print(endereco_json)

Repare no trecho de programa acima, que o identificador endereco_json fica associado a uma string contendo todo o texto do documento JSON equivalente ao dicionário informado na função json.dumps. Esse processo de transformar um objeto Python em um documento texto é conhecido por serialização.

De maneira similar, podemos ler um texto representando um documento JSON e transformá-lo em um dicionário Python, através da função json.loads:

Trecho de Código 5.2 - Transformando um documento JSON em um dicionário Python.
 1import json
 2
 3doc = '{"rua": "Av. dos Astronautas", "numero": 1758, \
 4      "bairro": "Jardim da Granja", \
 5      "cidade": "S\\u00e3o Jos\\u00e9 dos Campos", \
 6      "UF": "SP", "CEP": "12227-010"}'
 7
 8endereco = json.loads(doc)
 9
10print( endereco['rua'] )
11print( endereco['numero'] )
12print( endereco['cidade'] )

No trecho de código acima podemos observar que após transformar a string contendo o texto do documento JSON (doc) em um dicionário Python através da função json.loads, podemos acessar os valores associados às chaves rua, numero e cidade no dicionário (endereco). Este processo de transformar um documento texto em um objeto Python é chamado de deserialização.

Agora vamos ler um documento JSON.

Trecho de Código 5.3 - Documento JSON com informações sobre uma publicação científica (artigo.json).
{
  "indexed": {
    "date-parts": [ [2020, 4, 17] ],
    "date-time": "2020-04-17T06:23:05Z",
    "timestamp": 1587104585191
  },
  "reference-count": 55,
  "publisher": "MDPI AG",
  "issue": "8",
  "license": [
    {
      "URL": "https://creativecommons.org/licenses/by/4.0/",
      "start": {
        "date-parts": [ [2020, 4, 16] ],
        "date-time": "2020-04-16T00:00:00Z",
        "timestamp": 1586995200000
      }
    }
  ],
  "abstract": "In recent years, Earth observation (EO) satellites...",
  "DOI": "10.3390/rs12081253",
  "type": "article-journal",
  "created": {
    "date-parts": [ [2020, 4, 16] ],
    "date-time": "2020-04-16T17:01:39Z",
    "timestamp": 1587056499000
  },
  "page": "1253",
  "source": "Crossref",
  "is-referenced-by-count": 0,
  "title": "An Overview of Platforms for Big Earth Observation Data Management and Analysis",
  "prefix": "10.3390",
  "volume": "12",
  "author": [
    {
      "ORCID": "http://orcid.org/0000-0003-3239-2160",
      "authenticated-orcid": false,
      "given": "Vitor C. F.",
      "family": "Gomes",
      "sequence": "first",
      "affiliation": []
    },
    {
      "ORCID": "http://orcid.org/0000-0001-7534-0219",
      "authenticated-orcid": false,
      "given": "Gilberto R.",
      "family": "Queiroz",
      "sequence": "additional",
      "affiliation": []
    },
    {
      "ORCID": "http://orcid.org/0000-0003-2656-5504",
      "authenticated-orcid": false,
      "given": "Karine R.",
      "family": "Ferreira",
      "sequence": "additional",
      "affiliation": []
    }
  ],
  "member": "1968",
  "published-online": { "date-parts": [ [2020, 4, 16] ] },
  "container-title": "Remote Sensing",
  "original-title": [],
  "language": "en",
  "link": [{
    "URL": "https://www.mdpi.com/2072-4292/12/8/1253/pdf",
    "content-type": "unspecified",
    "content-version": "vor",
    "intended-application": "similarity-checking"
  }],
  "deposited": {
    "date-parts": [ [2020, 4, 16] ],
    "date-time": "2020-04-16T17:51:19Z",
    "timestamp": 1587059479000
  },
  "score": 1.0,
  "subtitle": [],
  "short-title": [],
  "issued": {
    "date-parts": [ [2020, 4, 16] ]
  },
  "references-count": 55,
  "journal-issue": {
    "published-online": {
      "date-parts": [ [2020, 4] ]
    },
    "issue": "8"
  },
  "alternative-id": ["rs12081253"],
  "URL": "http://dx.doi.org/10.3390/rs12081253",
  "ISSN": ["2072-4292"],
  "subject": ["General Earth and Planetary Sciences"],
  "container-title-short": "Remote Sensing"
}

Podemos ler o arquivo artigo.json utilizando a função interna do Python chamada open(), como mostrado abaixo:

Trecho de Código 5.4 - Leitura de um documento JSON.
1import json
2
3with open( 'artigo.json', 'r' ) as arq_json:
4    artigo = json.load( arq_json )
5
6    print( artigo['title'] )
7
8    for autor in artigo['author']:
9        print( 'Nome: {} {}'.format(autor['given'], autor['family']) )

Repare que após a abertura do arquivo na linha 03, realizamos a leitura dos dados deste arquivo na linha 04 através da função json.load(). Após a transformação do arquivo em um dicionário Python, podemos acessar os campos desse dicionário usando o operador [], recuperando assim o título do artigo e sua lista de autores.

Para gravar um arquivo JSON podemos usar a função json.dump como mostrado no trecho de código abaixo:

Trecho de Código 5.5 - Escrita de um documento JSON.
 1import json
 2
 3endereco = {
 4    'rua': 'Av. dos Astronautas',
 5    'numero': 1758,
 6    'bairro': 'Jardim da Granja',
 7    'cidade': 'São José dos Campos',
 8    'UF': 'SP',
 9    'CEP': '12227-010'
10}
11
12with open('endereco.json', 'w') as arq_json:
13    json.dump(endereco, arq_json)

Após a execução do trecho de código acima, você deverá ter um arquivo chamado endereco.json contendo a serialização do dicionário Python endereco na forma de um arquivo JSON.

Nota

As funções json.dump e json.dumps aceitam um argumento nomeado, indent, que pode ser usado para controlar a indentação do documento JSON. Na linha 14 do trecho de código acima, poderíamos utilizar um nível de indentação com 4 espaços:

json.dump(endereco, arq_json, indent=4)

Nota

Mais detalhes sobre leitura/escrita de arquivos JSON podem ser encontrados em [51], [24] e [54].