Estoy deserializando JSON a una clase de php (una entidad de Symfony) y funciona bien, pero tengo una propiedad anidada en mi JSON que no sé cómo ingresar a la clase de php.
Clase PHP:
class Vehicle { private $make: /** * @SerializedName("meta") */ private $colour; // with getters and setters... } // These 2 lines let us use the @SerializedName annotation $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $objectNormalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter); $encoders = [new JsonEncoder()]; $normalizers = [$objectNormalizer]; $serializer = new Serializer($normalizers, $encoders); $json = ' { "make": "VW Golf", "meta": { "colour": "red" } } '; $carJson = json_encode($json); $vehicle = $serializer->deserialize( $carJson, Vehicle::class, 'json', ); $vehicle->getMake(); // VW Golf $vehicle->getColour(); // ['meta' => ['colour' => 'red']] …pero esa última línea debería volver solo red .
Esperaba poder hacer algo como @SerializedName("meta.colour") o @SerializedName("[meta][colour]") o @SerializedName("meta[colour]") pero cada uno de ellos se resuelve en nulo.
Los documentos de Symfony sobre serialización no parecen manejar este caso (simple).
Esta publicación de Stackoverflow también se ocupa de las propiedades anidadas, pero en su ejemplo, la propiedad anidada debe deserializarse a otra clase de php, no asignada a la clase de php existente, por lo que eso no me ayuda.
¿Cómo puedo obtener $colour igual a red al deserializar el JSON?
Un normalizador personalizado hace el truco. El código sigue.
<?php namespace App\Serializer\Denormalizer; use App\Entity\Vehicle; use Symfony\Component\PropertyAccess\PropertyAccess; use Symfony\Component\PropertyAccess\PropertyAccessor; use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface; use Symfony\Component\Serializer\Normalizer\ContextAwareDenormalizerInterface; use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; class VehicleDenormalizer implements CacheableSupportsMethodInterface, ContextAwareDenormalizerInterface { protected ObjectNormalizer $normalizer; protected PropertyAccessor $propertyAccessor; public function __construct(ObjectNormalizer $normalizer) { $this->normalizer = $normalizer; $this->propertyAccessor = PropertyAccess::createPropertyAccessor(); } public function denormalize($data, $type, $format = null, array $context = []): Vehicle { /** @var Vehicle */ $vehicle = $this->normalizer->denormalize($data, $type, $format, $context); // It's possible to directly access the values, but that requires error // checking. This method will return a null if it doesn't exist. $colour = $this->propertyAccessor->getValue($data, '[meta][colour]'); $vehicle->setColour($colour); return $vehicle; } public function supportsDenormalization($data, $type, $format = null, array $context = []) { return Vehicle::class == $type; } public function hasCacheableSupportsMethod(): bool { return true; } } Para usar este desnormalizador, puede inyectar SerializerInterface o crear explícitamente el serializador (el código sigue).
$json = ' { "make": "VW Golf", "meta": { "colour": "red" } } '; $carJson = json_encode($json); // These 2 lines let us use the @SerializedName annotation $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $objectNormalizer = new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter); $normalizers = [new VehicleDenormalizer($objectNormalizer)]; $serializer = new Serializer($normalizers); /** @var Vehicle */ $vehicle = $serializer->denormalize( $carJson, Vehicle::class, ); $vehicle->getMake(); // VW Golf $vehicle->getColour(); // redEstoy creando explícitamente el serializador porque, por algún motivo, cuando los datos de origen eran una cadena y el destino era un número entero, el método de inyección no convierte automáticamente los tipos y aparece el siguiente error:
The type of the "VehicleNo" attribute for class "App\Entity\Vehicle" must be one of "int" ("string" given). (Este ejemplo de código no usa VehicleNo porque lo simplifiqué, pero lo incluí aquí para mostrar un ejemplo de un mensaje de error donde, digamos, Vehicle tiene una propiedad $vehicleNo de tipo int ).