15

Is is possible in Symfony Serializer to deserialize an array of objects in a property? I have a Boss class with the $Npc = [] property that needs to hold a array of Npc objects. I did see some examples in the documentation, but they do not state this feature. I have a json string with an array of NPC's For example:

class Boss {

    private $Npc = [];    

    /**
    * @return Npc[]
    */
    public function getNpcs(): array
    {
        return $this->npcs;
    }
}

I am using php7.1 and symfony/serializer version ^3.3.

Edit: I already tried PhpDocExtractor, but it would not let me install it. :(

Edit: This is a possible JSON value:

{
    "bossname": "Epic boss!",
    "npcs": [{
        "id": 24723,
        "name": "Selin Fireheart",
        "urlSlug": "selin-fireheart",
        "creatureDisplayId": 22642
    }]
}
Mike Rovers
  • 311
  • 1
  • 2
  • 9

4 Answers4

16

I found a way to do this :). I installed the Symfony PropertyAccess package through Composer. With this package, you can add adders, removers and hassers. This way Symfony Serializer will automaticly fill the array with the correct objects. Example:

private $npcs = [];

public function addNpc(Npc $npc): void
{
    $this->npcs[] = $npc;
}

public function hasNpcs(): bool
{
    return count($this->npcs) > 0
}

etc.

This way you can use the ObjectNormalizer with:

$normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());

Edit: At least since v3.4 you have to create a remover method as well. Otherwise it just won't work (no error or warning).

Michael Käfer
  • 1,597
  • 2
  • 19
  • 37
Mike Rovers
  • 311
  • 1
  • 2
  • 9
  • 4
    At least for v3.4, you **have** to set also a remover for this to work (I lost a couple of hours debuggin this). Make sure to check your plurials. – Brewal Nov 05 '18 at 14:59
  • 1
    I also needed to add setter method for the property `setNpcs()` on v5. As a note, even if you define `addNpc` and `hasNpcs` methods, they are not actually called. `Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor::getMutatorMethod()` will check if those methods exist and `Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor::extractFromReflectionType()` finds argument class name defined for you `addNpc()` method – Nuryagdy Mustapayev Nov 07 '22 at 15:04
8

I have struggled with this many hours without getting a result. Every time i added an adder function, the objectnormalizer wanted to invoke this function but got an error something like "The field xyz should be of type xyz[], array given".

This is cause i forgot to add the ArrayDenormalizer to the normalizer pool of the serializer. After adding this, everything worked fine.

Hope this is helpful for somebody.

Hett
  • 3,484
  • 2
  • 34
  • 51
Jim Panse
  • 2,220
  • 12
  • 34
6

The following (full) example is working for me with Symfony 5.3 components. It combines some of the above and adds some more necessary parts:

The Serializer needs to include ArrayDenormalizer as well as ObjectNormalizer. The ObjectNormalizer needs proper configuration. The full creation can look like this:

return new Serializer(
    [
        new ArrayDenormalizer(),
        new DateTimeNormalizer(),
        new ObjectNormalizer(
            null,
            null,
            null,
            new ReflectionExtractor()
        ),
    ],
    [
        new JsonEncoder(),
    ]
);

It seems like order is important regarding the normalizer / denormalizer, probably due to priority. Placing DateTimeNormalizer after ObjectNormalizer won't work.

The target class needs to provide three methods in order to allow mapping of an array of other classes:

class Place
{
    /**
     * @var OpeningHour[]
     */
    protected $openingHours = [];

    /**
     * @return OpeningHour[]
     */
    public function getOpeningHoursSpecification(): array
    {
        return $this->openingHours;
    }

    public function addOpeningHoursSpecification(OpeningHour $openingHour): void
    {
        $this->openingHours[] = $openingHour;
    }

    public function removeOpeningHoursSpecification(OpeningHour $openingHour): void
    {
    }
}

All three methods need to exist. The first in order to allow Serializer to fetch existing values and allow comparison. The other two need to exist in order to adapt existing value to match expected one by adding missing and removing no longer existing entities.

The above does not provide implementation for removal as this is taken from a one way project.

Daniel
  • 940
  • 6
  • 12
-3

Yes, you can deserialize the array but you need to provide on the second parameter the object as well as the information that it is in fact an array. You can do this like this:

use Symfony\Component\Serializer\Serializer;

class Boss {

    private $Npc = [];    

    /**
    * @return Npc[]
    */
    public function getNpcs(): array
    {
        return $serializer->deserialize($this->npcs, 'Acme\Npc[]', 'json');

    }
}

You can find more information about this in the documentation on handling arrays

db306
  • 934
  • 11
  • 22
  • That deserialized the json to a array of objects. But I want to deserialize to the Npc property (see the first post). – Mike Rovers Nov 13 '17 at 21:29
  • Your question was "Is is possible in Symfony Serializer to deserialize an array of objects in a property?" So you want it the other way round ? What do you want: your object to hold an array of objects or a serialized json representing an array of objects ? – db306 Nov 13 '17 at 21:34
  • See my second edit: I want to deserialize those npc objects from the JSON to the npc property of the Boss class as a Npc class. – Mike Rovers Nov 13 '17 at 21:40
  • It currently deserialized as a normal array. But I want to deserialize it as a Npc array – Mike Rovers Nov 13 '17 at 21:44
  • In order to be a Npc array you need an Npc Class – db306 Nov 13 '17 at 21:45
  • Yes, I know. I have a Npc class. But how do I let the serializer deserialize that into the array as Npc objects? – Mike Rovers Nov 13 '17 at 21:45
  • just change 'YourNameSpace\Boss[]' for 'YourNameSpace\Npc[]' Then you will be telling serializer, hey it's a Npc class ! – db306 Nov 13 '17 at 21:47
  • Why? That does not make sense. I never used Boss[]. It is a single boss with a Npc property that needs to filled with Npc Objects. But is deserializes as a normal array instead of npd objects. – Mike Rovers Nov 13 '17 at 21:48
  • You have a JSON serialized array of of Npc objects. You want to deserialize that array into Npc objects, which is logic because its an array of Npc objects. Right? Then you need to tell Serializer that it's an array of Npc objects. You do this by telling him : `$serializer->deserialize($this->npcs, 'Acme\Npc[]', 'json');` – db306 Nov 13 '17 at 21:51
  • Yea, That is right, but I want to deserialize a Boss object. How do I automatically let the serializer also deserialize the npc objects? If I use that, it would not make a boss object. – Mike Rovers Nov 13 '17 at 21:52
  • Then just deserialize your boss object: `$boss = $serializer->deserialize($your_json, Boss::class, 'json');` then when you do this `$boss->getNpcs()` it will deserialize the Npcs array of Npc objects – db306 Nov 13 '17 at 22:03
  • I assume you have the right structure than when you serialized this boss object otherwise you are trying to hydrate an object that doesn't fit the proper structure and thus will just not work the way you intend to – db306 Nov 13 '17 at 22:08
  • Thanks for the patience, I think I am way overthinking it. I am now deserializer the npc objects separately and setting them in the boss object. – Mike Rovers Nov 14 '17 at 09:52
  • @mikeRovers I think that should be the way to go :) In any case, in terms of your question and how its formulated that was the answer to the original question :) Thanks for ur rating – db306 Nov 14 '17 at 10:28