Skip to main content

@ParentProperty Annotation

The @ParentProperty annotation is used to identify a setter method or field for adding a parent reference to a child object.

This annotation is used by parsers to automatically establish parent-child relationships when parsing nested objects. When a child object is created within a parent object (e.g., in a list or as a property), the parser automatically sets the parent reference on the child.

Requirements​

  • Must be an instance method or field (not static)
  • For methods: Must accept exactly one parameter (the parent object type)
  • For fields: Can be any type (typically the parent object type)
  • The method or field does not need to be public (will be made accessible automatically)

Usage​

Can be applied to:

  • Bean setter methods or fields
  • @Rest-annotated classes and @RestOp-annotated methods when using the on() parameter

Example​

Using a Field​

public class AddressBook {
public List<Person> people;
}

public class Person {
@ParentProperty
public AddressBook addressBook; // Automatically set to containing AddressBook

public String name;
public char sex;
}

Using a Setter Method​

public class AddressBook {
public List<Person> people;
}

public class Person {
private AddressBook parent;

@ParentProperty
protected void setParent(AddressBook parent) {
this.parent = parent;
}

public String name;
public char sex;
}

When It's Called​

  • During parsing when a child object is created within a parent object
  • The parser automatically calls the setter or sets the field with a reference to the parent object
  • This allows child objects to navigate back to their parent if needed
  • Useful for bidirectional relationships where child objects need access to their parent context

Enclosing Bean Through Collections and Maps​

In the example above, each Person is not a direct property of AddressBook — it's an element of the List<Person> people field. When resolving a @ParentProperty member, the parser skips over any intervening List, Set, array, and Map containers, regardless of nesting depth, and injects the nearest enclosing bean instead. So Person.addressBook is set to the AddressBook, not to the List that directly contains it.

A bean that has no enclosing bean — for example, one parsed at the document root, or one that's an element of a top-level collection passed directly to the parser — has nothing to inject, so its @ParentProperty member is simply left null.

Cyclic Graphs and Serialization​

When a @ParentProperty back-reference is also a normally-visible bean property (e.g. a public field or getter, as in the field example above), it forms a parent/child cycle. Unlike Jackson's @JsonBackReference, Juneau does not auto-omit the @ParentProperty member on the write (serialize) side — the annotation is a parse-time convenience only. This is intentional.

The serialization behavior of such a cyclic graph therefore depends on the serializer's recursion settings (see Non-Tree Models and Recursion Detection):

  • Default configuration (detectRecursions=false, ignoreRecursions=false): the traversal is silently truncated at maxDepth (default 100), producing finite but semantically-incomplete output. No exception is thrown — maxDepth is a size guard, not a cycle detector.
  • detectRecursions(): fail-fast on the cycle with a SerializeException ("Recursion occurred, stack=...").
  • ignoreRecursions(): omit the repeated node so the output round-trips cleanly — parsing re-injects the parent via @ParentProperty.

Using the AddressBook/Person classes from the field example above, ignoreRecursions() round-trips cleanly end to end:

AddressBook book = new AddressBook();
book.people = new ArrayList<>();

Person person = new Person();
person.name = "John Smith";
person.sex = 'M';
book.people.add(person);

// Clone an existing serializer and enable ignoring of recursions.
WriterSerializer serializer = Json5Serializer
.DEFAULT_READABLE
.copy()
.ignoreRecursions()
.build();

// Serialize to JSON. The Person.addressBook back-reference is omitted to break the cycle.
String json = serializer.write(book);

// Parse it back. The parser re-injects the enclosing AddressBook into each Person.
AddressBook parsedBook = JsonParser.DEFAULT.read(json, AddressBook.class);
Person parsedPerson = parsedBook.people.get(0);

assert parsedPerson.addressBook == parsedBook; // Restored, even though person is nested inside a List