Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

serializing a json array using {} instead of [] #373

Closed
javiern opened this issue Mar 20, 2014 · 109 comments
Closed

serializing a json array using {} instead of [] #373

javiern opened this issue Mar 20, 2014 · 109 comments

Comments

@javiern
Copy link

@javiern javiern commented Mar 20, 2014

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

@PaddyLock
Copy link

@PaddyLock PaddyLock commented Apr 11, 2014

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...

@davidwdan
Copy link

@davidwdan davidwdan commented Apr 11, 2014

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);
...
@lwojciechowski
Copy link

@lwojciechowski lwojciechowski commented Apr 28, 2014

I have the same problem.

@manseuk
Copy link

@manseuk manseuk commented Apr 30, 2014

Same problem here

@patrickmatsumura
Copy link

@patrickmatsumura patrickmatsumura commented Jun 9, 2014

Me too

@lwojciechowski
Copy link

@lwojciechowski lwojciechowski commented Jun 11, 2014

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.

@Sander-Toonen
Copy link

@Sander-Toonen Sander-Toonen commented Jun 12, 2014

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

@cclose
Copy link

@cclose cclose commented Jul 10, 2014

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.

@cclose
Copy link

@cclose cclose commented Jul 10, 2014

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.

@lwojciechowski
Copy link

@lwojciechowski lwojciechowski commented Jul 22, 2014

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?

@lwojciechowski
Copy link

@lwojciechowski lwojciechowski commented Aug 19, 2014

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.

@erichjsonfosse
Copy link

@erichjsonfosse erichjsonfosse commented Dec 26, 2014

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?

@andreaslarssen
Copy link

@andreaslarssen andreaslarssen commented Dec 26, 2014

+1

1 similar comment
@b-durand
Copy link

@b-durand b-durand commented Dec 26, 2014

👍

@ggregoire
Copy link

@ggregoire ggregoire commented Jan 15, 2015

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());
    }
@theofidry
Copy link

@theofidry theofidry commented Jan 24, 2015

+1

@bassrock
Copy link

@bassrock bassrock commented Feb 4, 2015

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

@bassrock
Copy link

@bassrock bassrock commented Feb 4, 2015

I am seeing this issue on a OneToMany situation explained here: schmittjoh/serializer#387

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

@dotstormz
Copy link

@dotstormz dotstormz commented Feb 25, 2015

+1

1 similar comment
@ch3ric
Copy link

@ch3ric ch3ric commented Feb 26, 2015

+1

@joshribakoff
Copy link

@joshribakoff joshribakoff commented Feb 28, 2015

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

@pasxel
Copy link

@pasxel pasxel commented Mar 2, 2015

+1

@coatezy
Copy link

@coatezy coatezy commented Mar 11, 2015

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.

@soullivaneuh
Copy link
Contributor

@soullivaneuh soullivaneuh commented Mar 11, 2015

👍

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
@soullivaneuh
Copy link
Contributor

@soullivaneuh soullivaneuh commented Mar 11, 2015

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
@soullivaneuh
Copy link
Contributor

@soullivaneuh soullivaneuh commented Mar 12, 2015

@coatezy I'm trying to apply your solution.

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

Thanks.

@soullivaneuh
Copy link
Contributor

@soullivaneuh soullivaneuh commented Mar 12, 2015

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().

@stof
Copy link
Contributor

@stof stof commented Mar 12, 2015

@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.

@jbenezech
Copy link

@jbenezech jbenezech commented Oct 15, 2016

Same here when specifying a serialization group

@jedi4z
Copy link

@jedi4z jedi4z commented Oct 31, 2016

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": []
          }
        ]
      }
    }
  }
]
@pestaa
Copy link

@pestaa pestaa commented Dec 19, 2016

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.

@rdohms
Copy link

@rdohms rdohms commented Jan 23, 2017

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.

@desmax
Copy link

@desmax desmax commented Mar 3, 2017

javiern opened this issue on Mar 21, 2014

Soon is the anniversary, guys! 3 years!

@Florin-Birgu
Copy link

@Florin-Birgu Florin-Birgu commented Apr 9, 2017

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

@dTrimon
Copy link

@dTrimon dTrimon commented Apr 15, 2017

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<BddBundle\Entity\object

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

@BentCoder
Copy link

@BentCoder BentCoder commented Apr 21, 2017

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!

@unti1x
Copy link

@unti1x unti1x commented Apr 25, 2017

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": { }
    // ~~~
    }
// ~~~
}
@goetas
Copy link
Collaborator

@goetas goetas commented Apr 25, 2017

schmittjoh/serializer#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)

@unti1x
Copy link

@unti1x unti1x commented Apr 25, 2017

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

@goetas
Copy link
Collaborator

@goetas goetas commented Apr 25, 2017

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

@unti1x
Copy link

@unti1x unti1x commented Apr 25, 2017

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

@goetas
Copy link
Collaborator

@goetas goetas commented Apr 25, 2017

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

@goetas
Copy link
Collaborator

@goetas goetas commented Apr 25, 2017

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

@unti1x
Copy link

@unti1x unti1x commented Apr 25, 2017

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

@unti1x
Copy link

@unti1x unti1x commented Apr 25, 2017

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

@goetas
Copy link
Collaborator

@goetas goetas commented Apr 25, 2017

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

@goetas
Copy link
Collaborator

@goetas goetas commented Apr 25, 2017

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

that should have been fixed with schmittjoh/serializer#730

@unti1x
Copy link

@unti1x unti1x commented Apr 25, 2017

What a shame, 🤦‍♂️ I tested master branch of serializer bundle, not serializer itself. Yep, schmittjoh/serializer#730 does its job. Sorry.

@goetas
Copy link
Collaborator

@goetas goetas commented Apr 27, 2017

@BentCoder your issue will be fixed with 1.7.0, the option @SkipWhenEmpty will be introduced with schmittjoh/serializer#757 and is doing exactly what you need

@serk1284
Copy link

@serk1284 serk1284 commented Sep 12, 2017

Ok I figured it out.

@boskee
Copy link

@boskee boskee commented Sep 12, 2017

Me too. That was easy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.