Jmsserializerbundle: serializing a json array using {} instead of []

Created on 20 Mar 2014  ·  109Comments  ·  Source: schmittjoh/JMSSerializerBundle

HI, im using version 1.3, if i call serialize() with something like
array(
$obj
)

y get this

{"0": { _the object serialization_}}

but i spect something like this

[{ _the object serialization_}]

i manage to get it working by commeting this lines on JsonSerializationVisitor class:

public function endVisitingObject(ClassMetadata $metadata, $data, array $type, Context $context)
    {
        $rs = parent::endVisitingObject($metadata, $data, $type, $context);
        // 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);
        //    }
        //}

        return $rs;
    }

bug serializer issue

Most helpful comment

A temporary workaround:

<?php
/**
 * @author  Almog Baku
 *          [email protected]
 *          http://www.AlmogBaku.com
 *
 * 25/06/15 15:28
 */

namespace AlmogBaku\ApiBundle\Serializier;

use \JMS\Serializer\JsonSerializationVisitor as JsonSerializationVisitorBase;

class JsonSerializationVisitor extends JsonSerializationVisitorBase
{
    public function getResult()
    {
        if($this->getRoot() instanceof \ArrayObject) {
            $this->setRoot((array) $this->getRoot());
        }
        return parent::getResult();
    }
}

Then define in your parameters:

parameters:
    jms_serializer.json_serialization_visitor.class: AlmogBaku\ApiBundle\Serializier\JsonSerializationVisitor

All 109 comments

I'm having a similar problem where some entity collections are encoded like this
[{"id":1,"comment"....
And others like this
{"0":{"id":2,"first_name"....

Both arrays start like this
array(3) {
[0] =>
class...

I had this same problem, but I got around it by casting the data to an array in the JsonSerializationVisitor class:

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

I have the same problem.

Same problem here

Me too

Make sure it is regular array (not some kind of ArrayCollection) and the indexes are proper. Try rebuild indexes with array_values. It helped me.

Thank you nostrzak. This solved the problem.
I added a virtual property that returns the array wrapped in an array_values call.

I am also having this problem after updating from 0.9 to 1.0 (Serializer version 0.9 to 0.16). This is a huge problem as large portions of my code base are expecting arrays, not objects.

It seems that it's the root object of serialization that is a problem for me. If i have a variable that is an array of entities, and i serialize it:

    $serializer->serialize($myRevisions, 'json');

I get

    {"0":{ ###first object###, "1":{ ###second object###}}

When I expect

    [{ ### first ###}, { ### second ### }]

This is not a "I can't get it to work" thing, this was working fine until i upgraded. Additionally, I see inconsistent behavior if i start wrapping in more arrays.

If i do instead:

    $serializer->serialize(array($myRevisions), 'json');

I get

        {"0":[{ ### first ###}, { ### second ### }]}

Why is $myRevisions now serializing as an array and not a hash? This is very inconsistent. If i wrap again:

    $serializer->serialize(array(array($myRevisions)), 'json');

I get

    {"0":[[{ ### first ###}, { ### second ### }]]}

So, once an array goes one level down, it will serialize as an array, but if it's the root element, it will _always_ be a hash. If it was the expected behavior of these arrays to be hashes, i'd expect the behavior to be uniform.

This is affecting me when serializing: ArrayCollections, Array, array, [], Doctrine/ORM/Query->getResult() objects, and more.

nostrzak's suggestion of array_values did not fix my above examples, although javiern's comment did.

More weirdness. If i serialize an array of arrays (with one empty array), I get the proper array notation:

$serializer->serialize(array(array(), array(1,2,3,4)), 'json');

Yeilds:

[[], [1, 2, 3, 4]]

If I add my array of entities at the end, everything stays an array

$serializer->serialize(array(array(), array(1,2,3,4), $myRevisions), 'json');
[[], [1, 2, 3, 4], [ { ### first ###}, {### second ###}]]

But, if i put the array of entities on _first_, it converts to a hash:

$serializer->serialize(array($myRevisions, array(), array(1,2,3,4)), 'json');
{"0": [ { ### first ###}, {### second ###}], "1": [], "2": [1, 2, 3, 4]}

So I assume the issue is that something in my entities is throwing the code off.

I noticed that when array have empty element (like yours [ ]) it is serialized with the indexes. Maybe you have some empty values because of validation groups?

I found that using ArrayObject inside JmsSerializer instead of regular array causes the problem in certain cases. Casting it directly to array with ArrajObject::getArrayCopy() in file JsonSerializationVisitor.php:29 solves the problem in all my cases.

This is a big issue for me, as my front-end expects an array of elements, and not an object.

Any idea when this issue will be addressed?
Anything I can do to help?

+1

:+1:

Same issue here with an ArrayCollection, but I noticed that:

When I return a response from a @ParamConverter conversion, I get:

[ {}, {}, {} ] (as expected)

When I return a response from a $this->getDoctrine()->getRepository(...)->find(...), I get:

{ 3: {}, 4: {}, 5: {} }

I solved it with an Exclude and a VirtualProperty as advised by a previous comment.

    /**
     * @ORM\OneToMany(targetEntity="TestGoal", mappedBy="test",cascade={"persist"})
     * @Serializer\Exclude
     */
    protected $goals;

    /**
     * @Serializer\VirtualProperty
     * @Serializer\SerializedName("goals")
     *
     * Explanations:
     *
     * With this virtual property I force response into an array.
     * I dont know why but goals was sometime returned like [0 => {}, 1 => {}] instead of [{}, {}].
     * Then the JSON serialization was wrong too: {0 => {}, 1 => {}} instead of [{}, {}].
     *
     */
    public function goalsValues() {
        // I tried `array_values` directly inside the getGoals() function but that didnt work.
        return array_values($this->getGoals()->toArray());
    }

+1

+1 for a fix! Seeing the same inconsistent behavior too

I am seeing this issue on a OneToMany situation explained here: https://github.com/schmittjoh/serializer/issues/387

Even when I try to create a virtualized solution like @ggregoire I still get an object response.

+1

+1

Same issue here. its converting {} to [], which messes up Angular $watch

+1

I'm experiencing this issue when the context is removed from the array within GenericSerializationVisitor::visitArray and where the context's key is 0.

I have solved this by extending JsonSerializationVisitor, overriding the visitArray function and then rebuilding array's indexes returned by the parent with array_values.

I then replaced the 'jms_serializer.json_serialization_visitor.class' parameter with my class.

Of course this a JMSSerializer issue rather than the bundle but I thought I'd post my findings here just in case it saves somebody else an hour of head banging.

:+1:

The most strange, I have different results:

If I get array of object from a find on doctrine:

$incidents = $this->getDoctrine()->getRepository('AppBundle:Incident')->findAll();

This returns a proper json result.

But If y create an ArrayCollection object:

/** @var Object[] $objects */
$objects = [];
foreach ($state as $objectName => $objectInfo) {
    $object = new Object();
    $object->setName($objectName);
    array_push($objects, $object);
}

return new ArrayCollection($objects);

Then I get an ugly associative array.

Using thoses vendor packages:

$ composer info -i | grep jms
jms/aop-bundle                           1.0.1               Adds AOP capabilities to Symfony2
jms/cg                                   1.0.0               Toolset for generating PHP code
jms/di-extra-bundle                      1.4.0               Allows to configure dependency injection using annotations
jms/metadata                             1.5.1               Class/method/property metadata management in PHP
jms/parser-lib                           1.0.0               A library for easily creating recursive-descent parsers.
jms/security-extra-bundle                1.5.1               Enhances the Symfony2 Security Component by adding several new features
jms/serializer                           0.16.0              Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.
jms/serializer-bundle                    0.12.0              Allows you to easily serialize, and deserialize data of any complexity

Same issue using dev-master:

jms/serializer                           dev-master e619ca4  Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.
jms/serializer-bundle                    dev-master 8bce2e8  Allows you to easily serialize, and deserialize data of any complexity

@coatezy I'm trying to apply your solution.

Can you paste your new Visitor class and your fos_rest configuration file please?

Thanks.

Another clue, for the ArrayCollection, I'm using filter like this

        return $objects->filter(function (Object $object) {
            return $object->condition() === true;
        });

So my array has missing numeric keys. By adding an array_values, this is fixed.

EDIT: Or simply add $objects->getValues() instead of $objects->toArray().

@Soullivaneuh if your array is not indexed with a sequence from 0 to count($array) - 1, getting an JS object rather than an array is the expected behavior, because this is how associative arrays need to be converted to JSON. And if keys are not such sequence, your array is a map, not a list.

Yeah, I learned that on my depend. ;)

Thanks.

Sullivan SENECHAL

i tried commenting the JsonSerializationVisitor class, using array_values, but nothing worked , also the array of objects i'm trying to serialize it has keys from 0 -> count(array) -1 .

Is there some other workaround for this? :(

No. Unfortunately these bundles in the "Symfony ecosystem" are using arrays because Symfony forces it to be so. Its request bag only holds arrays. Its forms are made to work with arrays, etc. Even if you fix JMS Serializer, you can not POST your data and get an empty object in your $request... and even if you fix that your app will crash when you pass that request to the form, so you'd have to pretty much override the whole framework - that's why I say it cant be done (in reality it could but you'd be writing so much override code it'd be untenable).

As it turns out though, plain PHP can handle it just fine, so thats your workaround.

$str = "{foo:[], bar:{}}";
var_dump(json_encode(json_decode($str)) === $str); // true

Oh so often I find frameworks getting in the way when the language worked fine. That's why I prefer microframeworks. I've wasted 10s of man hours debugging what can be done in 1 line with raw PHP code.

As you can see, it would be unfair to blame PHP. Blame the "PHP Style" (Symfony style). It introduces the concept of casting objects to arrays, doing some processing, then converting back to objects. Presumably this is so the programmer can type foo['bar'] instead of foo->bar. I say its just laziness/ignorance of data types... but now its a part of the standard (Symfony core). You can see the mess its created. Its forced JMS Serializer to work the way it does, and if they change it they loose Symfony support so thats a no go.

See me rant more about this issue here - https://github.com/FriendsOfSymfony/FOSRestBundle/issues/980

Ok thanks for your explanation @joshribakoff

@joshribakoff I don't think this is the same issue. What you are ranting about in https://github.com/FriendsOfSymfony/FOSRestBundle/issues/980 is the invalid handling of empty objects.

I do believe this issue is about the serialization of actual data, and more specifically, the fact that arrays or array collections of data sometimes are serialized into objects of data with integer property names.

@erichfosse oops you're right. I still contend that its somewhat related. The PHP community has a coding standards problem. Having "Array objects" is just like an oxymoron & is confounding the issue(s), which manifests itself as an issue where programmers get a different data type than expected.

TL;DR watch out for recursion

I have some news, but maybe someone could help me understanding what's happening.

Some background info:

  • i am trying to serialize objects that contains an array of objects.
  • In the model, i defined a virtual property that returns an array of objects with the keys that go from 0 -> count() -1
  • in the final json , the result is the "wrong" array as stated in this whole thread.

I started throwing exceptions everywhere to understand what's going on under the hood and i finally noticed that something odd is happening in the visitArray function.
Therefore:
1) i copied the JsonSerializationVisitor so i had MY serialization visitor object
2) the function visitArray starts with:

$result = parent::visitArray($data, $type, $context);

and this $result is an array with the keys starting from ONE
is this a bug or a wanted behavior?

Update:
somewhere ,somehow from the model virtual property result to the serialization listener, a null appears as first element, thus causing the NON serialization of null, and hence the array that starts from 1

Final Update:

The problem was due to recursion: inside the objects a recursion occurred and the serializer replaces the object it is visiting with null, to avoid a serialization recursion.

I write it down in the hope that this could help someone with the same problem in the future

I could fix this using Doctrine's getArrayResult() instead of getResult(). However, this means that you do not get a collection of objects, but a nested array, as the docs say «for read-only purposes». Worked in my use case and I did not look into possible root causes.

Is this fixed?, i have this same problem and we have to "workaround" it with an angular.forEach... very frustrating.

Any solution \ workaround for this?

For me it happens when I have a OneToMany relationship (e.g Category > Products) and I remove the first element (a Product) from the ArrayCollection.
The initial ArrayCollection:

    0 => product0
    1 => product1
    2 => ...
    ...

is serialized correctly while the resulting ArrayCollection:

    1 => product1
    2 => ...
    ...

is incorrectly serialized as an object.

I fixed it by completing the removeProductmethod of the Category class like that:

    public function removeProduct(Product $product)
    {
        $this->products->removeElement($product);

        // Fix the keys of the ArrayCollection
        $this->products = new ArrayCollection($this->products->getValues());
    }

This certainly isn't optimal but it helped me overcome this issue.

otherwise there is the new serializer of Symfony2.7!

I oversight this new feature. I'll definitely give it a try.

I started having this issue seemingly for no reason. Upon further inspection, I noticed that I had a null element inside my array. When I removed it, it worked as expected and used [] instead of {}. I believe I'm running version 1.3 of the serializer.

Seems like a bug to me.

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

        // 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); <----THIS WOULD SET THE REFERENCE TO ARRAYOBJECT
            }
        }

        return $rs;
    }

If this happens, than due to this:

/**
     * @param array $data
     * @param array $type
     */
    public function visitArray($data, array $type, Context $context)
    {
        if (null === $this->root) {
            $this->root = array();
            $rs = &$this->root; <------REFERENCE
        } else {
            $rs = array();
        }

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

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

            $rs[$k] = $v;
        }

        return $rs;
    }

The first mentioned method sets the root to ArrayObject, because of the still existing refrence.

And an ArrayObject serializes to {0:bla} and not [bla].

Easy fix for now:

namespace YourOwnNamespace;

class JsonSerializationVisitor extends \JMS\Serializer\JsonSerializationVisitor{
    public function getResult()
    {
        //EXPLICITLY CAST TO ARRAY
        $result = @json_encode((array) $this->getRoot(), $this->getOptions());

        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()));
        }
    }
} 

and than in you config:

parameters:
  jms_serializer.json_serialization_visitor.class: YouOwnNamespace\JsonSerializationVisitor

In my case this error occurred only when using @GROUPS and only if one property contained a entity with no matching @GROUPS attributes.

Maybe this helps you....

I'm facing the same issue when I use serializerGroups in the @View annot.
Is there any solution by far?

A temporary workaround:

<?php
/**
 * @author  Almog Baku
 *          [email protected]
 *          http://www.AlmogBaku.com
 *
 * 25/06/15 15:28
 */

namespace AlmogBaku\ApiBundle\Serializier;

use \JMS\Serializer\JsonSerializationVisitor as JsonSerializationVisitorBase;

class JsonSerializationVisitor extends JsonSerializationVisitorBase
{
    public function getResult()
    {
        if($this->getRoot() instanceof \ArrayObject) {
            $this->setRoot((array) $this->getRoot());
        }
        return parent::getResult();
    }
}

Then define in your parameters:

parameters:
    jms_serializer.json_serialization_visitor.class: AlmogBaku\ApiBundle\Serializier\JsonSerializationVisitor

Looks good.

My solution is to remove serializerEnableMaxDepthChecks = true from View annotation. But it still not good enough because of huge response. I think this problem because of circular reference. I tried to remove one 'OneToMany' fields from response and it looks good to me. Once I added it back, I got an object instead of array again

Using the group annotation in my case actually _caused_ it :|


Q: Why is this email five sentences or less?
A: http://five.sentenc.es

On Wed, Jul 1, 2015 at 5:39 AM, Eugene Boiarynov [email protected]
wrote:

Annotation in controller helped me: @View(serializerGroups={"Default"})


Reply to this email directly or view it on GitHub
https://github.com/schmittjoh/JMSSerializerBundle/issues/373#issuecomment-117442114
.

+1 for ggregoire's solution. simple and quick fix.

The problem is also cause by the group annotation in my case, when I have only 1 object in the collection (with 2 or more, I receive an array)

We expected the same issue when serializing with serialization groups, what really caused the array to be serialized as object with key property instead of json array was that it contained a subelement which exposed no property for the used list.

I had the same issue as cfoehrdes.
An inconsistent rendering, sometimes it's an array sometimes it's an object.
I also have an object with a numeric key when the serializer returned only 1 data.

confirmed @cfoehrdes & @jeremygachet : this only occurs when using @Groups where subelements are entities and have nothing to expose with the specified group(s)

Note : when using FOSRestBundle, this little trick (usage of setSerializeNull(true)) might help :

        $context = new SerializationContext();
        $context->setSerializeNull(true);
        $view = View::create($objects,200);
        $view->setSerializationContext($context);
        return $this->handleView($view);

The workaround provided by @AlmogBaku based on @ARoddis code worked for me for the case in hand. Thanks for that! I hope there will be no unwanted side-effects in other serialization cases. Is there a reason that could not be a patch for this bug?

Same problem : when a subelement does not have any exposed property (or the property is null), it will fuck the output. I lost 3 hours on that, I'm quite happy that I managed to find this thread, but please fix this, do something.

+1

It reproduces when I add two groups or more to an entity

Can we have any news on this ? Every once in a while, routes are returning objects instead of arrays, it's so random and dependant of the data, that it would take me a week to go through each route of my API and do tests. The mobile developers I work for are upset and I look like an idiot because the serialization is random. Can we have an official response if this issue will be handled or not ?
Please, react.
@schmittjoh

:+1:

@ttibensky PR fixed it for our use case, any news about a potential merge @schmittjoh ?

@AngryDeveloper unfortunately no. I also sent them an email asking if they could merge it, but got no response. So I forked the bundle, applied the patch on a fork and used the fork instead official bundle. I believe you can do the same thing.

check this for more information about how to override official bundle with a fork using composer https://getcomposer.org/doc/05-repositories.md#vcs

I have the same problem too, coming only when updating the object serialized.

Thanks @ggregoire for your solution

Hi,
Just passing by to share my experience on the same issue.
I got the same issue on a collection (got an object instead of an array). I had 3 collections, only one was affected by this issue after some changes but I didn't understood why, there was no obvious reason(s).
Strangely, right after i persisted a new object in database for this collection, the bug disappeared and I got an array. (For anyone wondering, I cleared the symfony cache as well as my browser cache many times before - without success)

For anyone affected by this issue, I solved it by simply adding a max_depth property.

IDCI\Bundle\PatentBundle\Entity\ApplicationZone:
    exclusion_policy: ALL
    properties:
        id:
            expose: true
        countryCode:
            expose: true
        patents:
            expose: true
            max_depth: 4

I can finally retrieve applicationZones in an json array instead of a json object.

ApplicationZone got a OneToMany relation with Patent. Patent is an entity related to itself with a OneToMany a well. So as stated above by stormsson, there is indeed a recursion issue.

It's strange as the issue remains with a max_depth equal to 0, 1, 5 and more, but i got a json array when max_depth equal 2, 3 and 4.

+1 and Thx @AlmogBaku for your patch, it works well :) -> https://github.com/schmittjoh/JMSSerializerBundle/issues/373#issuecomment-115238225

The root of the problem seems to be how json_encode works.

$c = new stdClass();
$c->foo = 'bar';

$arrayOne = [
    0 => $c,
    1 => $c
];

$arrayTwo = [
    0 => $c,
    3 => $c
];

echo json_encode($arrayOne) . PHP_EOL;
echo json_encode($arrayTwo);

Will print

[{"foo":"bar"},{"foo":"bar"}]
{"0":{"foo":"bar"},"3":{"foo":"bar"}}

because the keys in $arrayTwo are not sequential. I found this since I was using the filter() method. In my example I had an ArrayCollection with two entries. In cases where the 0 key element was returned from the filter it was fine. However if the 0 element was excluded based on the filter then I had an array where the first key was 1 -> this led to being serialized as an object.

Maybe the reason @schmittjoh isn't responding is because it's quite hard to fix. You could fix it on the doctrine side, inside Doctrine\Common\Collections\ArrayCollection changing the filter method to

return new static(array_values(array_filter($this->elements, $p)));

It could also be changed inside the serializeCollection method of the ArrayCollectionHandler, returning

return $visitor->visitArray($collection->getValues(), $type, $context);

But in either case I have doubts if this is a real solution, obviously in my case I don't care about the array keys since they're nothing more than the order of the entities in the database. But if I have an ArrayCollection where the keys are actually important then I'd be frustrated if they were reset.

I ran into this issue when I changed a sub-entity (ie. a relation to one of the entities in the array i was serializing) to use exclusion strategy all without remembering to expose any fields, so it became blank, and caused this problem. thanks @lwojciechowski

I ran into this when implementing serialization on an entity that extends \Serializable. Because it implements its own serialization method, the JMS serializer was apparently not being invoked, which in turn meant the max_depth property was not being recognized. The entity has children, which in turn had properties back to their parents that was attempting to be serialized. The serializer would pick up on the recursion and then set that parent property to NULL, which I believe triggers this bug.

Putting in the work-around provided by @alafon did the trick.

For the case where the null value is happening due to self-referencing (ie serializing an item that has a group that contains an ArrayCollection of items) I used this:

<?php

namespace AppBundle\Serializer\Handler;

use JMS\Serializer\VisitorInterface;
use Doctrine\Common\Collections\Collection;
use JMS\Serializer\Context;

class ArrayCollectionHandler extends \JMS\Serializer\Handler\ArrayCollectionHandler
{
    public function serializeCollection(VisitorInterface $visitor, Collection $collection, array $type, Context $context)
    {
        // We change the base type, and pass through possible parameters.
        $type['name'] = 'array';
        //don't include items that will produce null elements
        $dataArray = [];
        foreach($collection->toArray() as $element){
            if(!$context->isVisiting($element)){
                $dataArray[] = $element;
            }
        }

        return $visitor->visitArray($dataArray, $type, $context);
    }
}

and setting:

parameters:
    jms_serializer.array_collection_handler.class: AppBundle\Serializer\Handler\ArrayCollectionHandler

@mickadoo You're right ! with array_values for remapping the indexes

$serializer->serialize(array_values($toSerialize), 'json'));

it gives back a proper json array.

I have the same problem
"[{\"id\":50,\"text\":\"some text\"}, ... ]"

I resolved this problem like this

use FOS\RestBundle\Controller\Annotations\View as RestView;

class ApiController extends FOSRestController
{
/** 
 * ...
 * @RestView
 * @return View
 */
public function messageListAction() 
{
    ...
    $result = [];
    $serializer = $this->get('jms_serializer');

    /** @var UserMessages $message */
    foreach ($user->getMessages() as $message) {
           // I use serializer map (in /Resources/config/serializer/) for exclude some fields
           $result[] = (array) json_decode($serializer
               ->serialize($message, $format = 'json')
           ); 
    }

    return $this->view(["data" => $result], Codes::HTTP_OK);
}

result:
{"data": [{"id": 50, "text": "some text"}, {"id": 50, "text": "some text"}, ... ]}

@rud-felix felix perfect alternative, thanks.
$data = json_decode($serializer->serialize($object, 'json', SerializationContext::create()));

Throwing my experience into the ring for new-comers - @AlmogBaku 's solution worked immediately and like a charm. I tried other things, like making sure annotations existed for sub-items, rebuilding the Doctrine collection as a simple array, etc. to no avail.

Thanks, everyone, for the time spent providing suggestions and discussions. It's greatly appreciated.

For me it may have been a case of over configuration. I had both MaxDepth and Groups defined, but were not using them with the SerializationContext.

For this to work, I _did not_ serialize the JMS defined object before passing it to the View. I configured the SerializationContext to use both _Groups_ and _MaxDepthChecks_, and then created the View and passed Context into that.

use JMS\Serializer\SerializationContext;
use FOS\RestBundle\Util\Codes;
use FOS\RestBundle\View\View;

...

$context = SerializationContext::create();
$context->setGroups(array('track'));
$context->enableMaxDepthChecks();

$view = View::create($track, Codes::HTTP_CREATED);
$view->setSerializationContext($context);

return $this->handleView($view);

Worked for me. Work for anyone else?

ran into the same issue today. little bit surprised to see that an issue reported early 2014 is still not fixed today. @AlmogBaku 's solution works for me.

I think the serializer will check to see if the array is a numeric array or not and decide serializing it to JS Array or Object.

If there is a cell inside a array is null, it will first skip that cell, skipping it makes indexes discontinue and serializer will recognize it as associative array.

array:28 [
  0 => "High Fashion"
  1 => "Contemporary"
  2 => "Streetwear"
  3 => null
  4 => "Grooming"
]

Since $arr[3] is null, it skips. Once skipped, index discontinued. => Serializer consider it as a associative array.

Solution for this case:

First remove empty cell then get the array values and serialize
array_values(array_filter($resources))

You can add this lines in your config.yml :

jms_serializer:
    visitors:
        json:
            options: [JSON_FORCE_OBJECT]

@yannchoquet I think the original problem was that the serializer was returning an object when an array was expected.

I've slightly modified @AlmogBaku 's solution, cause it wasn't working for me.
My situation:
I have player (P1), who have a team (T), which have array of players. And P1 obviously didn't exist in T players, to avoid self-reference. But it caused array to hash transformation :(

Here is solution:

<?php

/**
 * @author  Almog Baku
 *          [email protected]
 *          http://www.AlmogBaku.com
 *
 * 25/06/15 15:28
 *
 * @author Peter Prokunin
 * mailto:[email protected]
 * 20.07.16
 */

namespace CommonWPBundle\Serializer;

use JMS\Serializer\Context;
use \JMS\Serializer\JsonSerializationVisitor as JsonSerializationVisitorBase;

class JsonSerializationVisitor extends JsonSerializationVisitorBase
{
// Almog's code
    public function getResult()
    {
        if($this->getRoot() instanceof \ArrayObject) {
            $this->setRoot((array) $this->getRoot());
        }
        return parent::getResult();
    }
// Peter's code
    public function visitArray($data, array $type, Context $context)
    {
        $array = parent::visitArray($data, $type, $context);
        $keys = array_keys($array);
        foreach ($keys as $key){
            if(!is_numeric($key)){
                return $array;
            }
        }
        return array_values($array);
    }

}

Thanks, @peterpro - I had implemented the previous solution from @AlmogBaku and it was working fine until I ran into one particular data set I needed serialized and it started coming back as named hash. I applied your visitArray method and it's looking good again. Thank you.

the same troubles.. is someone going to solve it? any PR or branch where we can help?

@Enase I don't think anything need to fix when you understand while it serialize your array to JS object, read some of the comments here to know more, or may be mine.

https://github.com/schmittjoh/JMSSerializerBundle/issues/373#issuecomment-218934026

preSerialize(){
    $this->setSmth(array_values($this->getSmth()));
}

Same problem, spent 1 hour on it.

Same here, I use @Groups, @Expose. With ManyToMany and ManyToOne.

I solved it by specifying @Expose and @Groups on at least one property of all entities concerned. Don't know where was actually the problem.

+1 !!!!!

Same here when specifying a serialization group

I fix it with @MaxDepth() and @type annotations (look at this) you need to be careful with the array collections fields and prevent the huge nested.

<?php

namespace Amoblando\AdminBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Category
 *
 * @JMS\ExclusionPolicy("all")
 * @ORM\Table(name="category")
 * @ORM\Entity(repositoryClass="Amoblando\AdminBundle\Repository\CategoryRepository")
 */
class Category
{
    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\Column(name="id", type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     *
     * @JMS\Type("integer")
     * @JMS\Expose()
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     * @Assert\NotBlank()
     *
     * @JMS\Type("string")
     * @JMS\Expose()
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="string", length=255, nullable=true)
     *
     * @JMS\Type("string")
     * @JMS\Expose()
     */
    private $description;

    /**
     * @var int
     *
     * @ORM\OneToMany(targetEntity="Item", mappedBy="category")
     */
    private $items;

    /**
     * @var int
     *
     * @ORM\OneToMany(targetEntity="Category", mappedBy="parent", cascade={"persist", "remove"})
     *
     * @JMS\Type("ArrayCollection<Amoblando\AdminBundle\Entity\Category>")
     * @JMS\Expose()
     * @JMS\MaxDepth(2)
     */
    private $children;

    /**
     * @var int
     *
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
     *
     * @JMS\Type("Amoblando\AdminBundle\Entity\Category")
     * @JMS\Expose()
     */
    private $parent;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->items = new \Doctrine\Common\Collections\ArrayCollection();
        $this->children = new \Doctrine\Common\Collections\ArrayCollection();
    }

// Getters and setters



Then I get:

[
  {
    "id": 1,
    "name": "Parent",
    "description": "Esta es la padre",
    "children": [
      {
        "id": 2,
        "name": "Child 1",
        "children": []
      },
      {
        "id": 3,
        "name": "Child 2",
        "children": []
      }
    ]
  },
  {
    "id": 2,
    "name": "Child 1",
    "children": [
      {
        "id": 4,
        "name": "Sub child 1",
        "children": []
      }
    ],
    "parent": {
      "id": 1,
      "name": "Parent",
      "description": "Esta es la padre",
      "children": [
        {
          "id": 3,
          "name": "Child 2",
          "children": []
        }
      ]
    }
  },
  {
    "id": 3,
    "name": "Child 2",
    "children": [
      {
        "id": 5,
        "name": "Sub child 2",
        "children": []
      }
    ],
    "parent": {
      "id": 1,
      "name": "Parent",
      "description": "Esta es la padre",
      "children": [
        {
          "id": 2,
          "name": "Child 1",
          "children": []
        }
      ]
    }
  },
  {
    "id": 4,
    "name": "Sub child 1",
    "children": [],
    "parent": {
      "id": 2,
      "name": "Child 1",
      "children": [],
      "parent": {
        "id": 1,
        "name": "Parent",
        "description": "Esta es la padre",
        "children": [
          {
            "id": 3,
            "name": "Child 2",
            "children": []
          }
        ]
      }
    }
  },
  {
    "id": 5,
    "name": "Sub child 2",
    "children": [],
    "parent": {
      "id": 3,
      "name": "Child 2",
      "children": [],
      "parent": {
        "id": 1,
        "name": "Parent",
        "description": "Esta es la padre",
        "children": [
          {
            "id": 2,
            "name": "Child 1",
            "children": []
          }
        ]
      }
    }
  }
]

I think I have figured this out...

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

        // 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() && $context->getDepth() <= 1) {
                $this->setRoot(clone $rs);
            }
        }

        return $rs;
    }

The change is the additional check && $context->getDepth() <= 1 before changing the root result type to ArrayObject.

The library traverses the graph deeper before inserting the first root item. So if the first root item contains another nested (at any level) entity with "no properties or null properties only" (taking expose and exclusion strategies into account), then it correctly returns an empty ArrayObject -- but it also sets the root object to that type.

Is it maybe a possibility that is the @Type is array, JMS does an implicit array_values before serializing, i mean you are making clear you want an array not an object?

Also the same could happen with the ArrayCollectionHandler, doing the implicit array_values.

javiern opened this issue on Mar 21, 2014

Soon is the anniversary, guys! 3 years!

For me the problem was when one of the children was the parent itself

Thanks @jedi4z , I had this problem, seaking fo a solution, and you gave it, . I have Arraycollection, and when typing it in the annotation

type: ArrayCollection

It works, and I get my nice arrays! You saved my life!

I had the same issue yesterday and solved it.

Test example

Given we have these objects.

class Product
{
    public $id;
    public $name;
    public $price;
}

class Price
{
    public $amount;
    public $source;
}

EXAMPLE

Original response

$product = new Product();
$product->id = 111;
$product->name = 'Tomato';
$price = new Price();
$price->amount = null;
$price->source = null;
$product->price = $price; // As you can see, we set all properties of $price as null

Product Object
(
    [id] => 111
    [name] => Tomato
    [price] => Price Object
        (
            [amount] => 
            [source] => 
        )
)

When I call $this->serializer->serialize($product), response is this format {"0": {...},"1": {...}, .....}.

Refactored response

$product = new Product();
$product->id = 111;
$product->name = 'Tomato';
$price = new Price();
$price->amount = null;
$price->source = null;
$product->price = !array_filter((array) $price) ? null : $price; <------ Solution

Product Object
(
    [id] => 111
    [name] => Tomato
    [price] => 
)

Now, when I call $this->serializer->serialize($product), response is this format [{...}, {...}, ....].

OUTCOME
If your response object has a property (price) that holds an object (Price) and if all the properties of that object (Price) are empty, JMSSerializer messes things up. At least in my case!

It still uses object notation if serialized object has virtual_properties/relations which are being transformed to empty object, e.g. because of serialization groups.

AppBundle\Entity\Product:
    exclusion_policy: ALL
    properties: # ... 
    virtual_properties:
        getVendor:
            expose: true
            serialized_name: vendor
---
AppBundle\Entity\Vendor:
    exclusion_policy: ALL

output

{
  "0":
   {
       "vendor": { }
    // ~~~
    }
// ~~~
}

https://github.com/schmittjoh/serializer/pull/728 makes consistent the fact that an empty array is an array and an empty object stays an object.

If you want to have null instead of {}, then this is not the case exposed in this issue ( more likely is what @BentCoder expressed in his comment.

I doubt that with the current available feature-set can be achieved.

You will need something as "convert empty to null" as per-property serialization option

Is this your the case?

There is a similar feature for the XML serialization process in @XmlList(skipWhenEmpty=true)

I don't want to have null, I want it to use array notation for top level collection, not an object one

So we are talking of [] instead of [{},{},{},{}] ?

No, [{}, {}, {}] instead of {"0": {}, "1": {}, ...}

then just put @Type("array<Classname>")

this has been added in schmittjoh/serializer#728 so you will have to use master-dev version on composer for jms/serializer

I've tested it already and it's not my case (it was wrong serialization group and I fixed it before comment).

Problem is that whole output turns into object instead of array if one of related objects is empty

did you follow this in case you are serializing a top-level array https://github.com/schmittjoh/serializer/blob/master/doc/cookbook/arrays.rst ?

output turns into object instead of array if one of related objects is empty

that should have been fixed with https://github.com/schmittjoh/serializer/pull/730

What a shame, :man_facepalming: I tested master branch of serializer bundle, not serializer itself. Yep, schmittjoh/serializer#730 does its job. Sorry.

@BentCoder your issue will be fixed with 1.7.0, the option @SkipWhenEmpty will be introduced with https://github.com/schmittjoh/serializer/pull/757 and is doing exactly what you need

Ok I figured it out.

Me too. That was easy.

Was this page helpful?
0 / 5 - 0 ratings