Как обрабатывать рекурсивные объекты с помощью JMS Serializer

Я пытаюсь сериализовать и десериализовать граф объектов Doctrine.

Структура довольно сложная, но этот пример суммирует мою проблему:

Существует сущность Company со связью OneToMany с Employee.
Сущность Employee имеет связь ManyToOne с Company.

Это сериализуется следующим образом:

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

Таким образом, это nullссылка на родителя Employee Company. Для сериализации это нормально. Но теперь, когда я десериализую этот json, я получаю null Company в объекте Employee. Чего я хочу (и ожидаю), так это получить правильную ссылку на родителя Company.

Возможно ли это с помощью сериализатора JMS, и если да, то как это сделать?
Если это невозможно, какой может быть хороший обходной путь? Помните, что это большой график, я не хочу делать это вручную.


person Dennis Haarbrink    schedule 15.04.2016    source источник
comment
Вместо обнуления ссылки используйте ее идентификатор. По сути, вам нужен прокси вместо реального объекта. Я не знаю, поддерживает ли JMS это из коробки.   -  person Gordon    schedule 15.04.2016
comment
Это было бы возможным решением, но на самом деле возникает вопрос: поддерживает ли JMS что-то подобное?   -  person Dennis Haarbrink    schedule 15.04.2016
comment
Я знаю, что у него есть крючки @preSerialize и @postSerialize. Так что это выполнимо.   -  person Gordon    schedule 15.04.2016
comment
Да, я изучаю это сейчас, но я чувствую, что это не редкий вариант использования, и поэтому ожидаю, что он будет поддерживаться JMS по умолчанию.   -  person Dennis Haarbrink    schedule 15.04.2016
comment
Вы уже пробовали MaxDepth()?   -  person kero    schedule 15.04.2016
comment
@kingkero: Как это мне поможет?   -  person Dennis Haarbrink    schedule 15.04.2016


Ответы (3)


К сожалению, при десериализации сериализатор не может узнать, идентичны ли объекты или это действительно один и тот же объект. Даже если бы вы могли вкладывать их рекурсивно.

Но вы можете получить желаемый результат, объединив аннотацию @Accessor с некоторой бизнес-логикой. Итак, выходя из вашего примера:

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);
        }
    }
}

Я думаю, что это более естественный подход, чем использование @PostDeserialize, поскольку некоторые из этих методов, вероятно, уже есть в вашем коде. Кроме того, это гарантирует контракт в обоих направлениях, поэтому, если вы начнете с Employee, вы получите тот же результат.

person Kuba Birecki    schedule 15.04.2016
comment
На самом деле это немного лучшая реализация, чем мое текущее решение, включающее хук PostDeserialize в Company. Хотя основная идея та же. - person Dennis Haarbrink; 15.04.2016
comment
Да. Боюсь, от этого никуда не деться... Как я уже сказал, у сериализатора нет возможности подобрать какие-либо отношения, кроме тех, которые навязываются самой структуре json. - person Kuba Birecki; 15.04.2016
comment
Я думаю, вы правы. Я реализовал это сейчас, используя метод @Accessor, работает как шарм. Спасибо! - person Dennis Haarbrink; 15.04.2016

Как насчет установки ссылки вручную, когда вы десериализуете объект? Что-то вроде этого:

class Company { 

    ....

    @PostDeserialize
    public function setReferences()
    {
        foreach( $this->employees as $employee ){
            $employee->setCompany( $this );
        }
    }
}
person Rafa0809    schedule 16.07.2016

Хорошей практикой является максимальное упрощение ваших объектов JSON! Если вам приходится сталкиваться с такими проблемами, самое время переосмыслить свою модель данных!

Кроме того, задумывались ли вы об использовании HATEOAS (Hypermedia as the Engine of Application State) — принципа, согласно которому следует использовать гипертекстовые ссылки для создания лучшей навигации по API. Выглядит так:

{
  "id": 711,
  "manufacturer": "bmw",
  "model": "X5",
  "seats": 5,
  "drivers": [
   {
    "id": "23",
    "name": "Stefan Jauker",
    "links": [
     {
     "rel": "self",
     "href": "/api/v1/drivers/23"
    }
   ]
  }
 ]
}
person Rafa0809    schedule 20.01.2017
comment
К сожалению, это устаревшее приложение, с которым мне пришлось взаимодействовать. В противном случае, я не мог согласиться больше! - person Dennis Haarbrink; 23.01.2017
comment
Понятно... Итак, клиент, который читает этот JSON, должен знать об этих циклических зависимостях и устанавливать ссылки на декодирование JSON. - person Rafa0809; 23.01.2017