jsonobject
jsonobject
— это класс PHP, упрощающий использование объектов, полученных из определения JSON. Идея исходит из использования pydantic
в Python и его способности анализировать и проверять данные json в объектах.
jsonobject
Мне пришлось использовать API PHP, и этот API вернул мне jsonobject s. Поэтому мне нужно было проанализировать их в объекты PHP, которые я мог бы использовать в приложении.
Рабочий процесс
jsonobject
для анализа определения JSONДавайте возьмем следующий пример 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 "
}
}
Используя jsonobject
, я смогу определить свою модель данных, используя следующие классы:
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 ' ,
];
}
А затем добавьте следующую команду:
$ user = User:: fromObject ( json_decode ( $ json_text_definition ));
Класс jsonobject
выполнит анализ содержимого на объекты, и мы сможем использовать его атрибуты, как определено:
echo ( $ user -> name );
Определенные классы также могут иметь методы, которые упрощают реализацию модели данных приложения. Например, можно было бы определить класс User
следующим образом:
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
Идея класса jsonobject
состоит в том, чтобы использовать его для анализа данных json в объекты. Чтобы эти объекты могли содержать другие методы, которые помогут реализовать модель данных приложения.
При анализе объекта (или массива) json его содержимое анализируется рекурсивно в соответствии с типами, определенными в константе ATTRIBUTES
. Если данные недействительны, поскольку они не содержат ожидаемых значений, выдается исключение.
Чтобы использовать jsonobject необходимо создать подкласс jsonobject
и определить константу ATTRIBUTES
для этого класса, чтобы она определяла атрибуты, ожидаемые для объектов этого класса, а также тип каждого из них.
Константа ATTRIBUTES
представляет собой ассоциативный массив, где ключи — это имя каждого атрибута , а значения — тип каждого атрибута .
Возможными типами могут быть:
jsonobject
. При определении имени атрибутов можно добавить знак ?
в конце имени, чтобы указать, что атрибут является необязательным. Например, address?
в разделе вариантов использования является необязательным.
Каждое поле считается обязательным и должно существовать в анализируемом объекте (или массиве). Более того, объект должен относиться к определенному типу (т.е. он должен правильно анализироваться по конкретному типу).
Любой атрибут, который не является необязательным, считается обязательным. Это представляет особый интерес по двум причинам:
fromArray
или fromObject
).jsonobject
При создании объекта из внешней структуры jsonobject
позаботится о каждом обязательном поле. И если какой-либо из них отсутствует, возникнет исключение.
В следующем примере возникнет исключение, поскольку обязательное поле возраста не указано.
class User extends jsonobject {
const ATTRIBUTES = [
" name " => " str " ,
" age " => " int " ,
];
}
( . . . )
$ user = User:: fromArray ([ " name " => " John " ]);
При преобразовании объекта в массив или в объект (или при получении его представления в формате json) обязательное поле получит значение по умолчанию, даже если оно не установлено.
Итак, в следующем примере
class User extends jsonobject {
const ATTRIBUTES = [
" name " => " str " ,
" age " => " int " ,
" birthDate? " => " str "
];
}
$ user = new User ();
echo (( string ) $ user );
Результат будет
{
"name" : " " ,
"age" : 0
}
Потому что, хотя атрибуты name и age являются обязательными и получают значения по умолчанию (т. е. 0 для чисел, пустое для строк, списков или диктовок), атрибутbirthDate не является обязательным и еще не установлен. Поэтому он не генерируется на выходе.
null
для обязательных атрибутовПроблема установки нулевых значений имеет особую актуальность при рассмотрении вопроса о том, является ли атрибут необязательным или нет.
Можно подумать, что если мы установим значение null, это будет означать снятие значения, и поэтому это должно быть возможно только для необязательных значений, но не для обязательных значений.
В jsonobject
у нас другая концепция, поскольку установка свойства в значение null будет означать «установку значения в значение null », а не снятие свойства. Чтобы отменить свойство, мы должны использовать функцию unset или что-то в этом роде.
jsonobject
также позволяет сбрасывать значения. Для необязательного атрибута это означает удаление значения , и, следовательно, оно не будет иметь никакого значения в представлении массива или объекте (при получении значения ему будет присвоено значение null ).
Но для обязательного атрибута его снятие будет означать сброс его значения на значение по умолчанию . Это означает, что он будет инициализирован значением типа по умолчанию (т. е. 0 для чисел, пустым для списков, строк или диктовок и т. д.) или значением по умолчанию в константе ATTRIBUTES
.
jsonobject
также могут наследовать атрибуты своих родительских классов. Возьмем следующий пример:
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 "
]
}
В этом примере класс Vehicle
будет иметь атрибуты только «бренд» и «цвет» , но класс Car
будет иметь атрибуты «бренд» , «цвет» и «колеса» , а класс Boat
будет иметь атрибуты «бренд », «цвет» и «длина» .
Объекты из дочерних классов jsonobject
можно создавать с помощью статического метода ::fromArray
или ::fromObject
, начиная с проанализированного объекта json .
В предыдущем примере, если у нас есть файл car.json со следующим содержимым:
{
"brand" : " BMW " ,
"color" : " black "
}
Мы можем использовать следующий код, чтобы получить экземпляр класса Vehicle
:
$ json = file_get_contents ( " car.json " );
$ vehicle = Vehicle:: fromArray (( array ) json_decode ( $ json , true ));
Альтернативой является создание экземпляров объектов, как в следующем примере.
* PHP 8 и более поздние версии:
$ car = new Car (brand: " BMW " , color: " black " , wheels: 4 );
* предыдущие версии PHP:
$ car = new Car ([ " brand " => " BMW " , " color " => " black " , " wheels " => 4 ]);
jsonobject
jsonobject
является основным классом этой библиотеки. Его методы:
__construct($data)
— Создает новый объект из заданных данных.__get($name)
— Возвращает значение атрибута с заданным именем.__set($name, $value)
— устанавливает значение атрибута с заданным именем.__isset($name)
— возвращает true, если установлен атрибут с заданным именем.__unset($name)
— сбрасывает значение необязательного атрибута (или сбрасывает значение обязательного атрибута).toArray()
— Возвращает ассоциативный массив с данными объекта. Массив создается рекурсивно с посещением каждого из вложенных атрибутов для каждого атрибута.toObject()
— возвращает объект с данными объекта в качестве атрибутов. Массив создается рекурсивно с посещением каждого из вложенных атрибутов для каждого атрибута.toJson()
— возвращает строку json с представлением объекта как стандартного объекта.::fromArray($data)
— Создает объект путем анализа данного ассоциативного массива на атрибуты, определенные в классе. Каждый из атрибутов рекурсивно анализируется в соответствии с определенным для него типом.::fromObject($data)
— Создает объект путем анализа данного объекта на атрибуты, определенные в классе. Каждый из атрибутов рекурсивно анализируется в соответствии с определенным для него типом. JsonDict
Этот объект используется для работы со словарем, полученным из определения json. Класс JsonDict
типизирован таким образом, что каждый из элементов должен относиться к заданному типу.
Объекты JsonDict
можно использовать как объекты, подобные массиву (например, $jsonDict["key1"]), но (на момент написания этого текста) тип элементов, вставленных в словарь, не проверяется. Этот тип используется для анализа содержимого при создании словаря (например, с использованием статической функции fromArray
) или для выгрузки содержимого в массив или объект (например, с использованием функции toArray
).
Методы:
toArray()
toObject()
::fromArray($data)
::fromObject($data)
Эти методы интерпретируются так же, как и в случае с jsonobject
. А тип элементов в dict может относиться к сложным типам, которые будут рекурсивно учитываться при анализе содержимого.
например, тип list[list[int]]
будет использоваться для анализа [ [ 1, 2, 3], [ 4, 5, 6 ]]
JsonArray
Этот объект во многом аналогичен JsonDict
за исключением того, что индексы должны быть целыми числами. В этом случае $value["key1"]
создаст исключение.
В этом случае также реализована функция добавления элементов в массив (т.е. []
).
При определении класса можно инициализировать значения для вновь создаваемых объектов и для тех атрибутов, которые являются необязательными.
Есть два способа:
### Использование свойств класса
Можно инициализировать значение объекта с помощью свойств класса, поэтому, если значение атрибута установлено в классе, оно будет скопировано в экземпляр как атрибут, если оно определено.
Например
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
' sex? ' => ' str '
];
public $ sex = " not revealed " ;
}
Теперь атрибут sex
инициализируется как не раскрытый, а не нулевой .
Способ сделать это — определить кортеж [ <type>, <default value> ]
для типа объекта. Возьмем следующий пример:
class User extends jsonobject {
const ATTRIBUTES = [
' id ' => ' int ' ,
' name ' => ' str ' ,
' age ' => ' int ' ,
' emails ' => ' list[str] ' ,
' address? ' => ' Address ' ,
' sex? ' => [ ' str ' , ' not revealed ' ]
];
}
Атрибут sex
не является обязательным при получении пользовательских данных. Используя это новое определение класса, если sex
не установлен, вместо null
будет установлено значение «не раскрыто».
Важной особенностью является то, что если строка, установленная как <значение по умолчанию>, соответствует методу объекта, она будет вызвана при получении значения (если оно еще не установлено), и значение, установленное для этого свойства, будет быть результатом вызова.
Например
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");
}
}
В этом примере, если мы не установили birthDate
, но оно получено, оно будет вычислено путем вычитания возраста из текущей даты.
Если вы хотите проанализировать произвольный объект в jsonobject
, можно использовать функцию jsonobject ::parse_typed_value
. Это важно для возможности преобразования любого типа в тип jsonobject
.
например
$ myobject = jsonobject :: parse_typed_value ( " list[str] " , [ " my " , " name " , " is " , " John " ]);
Получит объект типа JsonList<str>
.
Поведение этой библиотеки по умолчанию — гарантировать, что значения, установленные для атрибутов, соответствуют их определенному типу. Но это означает, что это будет означать, что, поскольку float
не является целым int
, установка числа с плавающей запятой в 0
не удастся, поскольку 0
является целым числом. В этом случае пользователь должен привести значения перед их назначением. Чтобы контролировать, следует ли строго проверять тип или нет, можно использовать константу STRICT_TYPE_CHECKING
.
Если для
STRICT_TYPE_CHECKING
установлено значениеTrue
, типы будут строго проверяться, и, например, присвоение9.3
int
вызовет исключение. Если установлено значениеFalse
, числовые типы будут преобразованы один в другой. Например, если мы присвоим9.3
int
оно будет автоматически усечено до9
.
Другая важная проверка типа — это присвоение пустого значения (т. е. ""
или null
) числовому типу. В этом случае у нас есть константа STRICT_TYPE_CHECKING_EMPTY_ZERO
.
Если для
STRICT_TYPE_CHECKING_EMPTY_ZERO
установлено значениеTrue
(поведение по умолчанию), при присвоении пустого значения числовому типу оно будет считаться равным0
. т.е. присвоение пустой строки или значенияnull
атрибутуint
будет означать присвоение0
. Если установлено значениеFalse
, библиотека проверит типы и в конечном итоге вызовет исключение.
Теперь JsonList
также позволяет использовать отрицательные индексы, так что -1
будет последним элементом, -2
- предпоследним и т.д.
Объект JsonList
включает функции для сортировки и фильтрации.
public function sort(callable $callback = null) : JsonList
: сортирует список, используя заданный обратный вызов. Если обратный вызов не указан, он отсортирует список, используя функцию сравнения по умолчанию.public function filter(callable $callback) : JsonList
: фильтрует список, используя заданный обратный вызов. Обратный вызов должен возвращать логическое значение. Если обратный вызов возвращает true
, элемент будет включен в результирующий список. Если он вернет false
, элемент будет отброшен.