jsonobject
jsonobject
es una clase PHP para facilitar el uso de objetos provenientes de una definición JSON. La idea surge del uso de pydantic
en Python y su capacidad para analizar y validar datos json en objetos.
jsonobject
Tuve que usar una API de PHP y esa API me devolvió jsonobject s. Entonces necesitaba analizarlos en objetos PHP que pudiera usar en la aplicación.
El flujo de trabajo es
jsonobject
para analizar la definición JSONTomemos el siguiente ejemplo 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
, podré definir mi modelo de datos usando las siguientes clases:
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 ' ,
];
}
Y luego agregue el siguiente comando:
$ user = User:: fromObject ( json_decode ( $ json_text_definition ));
La clase jsonobject
analizará el contenido en objetos y podremos usar sus atributos como se define:
echo ( $ user -> name );
Las clases definidas también pueden tener métodos que facilitarán la implementación del modelo de datos de la aplicación. Por ejemplo, sería posible definir la clase User
de esta manera:
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
La idea de la clase jsonobject
es utilizarla para analizar datos json en objetos. De modo que estos objetos pueden contener otros métodos que ayudarán a implementar el modelo de datos de la aplicación.
Cuando se analiza el objeto json (o matriz), su contenido se analiza recursivamente según los tipos definidos en la constante ATTRIBUTES
. Si los datos no son válidos porque no contienen los valores esperados, se lanza una excepción.
Para usar jsonobject se debe subclasificar jsonobject
y definir la constante ATTRIBUTES
para esa clase de modo que defina los atributos esperados para los objetos de esa clase, junto con el tipo de cada uno.
La constante ATTRIBUTES
es una matriz asociativa donde las claves son el nombre de cada atributo y los valores son el tipo de cada atributo .
Los tipos posibles pueden ser:
jsonobject
. Al definir el nombre de los atributos, se puede agregar un ?
al final del nombre para indicar que el atributo es opcional. Por ejemplo, ¿la address?
en la sección de casos de uso es opcional.
Cada campo se considera obligatorio, por lo que debe existir en el objeto (o matriz) analizado. Además, el objeto debe ser del tipo definido (es decir, debe ser analizado correctamente según el tipo específico).
Cualquier atributo que no sea opcional se considera obligatorio. Esto es de especial interés en dos puntos:
fromArray
o fromObject
).jsonobject
Al crear el objeto a partir de una estructura externa, jsonobject
se encargará de todos los campos obligatorios. Y si falta alguno de ellos, se generará una excepción.
En el siguiente ejemplo, se generará una excepción porque no se proporciona la edad del campo obligatorio.
class User extends jsonobject {
const ATTRIBUTES = [
" name " => " str " ,
" age " => " int " ,
];
}
( . . . )
$ user = User:: fromArray ([ " name " => " John " ]);
Al convertir el objeto en una matriz o en un objeto (u obtener su representación json), un campo obligatorio obtendrá un valor predeterminado, incluso si no está configurado.
Entonces en el siguiente ejemplo
class User extends jsonobject {
const ATTRIBUTES = [
" name " => " str " ,
" age " => " int " ,
" birthDate? " => " str "
];
}
$ user = new User ();
echo (( string ) $ user );
La salida será
{
"name" : " " ,
"age" : 0
}
Porque si bien los atributos nombre y edad son obligatorios y obtienen sus valores predeterminados (es decir, 0 para números, vacío para cadenas, listas o dictados), el atributo fecha de nacimiento no es obligatorio y aún no se ha configurado. Por lo que no se genera en la salida.
null
los atributos obligatoriosEl problema de establecer valores en nulos es de especial relevancia al considerar si un atributo es opcional o no.
Se puede pensar que, si establecemos un valor en nulo, significaría desarmar el valor y, por lo tanto, solo debería ser posible para valores opcionales pero no para valores obligatorios.
En jsonobject
tenemos un concepto diferente, porque establecer una propiedad en nulo significará "establecer un valor en nulo " y no desarmar la propiedad. Para desarmar la propiedad, deberíamos usar la función unset o algo así.
jsonobject
también permite anular valores. Para un atributo opcional, significa eliminar el valor y, por lo tanto, no tendrá ningún valor en una representación de matriz o en un objeto (si se recupera el valor, se establecerá en nulo ).
Pero para un atributo obligatorio, desarmarlo significará restablecer su valor al valor predeterminado . Eso significa que se inicializará con el valor predeterminado del tipo (es decir, 0 para números, vacío para listas, cadenas o dictados, etc.) o su valor predeterminado en la constante ATTRIBUTES
.
jsonobject
s también pueden heredar atributos de sus clases principales. Tomemos el siguiente ejemplo:
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 "
]
}
En este ejemplo, la clase Vehicle
solo tendrá los atributos marca y color , pero la clase Car
tendrá atributos marca , color y ruedas , mientras que la clase Boat
tendrá atributos marca , color y longitud .
Los objetos de clases secundarias de jsonobject
se pueden crear utilizando el método estático ::fromArray
o ::fromObject
, a partir de un objeto json analizado.
En el ejemplo anterior, si tenemos un archivo car.json con el siguiente contenido:
{
"brand" : " BMW " ,
"color" : " black "
}
Podemos usar el siguiente código para obtener una instancia de la clase Vehicle
:
$ json = file_get_contents ( " car.json " );
$ vehicle = Vehicle:: fromArray (( array ) json_decode ( $ json , true ));
Una alternativa es crear instancias de objetos como en el siguiente ejemplo.
* PHP 8 y superior:
$ car = new Car (brand: " BMW " , color: " black " , wheels: 4 );
* versiones PHP anteriores:
$ car = new Car ([ " brand " => " BMW " , " color " => " black " , " wheels " => 4 ]);
jsonobject
El jsonobject
es la clase principal de esta biblioteca. Sus métodos son:
__construct($data)
: crea un nuevo objeto a partir de los datos proporcionados.__get($name)
: devuelve el valor del atributo con el nombre dado__set($name, $value)
: establece el valor del atributo con el nombre dado__isset($name)
: devuelve verdadero si se establece el atributo con el nombre de pila__unset($name)
: desestablece el valor de un atributo opcional (o restablece el valor de un atributo obligatorio).toArray()
- Devuelve una matriz asociativa con los datos del objeto. La matriz se crea de forma recursiva, visitando cada uno de los subatributos de cada atributo.toObject()
: devuelve un objeto con los datos del objeto como atributos. La matriz se crea de forma recursiva, visitando cada uno de los subatributos de cada atributo.toJson()
: devuelve una cadena json con la representación del objeto como objeto estándar.::fromArray($data)
: crea un objeto analizando la matriz asociativa dada en los atributos definidos en la clase. Cada uno de los atributos se analiza recursivamente, según el tipo definido en él.::fromObject($data)
: crea un objeto analizando el objeto dado en los atributos definidos en la clase. Cada uno de los atributos se analiza recursivamente, según el tipo definido en él. JsonDict
Este objeto se utiliza para tratar con un diccionario proveniente de una definición json. La clase JsonDict
está tipificada para que cada uno de los elementos debe ser de un tipo determinado.
Los objetos JsonDict
se pueden utilizar como objetos tipo matriz (por ejemplo, $jsonDict["key1"]) pero (al momento de escribir este texto) el tipo de los elementos insertados en el diccionario no están verificados. El tipo se utiliza para analizar el contenido al crear el dict (por ejemplo, usando la función estática fromArray
) o para volcar el contenido en una matriz o un objeto (por ejemplo, usando la función toArray
).
Los métodos son:
toArray()
toObject()
::fromArray($data)
::fromObject($data)
Estos métodos se interpretan de la misma manera que en el caso de jsonobject
. Y el tipo de elementos en el dict puede referirse a tipos complejos que se considerarán de forma recursiva al analizar el contenido.
por ejemplo, escriba list[list[int]]
para analizar [ [ 1, 2, 3], [ 4, 5, 6 ]]
JsonArray
Este objeto es muy parecido a JsonDict
con la excepción de que los índices deben ser números enteros. En este caso, $value["key1"]
producirá una excepción.
En este caso, también se implementa la función para agregar elementos a la matriz (es decir, []
).
Al definir la clase, es posible inicializar los valores de los objetos recién creados y de aquellos atributos que son opcionales.
Hay dos maneras:
### Usando propiedades de clase
Es posible inicializar el valor de un objeto utilizando las propiedades de la clase, de modo que si el valor de un atributo se establece en la clase, se copiará a la instancia como un atributo, si está definido.
P.ej
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
' sex? ' => ' str '
];
public $ sex = " not revealed " ;
}
Ahora, el atributo sex
se inicializa para que no se revele en lugar de ser null .
La forma de hacerlo es definir una tupla [ <type>, <default value> ]
para el tipo de objeto. Tomando el siguiente ejemplo:
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
' sex? ' => [ ' str ' , ' not revealed ' ]
];
}
El atributo sex
es opcional al recuperar los datos del usuario. Usando esta nueva definición para la clase, si no se establece sex
, el valor se establecerá en "no revelado" en lugar de null
.
Una característica importante es que, si la cadena establecida como <valor predeterminado> corresponde a un método del objeto, se llamará al obtener el valor (si aún no se ha establecido), y el valor establecido para esa propiedad será ser el resultado de la llamada.
P.ej
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");
}
}
En este ejemplo, si no habíamos configurado la propiedad birthDate
pero se recupera, se calculará restando la edad a la fecha actual.
Si desea analizar un objeto arbitrario en un jsonobject
, es posible utilizar la función jsonobject ::parse_typed_value
. Esto es importante para poder convertir de cualquier tipo a un tipo jsonobject
.
p.ej
$ myobject = jsonobject :: parse_typed_value ( " list[str] " , [ " my " , " name " , " is " , " John " ]);
Obtendrá un objeto de tipo JsonList<str>
.
El comportamiento predeterminado de esta biblioteca es garantizar que los valores establecidos para los atributos coincidan con su tipo definido. Pero eso significa que, como float
no es un int
, establecer un float en 0
fallará porque 0
es un número entero. En ese caso, el usuario debe emitir los valores antes de asignarlos. Para controlar si se debe verificar tan estrictamente el tipo o no, es posible usar la constante STRICT_TYPE_CHECKING
.
Si
STRICT_TYPE_CHECKING
se establece enTrue
, los tipos se verificarán estrictamente y, por ejemplo, asignar9.3
a unint
generará una excepción. Si se establece enFalse
, los tipos numéricos se convertirán uno a otro. Entonces, por ejemplo, si asignamos9.3
a unint
se truncará automáticamente a9
.
Otra verificación de tipos importante es cuando se asigna un valor vacío (es decir, ""
o null
) a un tipo numérico. En ese caso, tenemos la constante STRICT_TYPE_CHECKING_EMPTY_ZERO
.
Si
STRICT_TYPE_CHECKING_EMPTY_ZERO
se establece enTrue
(el comportamiento predeterminado), al asignar un valor vacío a un tipo numérico, se considerará0
. es decir, asignar una cadena vacía o un valornull
a un atributoint
significará asignar0
. Si se establece enFalse
, la biblioteca verificará los tipos y eventualmente generará una excepción.
Ahora JsonList
también permite utilizar índices negativos, de modo que -1
será el último elemento, -2
el penúltimo, etc.
El objeto JsonList
incluye funciones para ordenar o filtrar.
public function sort(callable $callback = null) : JsonList
: ordena la lista utilizando la devolución de llamada dada. Si no se proporciona ninguna devolución de llamada, ordenará la lista utilizando la función de comparación predeterminada.public function filter(callable $callback) : JsonList
: filtra la lista utilizando la devolución de llamada proporcionada. La devolución de llamada debe devolver un valor booleano. Si la devolución de llamada devuelve true
, el elemento se incluirá en la lista resultante. Si devuelve false
, el elemento será descartado.