I\'m trying to serialize a entity relation with JMS Serializer.
Here is the Entity:
class Ad
{
/**
* @Type(\"string\")
* @Groups({\"manag
I know this has already been answered but you could also use @Accessor. This probably (may, I can't be sure) work with deserialization too.
/**
* @Type("Acme\SearchBundle\Entity\Country")
* @Groups({"manage"})
*
* @var \Acme\SearchBundle\Entity\Country
*
* @Serializer\Accessor(getter="getCountryMinusId",setter="setCountryWithId")
*/
private $country;
/**
* @return string|null
*/
public function getCountryMinusId()
{
if (is_array($this->country) && isset($this->country['id'])) {
return $this->country['id'];
}
return null;
}
/**
* @param string $country
* @return $this
*/
public function setCountryWithId($country)
{
if (!is_array($this->country)) {
$this->country = array();
)
$this->country['id'] = $country;
return $this;
}
Yes, you could use @VirtualProperty
annotation:
/**
* @VirtualProperty
* @SerializedName("foo")
*/
public function bar()
{
return $this->country->getCode();
}
But be aware when it comes to deserialization:
@VirtualProperty This annotation can be defined on a method to indicate that the data returned by the method should appear like a property of the object.
> Note: This only works for serialization and is completely ignored during deserialization.
Hope this helps...
You can use @Type
and @Accessor
annotations:
/**
* @Type("string")
* @Accessor(getter="serializeType",setter="setType")
*/
protected $type;
public function serializeType()
{
return $this->type->getId();
}
Just to follow answered question:
If you don't like writing one method for each relation you have - just write your own handler. It's easy like
final class RelationsHandler
{
/**
* @var EntityManagerInterface
*/
private $manager;
/**
* RelationsHandler constructor.
*
* @param EntityManagerInterface $manager
*/
public function __construct(EntityManagerInterface $manager) { $this->manager = $manager; }
public function serializeRelation(JsonSerializationVisitor $visitor, $relation, array $type, Context $context)
{
if ($relation instanceof \Traversable) {
$relation = iterator_to_array($relation);
}
if (is_array($relation)) {
return array_map([$this, 'getSingleEntityRelation'], $relation);
}
return $this->getSingleEntityRelation($relation);
}
/**
* @param $relation
*
* @return array|mixed
*/
protected function getSingleEntityRelation($relation)
{
$metadata = $this->manager->getClassMetadata(get_class($relation));
$ids = $metadata->getIdentifierValues($relation);
if (!$metadata->isIdentifierComposite) {
$ids = array_shift($ids);
}
return $ids;
}
}
Register the Handler
jms_serializer.handler.relation:
class: MyBundle\RelationsHandler
arguments:
- "@doctrine.orm.entity_manager"
tags:
- { name: jms_serializer.handler, type: Relation, direction: serialization, format: json, method: serializeRelation}
- { name: jms_serializer.handler, type: Relation, direction: deserialization, format: json, method: deserializeRelation}
- { name: jms_serializer.handler, type: Relation<?>, direction: serialization, format: json, method: serializeRelation}
- { name: jms_serializer.handler, type: Relation<?>, direction: deserialization, format: json, method: deserializeRelation}
This allows you to replace virtual getter methods with `Type("Relation").
If you also want't to deserialize relation - you should tell each @Type("Relation")
the classname (@Type("Relation<FQCN>")
) which it should deserialize to or wrap the metadata driver with one which do it for you.
public function deserializeRelation(JsonDeserializationVisitor $visitor, $relation, array $type, Context $context)
{
$className = isset($type['params'][0]['name']) ? $type['params'][0]['name'] : null;
if (!class_exists($className, false)) {
throw new \InvalidArgumentException('Class name should be explicitly set for deserialization');
}
$metadata = $this->manager->getClassMetadata($className);
if (!is_array($relation)) {
return $this->manager->getReference($className, $relation);
}
$single = false;
if ($metadata->isIdentifierComposite) {
$single = true;
foreach ($metadata->getIdentifierFieldNames() as $idName) {
$single = $single && array_key_exists($idName, $relation);
}
}
if ($single) {
return $this->manager->getReference($className, $relation);
}
$objects = [];
foreach ($relation as $idSet) {
$objects[] = $this->manager->getReference($className, $idSet);
}
return $objects;
}
Alternatively, you can @inline
$country
which will serialize its properties into the parent relation. Then you can @Expose
the Country $id
and set its @SerializedName
to "country"
. Unlike Virtual properties, both serialization and deserialization will work for inline properties.
For this to work, you need to use the @ExclusionPolicy("All")
on each class and judiciously @Expose
the properties that you need in any of your groups. This is a more secure policy anyways.
/**
* @ExclusionPolicy("All")
*/
class Ad
{
//...
/**
* @Type("Acme\SearchBundle\Entity\Country")
*
* @Expose()
* @Inline()
* @Groups({"manage"})
*
* @var \Acme\SearchBundle\Entity\Country
*/
private $country;
//...
}
/**
* @ExclusionPolicy("All")
*/
class Country
{
//...
/**
* Get id
*
* @Expose()
* @Groups({"manage"})
* @SerializedName("country")
* @return string
*/
public function getId()
{
return $this->id;
}
}
The author wants to keep the property name, which doesn't apply to the accepted answer. As far as I understood, the answer by ScayTrase would keep the original property name but has another disadvantage according to the comments: The related object will be fetched if you are using Doctrine ORM @ManyToOne
, thus decreasing performance.
If you want to keep the original property name, you have to define the @VirtualProperty
at class level and @Exclude
the original property. Otherwise, the serialized property name will be derived from the getter method (countryId
in this case):
/**
* @Serializer\VirtualProperty(
* "country",
* exp="object.getCountryId()",
* options={@Serializer\SerializedName("country")}
* )
*/
class Ad {
/**
* @Serializer\Exclude
*/
private $country;
public function getCountryId() {
return $this->country === null ? null : $this->country->getId();
}
}