Search code examples
arraysjsonsymfonydeserializationjson-deserialization

Symfony - How to specify root to deserialize json array to object using JMS\Serializer


I receive json responses from a certain server to API requests in this format:

{
  "data": {
    #result here
  }
}

But I need to do deserialization of the result itself inside "data". I could not find in the documentation how to specify the json deserialization root

Example:

# Json Response
{
  "data": {
    "totalCount": 3,
    "version": "v1"
  }
}
<?php
# The object to deserialize into
namespace App;

use JMS\Serializer\Annotation as Serializer;

class ExampleResponse
{
    /**
     * @var int
     *
     * @Serializer\Type("integer")
     * @Serializer\SerializedName("totalCount")
     */
    protected $totalCount;

    /**
     * @var string
     *
     * @Serializer\Type("string")
     * @Serializer\SerializedName("version")
     */
    protected $version;
}
# Deserialization call
# I have to decode the json into an array and then json encode the internals of "data"
$this->serializer->deserialize(
    json_encode(
        json_decode(
            $json,
            true,
            512,
            JSON_THROW_ON_ERROR
        )['data'],
        JSON_THROW_ON_ERROR
    ),
    App\ExampleResponse::class,
    'json'
)

Is there a better solution?


Solution

  • It is possible but a little bit cumbersome.

    The easiest approach is to use json_decode and deserialize the object with the fromArray method:

    $response = json_decode($json, true);
    $this->serializer->fromArray($response['data'], App\ExampleResponse::class)
    

    The other solution is to use a subscriber to listen on the pre_deserialize event:

    class JsonDeserializationSubscriber implements EventSubscriberInterface
    {
        public static function getSubscribedEvents(): array
        {
            return [[
                'event' => 'serializer.pre_deserialize',
                'method' => 'onPreDeSerialize',
                'format' => 'json', // optional format
                'priority' => 0, // optional priority
            ]];
        }
    
    
        public function onPreDeSerialize(PreDeserializeEvent $event): void
        {
            $data = $event->getData();
    
            if (is_array($data) && array_key_exists('data', $data)) {
                $event->setData($data['data']);
            }
    
        }
    

    And you also need to configure the listener:

     $serializer = SerializerBuilder::create()->configureListeners(
       fn (EventDispatcher $e) => $e->addSubscriber(new JsonDeserializationSubscriber())
    )->build();
    $serializer->deserialize($json, App\ExampleResponse::class, 'json');