23 de março de 2019

Validando documentos JSON no PHP com JSON Schema

JSON Schema é uma especificação para validação de documentos JSON. A ideia é parecida com a de um XSD (XML Schema Definition) e um exemplo sólido da utilização de esquemas XML se dá na implementação e geração de notas fiscais eletrônicas, onde é muito importante que os dados estejam corretos e que se siga uma estrutura previamente definida.

Um esquema descreve a estrutura e os requerimentos que um documento deve ter. E a partir desses requerimentos podemos:

  • Gerar uma documentação do documento;
  • Validar o documento em cima do esquema;

É muito comum arquivos de configuração no formato JSON e com JSON Schema podemos validá-los. São muitos os casos de uso. Até mesmo quando a sua aplicação precisa, por algum motivo, consumir algum JSON de metadados relevantes para ela.

A especificação JSON Schema independe de linguagem. Você deve conseguir encontrar implementações dela para a sua linguagem favorita.

Para esse artigo, vou usar o PHP. Mas as ideias aqui são facilmente portadas para qualquer outra linguagem. Só vai mudar a implementação do código que lidará com a validação.

Suponhamos que a nossa aplicação precise consumir esse JSON:

{
  "name": "PHP",
  "frameworks": [
    "Laravel",
    "Symfony",
    "Zend Framework",
    "Slim"
  ]
}

Podemos validá-lo com o seguinte esquema:

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "frameworks": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 3
      }
    }
  }
}

Ele define que o documento precisa ter um objeto com dois atributos: nome e frameworks. O atributo nome deve aceitar apenas string e o atributo frameworks deve aceitar um array, sendo que cada item dele deve ser do tipo string e não pode ter menos que dois caracteres.

Podemos testar a validação do nosso documento através do JSON Schema Validator:

Na primeira coluna coloca-se o esquema e na segunda o JSON a ser validado.

Faça um teste, adicione um item “Yi” no array do atributo frameworks:

{
  "name": "PHP",
  "frameworks": [
    "Laravel",
    "Symfony",
    "Zend Framework",
    "Slim",
    "Yi"
  ]
}

Não passou na validação por causa da restrição "minLength": 3 que especifica que os itens do array precisam ter pelo menos três caracteres.

Apesar do nosso exemplo ser simples e didático, ele nos dá uma visão geral do que pode ser feito. A especificação completa do JSON Schema você pode encontrar aqui.

Um exemplo um pouco mais complexo, retirado do Understanding JSON Schema que demonstra como podemos reaproveitar definições:

{
  "shipping_address": {
    "street_address": "1600 Pennsylvania Avenue NW",
    "city": "Washington",
    "state": "DC"
  },
  "billing_address": {
    "street_address": "1st Street SE",
    "city": "Washington",
    "state": "DC"
  }
}

O esquema:

{
  "$schema": "http://json-schema.org/draft-04/schema#",

  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "street_address": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "state": {
          "type": "string"
        }
      },
      "required": [
        "street_address",
        "city",
        "state"
      ]
    }
  },

  "type": "object",
  "properties": {
    "billing_address": {
      "$ref": "#/definitions/address"
    },
    "shipping_address": {
      "$ref": "#/definitions/address"
    }
  }
}

Observe que na definição dos atributos do objeto ao invés de duplicarmos as especificações nós as reutilizamos informando a referência de onde podem ser encontradas:

  "properties": {
    "billing_address": {
      "$ref": "#/definitions/address"
    },
    "shipping_address": {
      "$ref": "#/definitions/address"
    }
  }

Veja também que na definição de address as propriedades foram marcadas como required (obrigatórias):

"required": [
    "street_address",
    "city",
    "state"
]

Por exemplo, se o documento não informar um valor para a propriedade city ele não terá sucesso na validação.

Validando JSON Schema no PHP

Agora que tivemos um overview do que é e o que podemos fazer, chegamos na parte que realmente “toca” esse artigo que é a validação desses esquemas no PHP.

Temos duas formas de fazer isso:

  • Via arquivo físico de esquema, por exemplo, my_schema.json;
  • Em runtime via uma construção de arrays;

Essa segunda opção pode ser útil quando há a necessidade de alterar o esquema em tempo de execução. É possível criar um esquema dinâmico que responda à necessidades específicas.

E o documento JSON a ser validado temos a mesma relação. Ele pode ser importado de um arquivo físico ou ele pode ser construído em tempo de execução.

Utilizaremos a library justinrainbow/json-schema.


Observação: Daqui pra frente é necessário que você tenha conhecimentos básicos em PHP e na utilização do Composer, o gerenciador de dependências oficial do PHP.

Quer elevar os seus conhecimentos em PHP para um patamar profissional? Temos três excelentes cursos bases que podem te levar do zero ao mercado de trabalho:


Dando continuidade, no seu ambiente, onde normalmente você cria os seus projetos, crie uma nova pasta. No meu caso, vou chamá-la de json-schema. Dentro dessa pasta, crie um arquivo chamado composer.json com a seguinte definição:

{
    "require": {
        "justinrainbow/json-schema": "^5.2"
    }
}

Especificamos a dependência do nosso projeto. Ainda na raiz dele, crie um arquivo json chamado “curso-schema.json” com a seguinte definição:

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "frameworks": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 3
      }
    }
  }
}

Crie outro arquivo chamado curso.json:

{
  "name": "PHP",
  "frameworks": [
    "Laravel",
    "Symfony",
    "Zend Framework",
    "Slim"
  ]
}

Agora, crie um arquivo PHP chamado validate.php. No momento essa é a estrutura do nosso projeto:

O validate.php deve ter a seguinte implementação:

<?php

require("./vendor/autoload.php");

use JsonSchema\Validator;

$data = json_decode(file_get_contents('curso.json'));
$schema = json_decode(file_get_contents('curso-schema.json'));

$validator = new Validator();
$validator->validate($data, $schema);

if ($validator->isValid()) {
    echo "O documento informado é válido.";
} else {
    echo "O documento é inválido. Violações: <br>";
    echo "<ul>";
    foreach ($validator->getErrors() as $error) {
        echo "<li>" . sprintf("[%s] %s\n", $error['property'], $error['message']) . "</li>";
    }
    echo "</ul>";
}

Para testá-lo, podemos usar o próprio servidor embutido do PHP. É só você navegar até a pasta desse projeto (via CMD ou terminal) e executar o seguinte comando:

php -S localhost:8000

E então executar o exemplo: http://localhost:8000/validate.php

Teste alterar o curso.json e colocar um nome inválido de framework para a nossa especificação, como “Yi”:

Além de importar um arquivo físico, também é possível construir as definições do esquema em tempo de execução, como mostra o exemplo na documentação:

$validator->validate(
    $request, (object) [
    "type"=>"object",
        "properties"=>(object)[
            "processRefund"=>(object)[
                "type"=>"boolean"
            ],
            "refundAmount"=>(object)[
                "type"=>"number"
            ]
        ]
    ],
    Constraint::CHECK_MODE_COERCE_TYPES
);

As possibilidades são muitas. Mais que importante que validar é conhecer melhor a especificação e tudo o que ela pode oferecer.

Um abraço!

Share

Head de desenvolvimento. Vasta experiência em desenvolvimento Web com foco em PHP. Graduado em Sistemas de Informação. Pós-graduando em Arquitetura de Software Distribuído pela PUC Minas. Zend Certified Engineer (ZCE) e Coffee Addicted Person (CAP).