Skip to content

Instantly share code, notes, and snippets.

@lt
Last active September 19, 2019 15:06
Show Gist options
  • Save lt/269810c917f2dbd1e09efe56688c441a to your computer and use it in GitHub Desktop.
Save lt/269810c917f2dbd1e09efe56688c441a to your computer and use it in GitHub Desktop.
Decode JSON into a specific class
<?php
function deep_copy_and_validate_object($source, $destination)
{
$reflectionClass = new \ReflectionClass($destination);
foreach ($reflectionClass->getProperties() as $reflectionProperty) {
$propertyName = $reflectionProperty->getName();
if (property_exists($source, $propertyName)) {
$propertyValue = $source->$propertyName;
$expectedClass = false;
$expectedArray = false;
$expectedType = false;
$comment = $reflectionProperty->getDocComment();
if (is_string($comment) && preg_match('~^(?:/\*|\s*)\*\s*@var\s+\$([^\[\]\s]+)(\[\])?\s+(\S+)~m', $comment, $match)) {
if ($match[1] === $propertyName) {
$expectedArray = !empty($match[2]);
if (class_exists($match[3])) {
$expectedClass = $match[3];
} else {
$expectedType = $match[3];
}
}
}
if ($expectedClass && is_object($propertyValue)) {
if ($expectedArray) {
throw new \DomainException('Expected an array but got an object: ' . $propertyName);
}
$tempDestination = new $expectedClass;
deep_copy_and_validate_object($propertyValue, $tempDestination);
$propertyValue = $tempDestination;
}
else if ($expectedArray && is_array($propertyValue)) {
if ($expectedClass) {
$tempArray = [];
foreach ($propertyValue as $tempSource) {
if (!is_object($tempSource)) {
throw new \DomainException('Array contains value that cannot be copied to annotated class');
}
$tempDestination = new $expectedClass;
deep_copy_and_validate_object($tempSource, $tempDestination);
$tempArray[] = $tempDestination;
}
$propertyValue = $tempArray;
}
else {
foreach ($propertyValue as $arrayValue) {
$actualType = gettype($arrayValue);
if ($actualType !== $expectedType) {
throw new \DomainException('Expected type ' . $expectedType . ' but got type ' . $actualType . ': ' . $propertyName);
}
}
}
}
else if ($expectedClass || $expectedArray) {
$actualType = gettype($propertyValue);
if ($expectedClass) {
throw new \DomainException('Expected an object but got type ' . $actualType . ': ' . $propertyName);
}
if ($expectedArray) {
throw new \DomainException('Expected an array but got type ' . $actualType . ': ' . $propertyName);
}
}
else if ($expectedType) {
$actualType = gettype($propertyValue);
if ($actualType !== $expectedType) {
throw new \DomainException('Expected type ' . $expectedType . ' but got type ' . $actualType . ': ' . $propertyName);
}
}
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($destination, $propertyValue);
}
}
}
function json_decode_class(string $json, $destination, int $depth = 512, int $options = 0)
{
$decodedObject = json_decode($json, false, $depth, $options);
if (!is_object($decodedObject)) {
return $decodedObject;
}
if (is_string($destination) && class_exists($destination)) {
$destination = new $destination;
}
else if (!is_object($destination)) {
throw new InvalidArgumentException('$class must be the fully qualified name of an existing class, or an object');
}
deep_copy_and_validate_object($decodedObject, $destination);
return $destination;
}
class C2 {
/** @var $qux string */
private $qux;
}
class C1 {
/** @var $foo C2 */
public $foo;
/** @var $bar[] C2 */
public $bar;
}
$json = <<<EOJ
{
"foo": {
"qux": "Hello"
},
"bar": [
{
"qux": "X"
},
{
"qux": "Y"
},
{
"qux": "Z"
}
],
"does not exist": "does not get copied"
}
EOJ;
$obj = json_decode_class($json, 'C1');
var_dump($obj);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment