Skip to content

[Serializer] Normalizers can't handle \stdClass #33047

@pbowyer

Description

@pbowyer

Symfony version(s) affected: 4.3.3

Description
When an object contains properties of \stdClass, serialization fails.

[Possible secondary issue: ObjectNormalizer fails to read data from this object]

How to reproduce
My first attempt with the standard Symfony configuration of the serializer (using ObjectNormalizer is missing most properties.

Code

<?php
// Setup instructions:
// composer require gocardless/gocardless-pro
// composer require symfony/serializer-pack
use GoCardlessPro\Resources\Event;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;

require_once 'vendor/autoload.php';

// This - I hope - mimics the Symfony framework default Serializer configuration
$encoders = [new XmlEncoder(), new JsonEncoder()];
$normalizers = [new ObjectNormalizer()];
$serializer = new Serializer($normalizers, $encoders);
// End configuring the serializer

// Begin configuring the object to serialize
$json = '{"id":"EV0000000000000","created_at":"2019-08-06T20:09:15.731Z","action":"active","resource_type":"mandates","details":{"origin":"gocardless","cause":"mandate_activated","description":"The time window after submission for the banks to refuse a mandate has ended without any errors being received, so this mandate is now active."},"links":{"mandate":"MD00000000000"},"metadata":[],"model_name":"Event"}';
$event = new Event(json_decode($json));

echo "Expected\n";
// Get rid of the line breaks:
echo json_encode(json_decode($json));

echo "\n\nActual\n";
echo $serializer->serialize($event, 'json');

Output

Expected
{"id":"EV0000000000000","created_at":"2019-08-06T20:09:15.731Z","action":"active","resource_type":"mandates","details":{"origin":"gocardless","cause":"mandate_activated","description":"The time window after submission for the banks to refuse a mandate has ended without any errors being received, so this mandate is now active."},"links":{"mandate":"MD00000000000"},"metadata":[],"model_name":"Event"}

Actual
{"api_response":null}

I assume that is expected behaviour (though don't understand why) so tried a different normalizer: PropertyNormalizer. This worked if the structure was all arrays (see comment in code) but not with objects:

Code

<?php
// Setup instructions:
// composer require gocardless/gocardless-pro
// composer require symfony/serializer-pack
use GoCardlessPro\Resources\Event;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
use Symfony\Component\Serializer\Serializer;

require_once 'vendor/autoload.php';

// Custom Serializer configuration
$encoders = [new XmlEncoder(), new JsonEncoder()];
$normalizers = [new PropertyNormalizer()];
$serializer = new Serializer($normalizers, $encoders);
// End configuring the serializer

$json = '{"id":"EV0000000000000","created_at":"2019-08-06T20:09:15.731Z","action":"active","resource_type":"mandates","details":{"origin":"gocardless","cause":"mandate_activated","description":"The time window after submission for the banks to refuse a mandate has ended without any errors being received, so this mandate is now active."},"links":{"mandate":"MD00000000000"},"metadata":[],"model_name":"Event"}';
// All problems can be fixed if I could change this to $event = new Event(json_decode($json, true));
// But I can't - the library that creates these objects doesn't
// So I need the serializer to handle stdClass objects
$event = new Event(json_decode($json));

echo "Expected\n";
// Get rid of the line breaks:
echo json_encode(json_decode($json));

echo "\n\nActual\n";
// IMO this should work, but it throws an exception:
// PHP Fatal error:  Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: Could not normalize object of type stdClass, no supporting normalizer found.
#echo $serializer->serialize($event, 'json');

// So I try doing my own conversions:
// It still throws the same exception
$callback = function ($innerObject, $outerObject, string $attributeName, string $format = null, array $context = []) {
    return $innerObject instanceof \stdClass ? (array)$innerObject : $innerObject;
};
echo $serializer->serialize($event, 'json', [
    'callbacks' => [
        // This assumes I know all the object properties that are stdClass - which I won't
        'details' => $callback,
        'links' => $callback,
        'metadata' => $callback,
        'data' => $callback,
    ]
]);

Output

Expected
{"id":"EV0000000000000","created_at":"2019-08-06T20:09:15.731Z","action":"active","resource_type":"mandates","details":{"origin":"gocardless","cause":"mandate_activated","description":"The time window after submission for the banks to refuse a mandate has ended without any errors being received, so this mandate is now active."},"links":{"mandate":"MD00000000000"},"metadata":[],"model_name":"Event"}

Actual
PHP Fatal error:  Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: Could not normalize object of type stdClass, no supporting normalizer found. in Z:\path\to\vendor\symfony\serializer\Serializer.php:173
Stack trace:
#0 Z:\path\to\vendor\symfony\serializer\Serializer.php(162): Symfony\Component\Serializer\Serializer->normalize(Object(stdClass), 'json', Array)
#1 Z:\path\to\vendor\symfony\serializer\Normalizer\AbstractObjectNormalizer.php(206): Symfony\Component\Serializer\Serializer->normalize(Array, 'json', Array)
#2 Z:\path\to\vendor\symfony\serializer\Serializer.php(152): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->normalize(Object(GoCardlessPro\Resources\Event), 'json', Array)
#3 Z:\path\to\vendor\symfony\serializer\Serializer.php(125): Symfony\Component\Serializer\Serializer->normalize in Z:\path\to\vendor\symfony\serializer\Serializer.php on line 173

Fatal error: Uncaught Symfony\Component\Serializer\Exception\NotNormalizableValueException: Could not normalize object of type stdClass, no supporting normalizer found. in Z:\path\to\vendor\symfony\serializer\Serializer.php on line 173

Symfony\Component\Serializer\Exception\NotNormalizableValueException: Could not normalize object of type stdClass, no supporting normalizer found. in Z:\path\to\vendor\symfony\serializer\Serializer.php on line 173

Call Stack:
    0.0002     405288   1. {main}() Z:\path\to\index.php:0
    0.0130    1260760   2. Symfony\Component\Serializer\Serializer->serialize() Z:\path\to\index.php:45
    0.0131    1261136   3. Symfony\Component\Serializer\Serializer->normalize() Z:\path\to\vendor\symfony\serializer\Serializer.php:125
    0.0131    1262264   4. Symfony\Component\Serializer\Normalizer\PropertyNormalizer->normalize() Z:\path\to\vendor\symfony\serializer\Serializer.php:152
    0.0138    1267416   5. Symfony\Component\Serializer\Serializer->normalize() Z:\path\to\vendor\symfony\serializer\Normalizer\AbstractObjectNormalizer.php:206
    0.0138    1267792   6. Symfony\Component\Serializer\Serializer->normalize() Z:\path\to\vendor\symfony\serializer\Serializer.php:162

Possible Solution
Write ones own normalizer for stdClass? As it's a PHP built-in class, I'm not clear this is intended; why can't the standard object normalizers cope with it?

Additional context

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions