Skip to content

Commit

Permalink
Merge pull request #730 from schmittjoh/std-class-and-cleanup
Browse files Browse the repository at this point in the history
Fix stdClass inconsistencies when serializing to JSON
  • Loading branch information
goetas authored Apr 21, 2017
2 parents 89419d1 + 39f8401 commit f547e35
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 27 deletions.
3 changes: 3 additions & 0 deletions src/JMS/Serializer/GenericSerializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
use JMS\Serializer\Exception\InvalidArgumentException;
use JMS\Serializer\Metadata\PropertyMetadata;

/**
* @deprecated
*/
abstract class GenericSerializationVisitor extends AbstractVisitor
{
private $navigator;
Expand Down
4 changes: 2 additions & 2 deletions src/JMS/Serializer/Handler/FormErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
use JMS\Serializer\YamlSerializationVisitor;
use JMS\Serializer\JsonSerializationVisitor;
use JMS\Serializer\GraphNavigator;
use JMS\Serializer\GenericSerializationVisitor;
use JMS\Serializer\VisitorInterface;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormError;
use Symfony\Component\Translation\TranslatorInterface;
Expand Down Expand Up @@ -125,7 +125,7 @@ private function getErrorMessage(FormError $error)
return $this->translator->trans($error->getMessageTemplate(), $error->getMessageParameters(), 'validators');
}

private function convertFormToArray(GenericSerializationVisitor $visitor, Form $data)
private function convertFormToArray(VisitorInterface $visitor, Form $data)
{
$isRoot = null === $visitor->getRoot();

Expand Down
219 changes: 196 additions & 23 deletions src/JMS/Serializer/JsonSerializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,63 +19,236 @@
namespace JMS\Serializer;

use JMS\Serializer\Metadata\ClassMetadata;
use JMS\Serializer\Exception\InvalidArgumentException;
use JMS\Serializer\Metadata\PropertyMetadata;

class JsonSerializationVisitor extends GenericSerializationVisitor
{
private $options = 0;

public function getResult()
private $navigator;
private $root;
private $dataStack;
private $data;

public function setNavigator(GraphNavigator $navigator)
{
$result = @json_encode($this->getRoot(), $this->options);
$this->navigator = $navigator;
$this->root = null;
$this->dataStack = new \SplStack;
}

switch (json_last_error()) {
case JSON_ERROR_NONE:
return $result;
/**
* @return GraphNavigator
*/
public function getNavigator()
{
return $this->navigator;
}

case JSON_ERROR_UTF8:
throw new \RuntimeException('Your data could not be encoded because it contains invalid UTF8 characters.');
public function visitNull($data, array $type, Context $context)
{
return null;
}

default:
throw new \RuntimeException(sprintf('An error occurred while encoding your data (error code %d).', json_last_error()));
public function visitString($data, array $type, Context $context)
{
if (null === $this->root) {
$this->root = $data;
}

return (string) $data;
}

public function getOptions()
public function visitBoolean($data, array $type, Context $context)
{
return $this->options;
if (null === $this->root) {
$this->root = $data;
}

return (boolean) $data;
}

public function setOptions($options)
public function visitInteger($data, array $type, Context $context)
{
$this->options = (integer) $options;
if (null === $this->root) {
$this->root = $data;
}

return (int) $data;
}

public function visitDouble($data, array $type, Context $context)
{
if (null === $this->root) {
$this->root = $data;
}

return (float) $data;
}

/**
* @param array $data
* @param array $type
* @param Context $context
* @return mixed
*/
public function visitArray($data, array $type, Context $context)
{
$result = parent::visitArray($data, $type, $context);
$this->dataStack->push($data);

$isHash = isset($type['params'][1]);

if (null !== $this->getRoot() && isset($type['params'][1]) && 0 === count($result)) {
// ArrayObject is specially treated by the json_encode function and
// serialized to { } while a mere array would be serialized to [].
return new \ArrayObject();
if (null === $this->root) {
$this->root = $isHash ? new \ArrayObject() : array();
$rs = &$this->root;
} else {
$rs = $isHash ? new \ArrayObject() : array();
}

$isList = isset($type['params'][0]) && ! isset($type['params'][1]);

foreach ($data as $k => $v) {
$v = $this->navigator->accept($v, $this->getElementType($type), $context);

if (null === $v && $context->shouldSerializeNull() !== true) {
continue;
}

if ($isList) {
$rs[] = $v;
} else {
$rs[$k] = $v;
}
}

return $result;
$this->dataStack->pop();
return $rs;
}

public function startVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
{
if (null === $this->root) {
$this->root = new \stdClass;
}

$this->dataStack->push($this->data);
$this->data = array();
}

public function endVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
{
$rs = parent::endVisitingObject($metadata, $data, $type, $context);
$rs = $this->data;
$this->data = $this->dataStack->pop();

// Force JSON output to "{}" instead of "[]" if it contains either no properties or all properties are null.
if (empty($rs)) {
$rs = new \ArrayObject();
}

if (array() === $this->getRoot()) {
$this->setRoot(clone $rs);
}
if ($this->root instanceof \stdClass && 0 === $this->dataStack->count()) {
$this->root = $rs;
}

return $rs;
}

public function visitProperty(PropertyMetadata $metadata, $data, Context $context)
{
$v = $this->accessor->getValue($data, $metadata);

$v = $this->navigator->accept($v, $metadata->type, $context);
if (null === $v && $context->shouldSerializeNull() !== true) {
return;
}

$k = $this->namingStrategy->translateName($metadata);

if ($metadata->inline) {
if (is_array($v)) {
$this->data = array_merge($this->data, $v);
}
} else {
$this->data[$k] = $v;
}
}

/**
* Allows you to add additional data to the current object/root element.
* @deprecated use setData instead
* @param string $key
* @param integer|float|boolean|string|array|null $value This value must either be a regular scalar, or an array.
* It must not contain any objects anymore.
*/
public function addData($key, $value)
{
if (isset($this->data[$key])) {
throw new InvalidArgumentException(sprintf('There is already data for "%s".', $key));
}

$this->data[$key] = $value;
}

/**
* Checks if some data key exists.
*
* @param string $key
* @return boolean
*/
public function hasData($key)
{
return isset($this->data[$key]);
}

/**
* Allows you to replace existing data on the current object/root element.
*
* @param string $key
* @param integer|float|boolean|string|array|null $value This value must either be a regular scalar, or an array.
* It must not contain any objects anymore.
*/
public function setData($key, $value)
{
$this->data[$key] = $value;
}

public function getRoot()
{
return $this->root;
}

/**
* @param array|\ArrayObject $data the passed data must be understood by whatever encoding function is applied later.
*/
public function setRoot($data)
{
$this->root = $data;
}


public function getResult()
{
$result = @json_encode($this->getRoot(), $this->options);

switch (json_last_error()) {
case JSON_ERROR_NONE:
return $result;

case JSON_ERROR_UTF8:
throw new \RuntimeException('Your data could not be encoded because it contains invalid UTF8 characters.');

default:
throw new \RuntimeException(sprintf('An error occurred while encoding your data (error code %d).', json_last_error()));
}
}

public function getOptions()
{
return $this->options;
}

public function setOptions($options)
{
$this->options = (integer) $options;
}
}
2 changes: 1 addition & 1 deletion src/JMS/Serializer/Serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ private function handleDeserializeResult($visitorResult, $navigatorResult)

private function convertArrayObjects($data)
{
if ($data instanceof \ArrayObject) {
if ($data instanceof \ArrayObject || $data instanceof \stdClass) {
$data = (array) $data;
}
if (is_array($data)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public function testSerializeWithNonUtf8EncodingWhenDisplayErrorsOn()

public function testSerializeArrayWithEmptyObject()
{
$this->assertEquals('{"0":{}}', $this->serialize(array(new \stdClass())));
$this->assertEquals('[{}]', $this->serialize(array(new \stdClass())));
}

public function testSerializeRootArrayWithDefinedKeys()
Expand Down Expand Up @@ -350,6 +350,8 @@ public function getTypeHintedArraysAndStdClass()

return [

[[$c1], '[{}]', SerializationContext::create()->setInitialType('array<stdClass>')],

[[$c2], '[{"foo":"bar"}]', SerializationContext::create()->setInitialType('array<stdClass>')],

[[$tag], '[{"name":"tag"}]', SerializationContext::create()->setInitialType('array<JMS\Serializer\Tests\Fixtures\Tag>')],
Expand Down

0 comments on commit f547e35

Please sign in to comment.