jsonobject
jsonobject
é uma classe PHP para facilitar o uso de objetos provenientes de uma definição JSON. A ideia vem do uso pydantic
em python e de sua capacidade de analisar e validar dados json em objetos.
jsonobject
Tive que usar uma API do PHP, e essa API me retornou jsonobject s. Então, eu precisava analisá-los em objetos PHP que pudesse usar no aplicativo.
O fluxo de trabalho é
jsonobject
para analisar a definição JSONVejamos o seguinte exemplo JSON:
{
"id" : 0 ,
"name" : " John Doe " ,
"age" : 42 ,
"emails" : [
" [email protected] " ,
" [email protected] "
],
"address" : {
"street" : " My street " ,
"number" : 42 ,
"city" : " My city " ,
"country" : " My country "
}
}
Usando jsonobject
, poderei definir meu modelo de dados usando as seguintes classes:
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
];
}
class Address extends jsonobject {
const ATTRIBUTES = [
' street ' => ' str ' ,
' number ' => ' int ' ,
' city ' => ' str ' ,
' country ' => ' str ' ,
];
}
E então adicione o seguinte comando:
$ user = User:: fromObject ( json_decode ( $ json_text_definition ));
A classe jsonobject
realizará a análise do conteúdo em objetos, e poderemos usar seus atributos conforme definido:
echo ( $ user -> name );
As classes definidas também podem possuir métodos que facilitarão a implementação do modelo de dados da aplicação. Por exemplo, seria possível definir a classe User
assim:
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
];
public function isAdult () {
return $ this -> age >= 18 ;
}
}
jsonobject
A ideia da classe jsonobject
é usá-la para analisar dados json em objetos. Para que esses objetos possam conter outros métodos que ajudarão a implementar o modelo de dados da aplicação.
Quando o objeto JSON (ou array) é analisado, seu conteúdo é analisado recursivamente de acordo com os tipos definidos na constante ATTRIBUTES
. Se os dados não forem válidos, porque não contêm os valores esperados, uma exceção será lançada.
Para utilizar jsonobject deve-se subclassificar jsonobject
e definir a constante ATTRIBUTES
para aquela classe para que ela defina os atributos esperados para os objetos daquela classe, junto com o tipo de cada um.
A constante ATTRIBUTES
é uma matriz associativa onde as chaves são o nome de cada atributo e os valores são o tipo de cada atributo .
Os tipos possíveis podem ser:
jsonobject
. Ao definir o nome dos atributos, pode-se adicionar um ?
no final do nome para indicar que o atributo é opcional. Por exemplo, o nome do atributo address?
na seção de casos de uso é opcional.
Cada campo é considerado obrigatório, portanto deve existir no objeto analisado (ou array). Além disso, o objeto deve ser do tipo definido (ou seja, deve ser analisado corretamente pelo tipo específico).
Qualquer atributo que não seja opcional é considerado obrigatório. Isto é de especial interesse em dois pontos:
fromArray
ou fromObject
).jsonobject
Ao criar o objeto a partir de uma estrutura externa, o jsonobject
cuidará de todos os campos obrigatórios. E se algum deles estiver faltando, uma exceção será gerada.
No próximo exemplo, uma exceção será gerada porque o campo obrigatório idade não é fornecido.
class User extends jsonobject {
const ATTRIBUTES = [
" name " => " str " ,
" age " => " int " ,
];
}
( . . . )
$ user = User:: fromArray ([ " name " => " John " ]);
Ao converter o objeto em um array ou em um objeto (ou obter sua representação json), um campo obrigatório receberá um valor padrão, mesmo que não esteja definido.
Então no próximo exemplo
class User extends jsonobject {
const ATTRIBUTES = [
" name " => " str " ,
" age " => " int " ,
" birthDate? " => " str "
];
}
$ user = new User ();
echo (( string ) $ user );
A saída será
{
"name" : " " ,
"age" : 0
}
Porque embora os atributos nome e idade sejam obrigatórios e obtenham seus valores padrão (ou seja, 0 para números, vazio para strings, listas ou dictos), o atributo birthDate não é obrigatório e ainda não foi definido. Portanto, não é gerado na saída.
null
em atributos obrigatóriosO problema de definir valores como nulos é de especial relevância quando se considera se um atributo é opcional ou não.
Pode-se pensar que, se definirmos um valor como nulo, isso significaria cancelar a definição do valor e, portanto, isso só deveria ser possível para valores opcionais, mas não para valores obrigatórios.
No jsonobject
temos um conceito diferente, pois definir uma propriedade como nula significará "definir um valor como nulo " e não desmarcar a propriedade. Para cancelar a configuração da propriedade, devemos usar a função unset ou algo parecido.
jsonobject
também permite cancelar a definição de valores. Para um atributo opcional, significa remover o valor e, portanto, não terá nenhum valor em uma representação de array ou objeto (se recuperar o valor, será definido como null ).
Mas para um atributo obrigatório, desativá-lo significará redefinir seu valor para o padrão . Isso significa que ele será inicializado com o valor padrão do tipo (ou seja, 0 para números, vazio para listas, strings ou dictos, etc.) ou com seu valor padrão na constante ATTRIBUTES
.
jsonobject
s também são capazes de herdar atributos de suas classes pai. Veja o seguinte exemplo:
class Vehicle extends jsonobject {
const ATTRIBUTES = [
" brand " => " str " ,
" color " => " str "
]
}
class Car extends Vehicle {
const ATTRIBUTES = [
" wheels " => " int "
]
}
class Boat extends Vehicle {
const ATTRIBUTES = [
" length " => " float "
]
}
Neste exemplo, a classe Vehicle
terá apenas os atributos marca e cor , mas a classe Car
terá os atributos marca , cor e rodas , enquanto a classe Boat
terá os atributos marca , cor e comprimento .
Objetos de classes filhas de jsonobject
podem ser criados usando o método estático ::fromArray
ou ::fromObject
, começando a partir de um objeto analisado json .
No exemplo anterior, se tivermos um arquivo car.json com o seguinte conteúdo:
{
"brand" : " BMW " ,
"color" : " black "
}
Podemos usar o seguinte código para obter uma instância da classe Vehicle
:
$ json = file_get_contents ( " car.json " );
$ vehicle = Vehicle:: fromArray (( array ) json_decode ( $ json , true ));
Uma alternativa é instanciar objetos como no próximo exemplo
* PHP 8 e superior:
$ car = new Car (brand: " BMW " , color: " black " , wheels: 4 );
* versões anteriores do PHP:
$ car = new Car ([ " brand " => " BMW " , " color " => " black " , " wheels " => 4 ]);
jsonobject
O jsonobject
é a classe principal desta biblioteca. Seus métodos são:
__construct($data)
- Cria um novo objeto a partir dos dados fornecidos__get($name)
- Retorna o valor do atributo com o nome fornecido__set($name, $value)
- Define o valor do atributo com o nome fornecido__isset($name)
- Retorna verdadeiro se o atributo com o nome fornecido estiver definido__unset($name)
- Desativa o valor de um atributo opcional (ou redefine o valor de um atributo obrigatório).toArray()
– Retorna um array associativo com os dados do objeto. O array é criado recursivamente, visitando cada um dos subatributos de cada atributo.toObject()
- Retorna um objeto com os dados do objeto como atributos. O array é criado recursivamente, visitando cada um dos subatributos de cada atributo.toJson()
- Retorna uma string json com a representação do objeto como objeto padrão.::fromArray($data)
- Cria um objeto, analisando o array associativo fornecido nos atributos definidos na classe. Cada um dos atributos é analisado recursivamente, de acordo com o tipo definido para ele.::fromObject($data)
- Cria um objeto, analisando o objeto fornecido nos atributos definidos na classe. Cada um dos atributos é analisado recursivamente, de acordo com o tipo definido para ele. JsonDict
Este objeto é usado para lidar com um dicionário proveniente de uma definição json. A classe JsonDict
é digitada de forma que cada um dos elementos seja de um determinado tipo.
Os objetos JsonDict
podem ser usados como objetos do tipo array (ex. $jsonDict["key1"]) mas (no momento da escrita deste texto) o tipo dos elementos inseridos no dicionário não são verificados. O tipo é usado para analisar o conteúdo ao criar o dict (por exemplo, usando a função estática fromArray
) ou para despejar o conteúdo em um array ou objeto (por exemplo, usando a função toArray
).
Os métodos são:
toArray()
toObject()
::fromArray($data)
::fromObject($data)
Esses métodos são interpretados da mesma maneira que no caso de jsonobject
. E o tipo dos elementos no dict pode referir-se a tipos complexos que serão considerados recursivamente ao analisar o conteúdo.
por exemplo, tipo list[list[int]]
será usado para analisar [ [ 1, 2, 3], [ 4, 5, 6 ]]
JsonArray
Este objeto é praticamente igual ao JsonDict
com a exceção de que os índices devem ser números inteiros. Neste caso $value["key1"]
produzirá uma exceção.
Neste caso, a função para anexar elementos ao array (ou seja, []
) também é implementada.
Ao definir a classe, é possível inicializar os valores dos objetos recém-criados e dos atributos opcionais.
Existem duas maneiras:
### Usando propriedades de classe
É possível inicializar o valor de um objeto utilizando as propriedades da classe, portanto se o valor de um atributo for definido na classe, ele será copiado para a instância como um atributo, caso esteja definido.
Por exemplo
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
' sex? ' => ' str '
];
public $ sex = " not revealed " ;
}
Agora, o atributo sex
é inicializado como not revelado em vez de ser null .
A maneira de fazer isso é definir uma tupla [ <type>, <default value> ]
para o tipo do objeto. Tomando o próximo exemplo:
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
' sex? ' => [ ' str ' , ' not revealed ' ]
];
}
O atributo sex
é opcional ao recuperar os dados do usuário. Usando esta nova definição para a classe, se sex
não for definido, o valor será definido como "não revelado" em vez de null
.
Uma característica importante é que, se a string definida como <valor padrão> corresponder a um método do objeto, ela será chamada ao obter o valor (caso ainda não tenha sido definido), e o valor definido para aquela propriedade será ser o resultado da chamada.
Por exemplo
class User extends jsonobject {
const ATTRIBUTE = [
...
' birthDay? ' => [ ' str ' , ' computeBirthDate ' ]
]
function computeBirthDate () {
$ now = new DateTime ();
$ now ->sub( DateInterval ::createFromDateString("{ $ this -> age } years"));
return $ now ->format("Y-m-d");
}
}
Neste exemplo, se não tivermos definido a propriedade birthDate
, mas ela for recuperada, ela será calculada subtraindo a idade da data atual.
Se quiser analisar um objeto arbitrário para um jsonobject
, é possível usar a função jsonobject ::parse_typed_value
. Isso é importante para poder converter de qualquer tipo para um tipo jsonobject
.
por exemplo
$ myobject = jsonobject :: parse_typed_value ( " list[str] " , [ " my " , " name " , " is " , " John " ]);
Obterá um objeto do tipo JsonList<str>
.
O comportamento padrão desta biblioteca é garantir que os valores definidos para os atributos correspondam ao tipo definido. Mas isso significa que, como um float
não é um int
, definir um float como 0
falhará porque 0
é um número inteiro. Nesse caso, o usuário deve converter os valores antes de atribuí-los. Para controlar se deve ou não verificar rigorosamente o tipo, é possível usar a constante STRICT_TYPE_CHECKING
.
Se
STRICT_TYPE_CHECKING
estiver definido comoTrue
, os tipos serão rigorosamente verificados e, por exemplo, atribuir9.3
a umint
gerará uma exceção. Se definido comoFalse
, os tipos numéricos serão convertidos entre si. Então, por exemplo, se atribuirmos9.3
a umint
ele será automaticamente truncado para9
.
Outra verificação de tipo importante é ao atribuir um valor vazio (ou seja ""
ou null
) a um tipo numérico. Nesse caso, temos a constante STRICT_TYPE_CHECKING_EMPTY_ZERO
.
Se
STRICT_TYPE_CHECKING_EMPTY_ZERO
estiver definido comoTrue
(comportamento padrão), ao atribuir um valor vazio a um tipo numérico, ele será considerado0
. ou seja, atribuir uma string vazia ou um valornull
a um atributoint
significará atribuir0
. Se definido comoFalse
, a biblioteca verificará os tipos e eventualmente gerará uma exceção.
Agora JsonList
também permite usar índices negativos, de forma que -1
será o último elemento, -2
o penúltimo, etc.
O objeto JsonList
inclui funções para classificação ou filtragem.
public function sort(callable $callback = null) : JsonList
: classifica a lista usando o retorno de chamada fornecido. Se nenhum retorno de chamada for fornecido, a lista será classificada usando a função de comparação padrão.public function filter(callable $callback) : JsonList
: filtra a lista usando o retorno de chamada fornecido. O retorno de chamada deve retornar um valor booleano. Se o retorno de chamada retornar true
, o elemento será incluído na lista resultante. Se retornar false
, o elemento será descartado.