3
votes

I am trying to serialize and deserialize a Doctrine object graph.

The structure is pretty complex, but this example sums up my problem:

There is a Company entity with a OneToMany relationship to Employee.
The Employee entity has a ManyToOne relationship with the Company.

This is serialized as follows:

{
    "company": {
        "name": "MegaCorp",
        "employees": [{
            "name": "John Doe",
            "company": null
        }]
    }
}

So it nulls the reference to the Employee's parent Company. For serialization this is ok. But now when I deserialize this json, I get a null Company in the Employee object. What I want (and expect) is to get a correct reference to the parent Company.

Is this possible using JMS serializer and if so, how can it be done?
If it is not possible, what could be a good workaround? Remember that it is a large graph, I don't want to do it manually.

3
Instead of null'ing the reference, use it's ID. Basically, you want a proxy instead of the real object. I don't know if JMS supports this out of the box.Gordon
That would be a possible solution, but indeed the question is: Does JMS support something like that?Dennis Haarbrink
I know it has @preSerialize and @postSerialize hooks. So it's doable.Gordon
Yeah, I am looking into that now, but I feel like this is not an uncommon use case and as such expect it to be supported by JMS by default.Dennis Haarbrink
Have you already tried MaxDepth()?kero

3 Answers

2
votes

Unfortunately, when deserializing, there is no way for the serializer to know whether the objects are identical or actually the same object. Even if you could nest them recursively.

But, you can get the desired result when combining the @Accessor annotation with some business logic. So going off of your example:

class Company {
    /**
     * @Accessor(setter="addEmployees")
     */
    private $employees;

    public function addEmployee(Employee $employee)
    {
        if (!$this->employees->contains($employee)) {
            $this->employees[] = $employee;
            $employee->setCompany($this);
        }
    }

    public function addEmployees($employees)
    {
        foreach ($employees as $employee) {
            $this->addEmployee($employee);
        }
    }
}

class Employee {
    /**
     * @Accessor(setter="setCompany")
     */
    private $company;

    public setCompany(Company $company = null)
    {
        $this->company = $company;

        if ($company) {
            $company->addEmployee($this);
        }
    }
}

I think this is a more natural approach than using @PostDeserialize as you probably already have some of these methods in your code. Also, it ensures the contract in both ways, so if you were to start from Employee you'd get the same result.

0
votes

What's about set the reference manually when you have deserialize the object? Something like this:

class Company { 

    ....

    @PostDeserialize
    public function setReferences()
    {
        foreach( $this->employees as $employee ){
            $employee->setCompany( $this );
        }
    }
}
0
votes

Is a good practice to keep as simple as possible your JSON entities! If you have to deal with this kind of issues, is the moment to rethink your data model!

In addition, did you think about use HATEOAS (Hypermedia as the Engine of Application State) is a principle that hypertext links should be used to create a better navigation through the API. Looks like this:

{
  "id": 711,
  "manufacturer": "bmw",
  "model": "X5",
  "seats": 5,
  "drivers": [
   {
    "id": "23",
    "name": "Stefan Jauker",
    "links": [
     {
     "rel": "self",
     "href": "/api/v1/drivers/23"
    }
   ]
  }
 ]
}