Skip to content

Commit

Permalink
Merge pull request #4638 from neos/bugfix/jsonSerializeablePropertyVa…
Browse files Browse the repository at this point in the history
…lues

BUGFIX: Neos Ui JSON serializable property values
  • Loading branch information
mhsdesign authored Feb 16, 2024
2 parents 6b73fe1 + eada1db commit 85c5e2d
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 4 deletions.
4 changes: 4 additions & 0 deletions Neos.ContentRepository/Classes/Domain/Model/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,10 @@ public function getProperty($propertyName, bool $returnNodesAsIdentifiers = fals
}

try {
/**
* In case the value is a value object it _will_ already be deserialized due to the feature in flow_json_array
* {@see \Neos\Flow\Persistence\Doctrine\DataTypes\JsonArrayType::deserializeValueObject}
*/
return $this->propertyMapper->convert($value, $expectedPropertyType);
} catch (\Neos\Flow\Property\Exception $exception) {
throw new NodeException(sprintf('Failed to convert property "%s" of node "%s" to the expected type of "%s": %s', $propertyName, $this->getIdentifier(), $expectedPropertyType, $exception->getMessage()), 1630675703, $exception);
Expand Down
21 changes: 17 additions & 4 deletions Neos.Neos/Classes/Service/Mapping/NodePropertyConverterService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Neos\Flow\Property\PropertyMapper;
use Neos\Flow\Property\PropertyMappingConfiguration;
use Neos\Flow\Property\PropertyMappingConfigurationInterface;
use Neos\Flow\Property\TypeConverter\DenormalizingObjectConverter;
use Neos\Utility\ObjectAccess;
use Neos\Utility\TypeHandling;
use Neos\ContentRepository\Domain\Model\NodeInterface;
Expand Down Expand Up @@ -155,15 +156,27 @@ public function getPropertiesArray(NodeInterface $node)
}

/**
* Convert the given value to a simple type or an array of simple types.
* Convert the given value to a simple type or an array of simple types or to a \JsonSerializable.
*
* @param mixed $propertyValue
* @param string $dataType
* @return mixed
* @param mixed $propertyValue the deserialized node property value
* @param string $dataType the property type from the node type schema
* @return \JsonSerializable|int|float|string|bool|null|array<mixed>
* @throws PropertyException
*/
protected function convertValue($propertyValue, $dataType)
{
if ($propertyValue instanceof \JsonSerializable && DenormalizingObjectConverter::isDenormalizable($propertyValue::class)) {
/**
* Value object support, as they can be stored directly the node properties flow_json_array
*
* If the value is json-serializable and deserializeable via the {@see DenormalizingObjectConverter} (via e.g. fromArray)
* We return the json-serializable directly.
*
* {@see \Neos\Flow\Persistence\Doctrine\DataTypes\JsonArrayType::deserializeValueObject()}
*/
return $propertyValue;
}

$parsedType = TypeHandling::parseType($dataType);

// This hardcoded handling is to circumvent rewriting PropertyMappers that convert objects. Usually they expect the source to be an object already and break if not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* source code.
*/

use GuzzleHttp\Psr7\Uri;
use Neos\ContentRepository\Domain\Model\Node;
use Neos\ContentRepository\Domain\Model\NodeType;
use Neos\Flow\Tests\FunctionalTestCase;
Expand Down Expand Up @@ -183,4 +184,67 @@ public function complexTypesWithGivenTypeConverterAreConvertedByTypeConverter()

self::assertEquals($expected, $actual);
}

/**
* @test
*/
public function jsonSerializedAbleTypesAreDirectlySerialized()
{
$voClassName = 'Value' . md5(uniqid(mt_rand(), true));
eval('class ' . $voClassName . ' implements \JsonSerializable' . <<<'PHP'
{
public function __construct(
public \Psr\Http\Message\UriInterface $uri
) {
}
public static function fromArray(array $array): self
{
return new self(
new \GuzzleHttp\Psr7\Uri($array['uri'])
);
}
public function jsonSerialize(): array
{
return [
'uri' => $this->uri->__toString()
];
}
}
PHP);

$propertyValue = new $voClassName(new Uri('localhost://foo.html'));
$expected = '{"uri":"localhost:\\/\\/foo.html"}';

$nodeType = $this
->getMockBuilder(NodeType::class)
->setMethods(['getPropertyType'])
->disableOriginalConstructor()
->getMock();
$nodeType
->expects(self::any())
->method('getPropertyType')
->willReturn(ImageInterface::class);

$node = $this
->getMockBuilder(Node::class)
->setMethods(['getProperty', 'getNodeType'])
->disableOriginalConstructor()
->getMock();
$node
->expects(self::any())
->method('getProperty')
->willReturn($propertyValue);
$node
->expects(self::any())
->method('getNodeType')
->willReturn($nodeType);

$nodePropertyConverterService = new NodePropertyConverterService();

$actual = $nodePropertyConverterService->getProperty($node, 'dontcare');

self::assertEquals($expected, json_encode($actual));
}
}

0 comments on commit 85c5e2d

Please sign in to comment.