Рекурсивные объекты в JSON

Предположим, у меня есть следующий код:

public class A {
    B b;

    public A() {
        this.b = new B(this);
    }
}

public class B {
    A a;

    B(A a) {
        this.a = a;
    }

}

как вы можете видеть, эти объекты (A и B) ссылаются друг на друга, вызывая бесконечную рекурсию, если вы пытаетесь преобразовать объекты в код JSON: A имеет B, который как тот же A, который имеет тот же B, и так далее, и так далее.

но если я попытаюсь преобразовать эти объекты в JSON, например, используя Gson, a StackOverflowError поднимает, из-за рекурсии (которая, кстати, полностью логический.)

теперь есть ли способ в JSON объявить такую рекурсию? Если нет, то есть ли способ справиться с такой рекурсией в JSON?

или мне нужно вручную проверить рекурсию, удалить ее, преобразовать объект в JSON и повторно применить рекурсию при перестройке строки JSON в объекты Java?

3 ответов


Джексон имеет некоторые способы борьбы с циклическими или двунаправленными зависимостями, в частности, двумя.

если вы должны подвергнуть свою сущность внешнему миру, я рекомендую добавить @JsonIgnore на свойство, которое вызывает круговую ссылку. Это скажет Джексону не сериализовывать эту собственность. Однако это означает, что вы должны снова ссылаться на них при десериализации (и при сериализации вы должны добавить что-то в структуру данных JSON знайте, что ссылаться снова при десериализации).

другой способ, и я подозреваю, что это вам больше по душе, - использовать двунаправленные функции, предоставляемые Джексоном. Вы можете использовать @JsonManagedReference или @JsonBackReference. @JsonManagedReference является "прямой" частью свойства, и она будет сериализована нормально. @JsonBackReference является "задней" частью ссылки; он не будет сериализован, но будет реконструирован, когда тип " вперед десериализованный.

вы можете проверить примеры здесь.

ниже я покажу один из тех примеров, которые должны прояснить несколько вещей

public class NodeList {
    @JsonManagedReference
    public List<NodeForList> nodes; //this one gets serialized
}

public class NodeForList {
    public String name;

    @JsonBackReference 
    public NodeList parent; //this one wont get serialized, but will be reconstructed upon deserialization

    public NodeForList() { this(null); }
    public NodeForList(String n) { name = n; }
}

хотя JSON очень похож на буквальную нотацию объекта JavaScript, они не совсем то же самое. Это тот случай, когда разница имеет значение.

JavaScript имеет понятие ссылка в памяти: много переменных могут указывать на один и тот же объект. фактически, для некоторых типов данных это запечено непосредственно в самом типе: все переменные одного и того же значения, если это значение является одним из этих типов, указывают на одно и то же место. String и Boolean работайте так. Технически, так что Null и undefined, хотя вопрос спорный, так как для каждого из этих типов в любом случае существует только одно возможное значение.

даже если ссылки не запечены в типы, тем не менее, переменные все еще могут ссылаться на один и тот же объект в памяти.

JSON, напротив, не имеет понятия о ссылках. Все является новой и отличной ценностью, и никакие две вещи не могут указывать на одно и то же место. формат JSON Парсеры бумаги над этой разницей, как и JavaScript eval (хотя вы не должны использовать eval С JSON в любом случае); поскольку каждое значение считывается в память, оно преобразуется в переменную соответствующего типа, и если это тип, где все переменные одного и того же значения указывают на одно и то же место, то они это делают.

но это работает только тогда, когда ссылки запекаются в тип; JSON не имеет способа указать переменные, и поэтому любые точки в файле JSON, которые can см. различные места,do см. различные места. это означает, что вы не можете выполнять рекурсию в файлах JSON.

вместо этого, как вы говорите, ваш генератор JSON должен проверить рекурсию и удалить ее, заменив ее каким-то примечанием, что это значение должно относиться к чему-то другому. Как вы кодируете эти записки до вас; есть много способов сделать это, и некоторые из них будут работать лучше для конкретных потребностей вашего приложения, чем другие. В любом событие, однако вы это делаете, ваш парсер JSON на другом конце должен искать эти заметки и преобразовывать их обратно в рекурсивные ссылки.

альтернативой является использование другого формата, который имеет некоторый метод изначально кодирования ссылок.


Если вы просто хотите конвертировать в Json, а затем обратно, вы можете использовать Jackson с @JsonIdentityInfo Примечание. Вместо того, чтобы пытаться сохранить сами объекты, он будет хранить ссылки.

см. здесь для обработки идентификаторов объектов:http://wiki.fasterxml.com/JacksonFeatureObjectIdentity