This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/docs by this push:
     new 6737f3fcc6 Document @ParentProperty collection/map enclosing-bean 
behavior change (TODO-291)
6737f3fcc6 is described below

commit 6737f3fcc65b247c4d18d05692a1319811b81947
Author: James Bognar <[email protected]>
AuthorDate: Fri Jul 24 09:08:17 2026 -0400

    Document @ParentProperty collection/map enclosing-bean behavior change 
(TODO-291)
    
    - 10.0.0 release note: breaking-change entry + migration guidance
    - 03.03.08.ParentPropertyAnnotation.md: new "Enclosing Bean Through 
Collections
      and Maps" section + round-trip example
    
    Co-authored-by: Cursor <[email protected]>
---
 pages/release-notes/10.0.0.md                     |  1 +
 pages/topics/03.03.08.ParentPropertyAnnotation.md | 34 +++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 54b46b20f6..d30205b9a2 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -780,6 +780,7 @@ _TBD — to be filled in as development continues._
 - **`Schema.exclusiveMaximum()` / `Schema.exclusiveMinimum()` boolean 
annotation members removed (TODO-238).** These `@Deprecated(forRemoval)` legacy 
Swagger 2.0/OpenAPI 3.0/JSON Schema Draft 04 boolean forms on 
`org.apache.juneau.commons.Schema` are gone. **Migration:** use the existing 
short-form boolean aliases `emax()` / `emin()` instead (unchanged); for JSON 
Schema Draft 2020-12 numeric semantics, `exclusiveMaximumValue()` / 
`exclusiveMinimumValue()` (also unchanged) remain availabl [...]
 - **`HttpPartSchema.T_OBJECT_CSV` / `T_OBJECT_PIPES` / `T_OBJECT_SSV` / 
`T_OBJECT_TSV` constants removed, along with their sole-purpose builder methods 
`tObjectCsv()` / `tObjectPipes()` / `tObjectSsv()` / `tObjectTsv()` 
(TODO-238).** A `collectionFormat` on an `object`-type part is not a valid 
OpenAPI 2.0 concept (`collectionFormat` is defined only for `array` types), and 
these presets had zero usage in the framework. The array-type equivalents 
(`T_ARRAY_CSV`, `T_ARRAY_PIPES`, `T_ARRAY_S [...]
 - **`XmlSerializer.DEFAULT_XS_NAMESPACE` removed (TODO-238).** This unused 
`protected` namespace constant (`xs` → `http://www.w3.org/2001/XMLSchema`) had 
no consumers anywhere in the framework. `DEFAULT_JUNEAU_NAMESPACE` is 
unaffected. **Migration:** none expected for normal usage; any external 
subclass that referenced the constant should declare its own namespace via 
`Namespace.of("xs", "http://www.w3.org/2001/XMLSchema";)`.
+- **`@ParentProperty` through collections/maps now injects the enclosing bean 
(behavioral change) (TODO-291).** Previously, when a bean annotated with 
`@ParentProperty` was nested inside a `List`, `Set`, array, or `Map` — rather 
than being a direct bean property — the parser injected the 
immediately-containing container (e.g. the `JsonList`/`JsonMap`) into the 
`@ParentProperty` member. As of 10.0.0, the parser instead injects the nearest 
enclosing *bean*, skipping all intermediate contai [...]
 
 _Other entries TBD — to be filled in before release. See also the major 
version bump note above._
 
diff --git a/pages/topics/03.03.08.ParentPropertyAnnotation.md 
b/pages/topics/03.03.08.ParentPropertyAnnotation.md
index 92ffb3a61b..170a72dca3 100644
--- a/pages/topics/03.03.08.ParentPropertyAnnotation.md
+++ b/pages/topics/03.03.08.ParentPropertyAnnotation.md
@@ -65,6 +65,12 @@ public class Person {
 - 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.
@@ -74,3 +80,31 @@ The serialization behavior of such a cyclic graph therefore 
depends on the seria
 - **Default configuration** (`detectRecursions=false`, 
`ignoreRecursions=false`): the traversal is silently truncated at <a 
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#maxDepth(int)"
 target="_blank">maxDepth</a> (default `100`), producing finite but 
semantically-incomplete output. No exception is thrown — `maxDepth` is a size 
guard, not a cycle detector.
 - <a 
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#detectRecursions()"
 target="_blank">detectRecursions()</a>: fail-fast on the cycle with a 
`SerializeException` ("Recursion occurred, stack=...").
 - <a 
href="/site/apidocs/org/apache/juneau/marshall/MarshallingTraverseContext.Builder.html#ignoreRecursions()"
 target="_blank">ignoreRecursions()</a>: 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:
+
+```java
+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
+```

Reply via email to