Files
server/shared/lib/Json/JsonSerializableObject.php
2026-02-10 18:46:11 -05:00

165 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace KTXF\Json;
use DateTimeInterface;
use DateTimeZone;
use DateInterval;
use KTXF\Json\JsonSerializable;
use KTXF\Json\JsonDeserializable;
abstract class JsonSerializableObject implements JsonSerializable, JsonDeserializable {
protected string $dateTimeFormat = 'c'; // ISO 8601 format by default
protected array $serializableProperties = []; // Empty array means serialize all properties
protected array $nonSerializableProperties = ['dateTimeFormat', 'serializableProperties', 'nonSerializableProperties']; // Properties to exclude from serialization
public function jsonSerialize(): mixed {
$vars = get_object_vars($this);
// Filter properties based on serializableProperties if specified
if (!empty($this->serializableProperties)) {
$vars = array_filter($vars, function($key) {
return in_array($key, $this->serializableProperties);
}, ARRAY_FILTER_USE_KEY);
}
// Process each property for special types
foreach ($vars as $key => $value) {
// Skip internal control properties
if (in_array($key, $this->nonSerializableProperties)) {
unset($vars[$key]);
continue;
}
// Handle DateTimeInterface (DateTime/DateTimeImmutable)
if ($value instanceof DateTimeInterface) {
$vars[$key] = $value->format($this->dateTimeFormat);
}
// Handle DateTimeZone
elseif ($value instanceof DateTimeZone) {
$vars[$key] = $value->getName();
}
// Handle DateInterval
elseif ($value instanceof DateInterval) {
$vars[$key] = $this->fromDateInterval($value);
}
// Handle backed enums
elseif ($value instanceof \BackedEnum) {
$vars[$key] = $value->value;
}
// Handle JsonSerializable objects
elseif ($value instanceof JsonSerializable) {
$vars[$key] = $value->jsonSerialize();
}
}
return $vars;
}
public function jsonDeserialize(array|string $data): static {
if (is_string($data)) {
$data = json_decode($data, true);
}
foreach ($data as $key => $value) {
if (property_exists($this, $key)) {
// Skip internal control properties
if (in_array($key, $this->nonSerializableProperties)) {
continue;
}
// Check if property should be deserialized (if serializableProperties is set)
if (!empty($this->serializableProperties) && !in_array($key, $this->serializableProperties)) {
continue;
}
$type = gettype($this->$key);
// Handle JsonDeserializable objects
if ($type === 'object' && $this->$key instanceof JsonDeserializable) {
$this->$key = $this->$key->jsonDeserialize($value);
}
// Handle DateTimeInterface (DateTime/DateTimeImmutable)
elseif ($type === 'object' && $this->$key instanceof DateTimeInterface) {
$this->$key = new \DateTimeImmutable($value);
}
// Handle DateTimeZone
elseif ($type === 'object' && $this->$key instanceof DateTimeZone) {
$this->$key = new DateTimeZone($value);
}
// Handle DateInterval
elseif ($type === 'object' && $this->$key instanceof DateInterval) {
$this->$key = $this->toDateInterval($value);
}
// Handle backed enums
elseif ($type === 'object' && $this->$key instanceof \BackedEnum) {
$enumClass = get_class($this->$key);
$this->$key = $enumClass::from($value);
}
// Handle regular values
else {
$this->$key = $value;
}
}
}
return $this;
}
protected function fromDateInterval(DateInterval $interval): string {
$spec = '';
// Handle negative intervals
if ($interval->invert === 1) {
$spec = '-';
}
$spec .= 'P';
if ($interval->y > 0) $spec .= $interval->y . 'Y';
if ($interval->m > 0) $spec .= $interval->m . 'M';
if ($interval->d > 0) $spec .= $interval->d . 'D';
$timePart = '';
if ($interval->h > 0) $timePart .= $interval->h . 'H';
if ($interval->i > 0) $timePart .= $interval->i . 'M';
if ($interval->s > 0) $timePart .= $interval->s . 'S';
if (!empty($timePart)) {
$spec .= 'T' . $timePart;
}
// Handle edge case of zero duration
if ($spec === 'P' || $spec === '-P') {
$spec = 'PT0S';
}
return $spec;
}
protected function toDateInterval(string $value): DateInterval {
$isNegative = false;
// Check for negative interval
if (str_starts_with($value, '-')) {
$isNegative = true;
$value = substr($value, 1);
}
// Create the interval
$interval = new DateInterval($value);
// Set invert property for negative intervals
if ($isNegative) {
$interval->invert = 1;
}
return $interval;
}
}