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 d332bfce5e Add view-based projection (@JsonView analog) to bean
marshalling; adopt isEmpty utility across the reactor.
d332bfce5e is described below
commit d332bfce5e8a0d2c7d5e9d2de30ad0896aeb5c41
Author: James Bognar <[email protected]>
AuthorDate: Tue Jun 16 13:12:06 2026 -0400
Add view-based projection (@JsonView analog) to bean marshalling; adopt
isEmpty utility across the reactor.
---
pages/release-notes/10.0.0.md | 48 +++++++++
pages/topics/02.04.12.ViewProjection.md | 182 ++++++++++++++++++++++++++++++++
pages/topics/02.20.JacksonComparison.md | 1 +
sidebars.ts | 19 ++--
4 files changed, 243 insertions(+), 7 deletions(-)
diff --git a/pages/release-notes/10.0.0.md b/pages/release-notes/10.0.0.md
index 87452d8a85..a4cbda460a 100644
--- a/pages/release-notes/10.0.0.md
+++ b/pages/release-notes/10.0.0.md
@@ -30,6 +30,54 @@ No functional behavior was changed as part of this cleanup;
all changes were pur
### New Features
+### juneau-marshall / juneau-commons
+
+#### View-based property projection (`@MarshalledProp(view=...)`)
+
+Juneau now supports **view-based projection**: declare named views on bean
properties and select an active
+view per serialize/parse call so only the in-view properties are emitted or
consumed. This is the Juneau
+equivalent of Jackson's `@JsonView` / `MapperFeature.DEFAULT_VIEW_INCLUSION`.
+
+Tag a property with one or more string view names via the new `view` member on
`@MarshalledProp`:
+
+```java
+public class Person {
+ public String id; // untagged — visible in every
view
+
+ @MarshalledProp(view = "summary")
+ public String name;
+
+ @MarshalledProp(view = "detail")
+ public String description;
+}
+```
+
+Activate a view on any serializer or parser builder, or override it per call
on a session:
+
+```java
+// Context-level default:
+var s = Json5Serializer.DEFAULT.copy().activeView("summary").build();
+s.serialize(person); // → {id:'1',name:'Alice'}
+
+// Per-call session override:
+s.createSession().activeView("detail").build().serialize(person); // →
{description:'A person',id:'1'}
+```
+
+Key behaviours:
+- **Default-view policy:** untagged properties are included in all views by
default (mirrors Jackson's
+ `DEFAULT_VIEW_INCLUSION=true`). Call `disableDefaultViewInclusion()` to
reverse.
+- **Union semantics:** `@MarshalledProp(view = {"A","B"})` makes a property
visible when `A` *or* `B` is active.
+- **Flat string names:** no marker-class hierarchy required.
+- **Parse-side:** out-of-view input properties are routed through the existing
unknown/ignored-property
+ mechanism (silently ignored by default; `ignoreUnknownBeanProperties(false)`
makes them throw).
+- **Mix-in support:** view membership can be applied to unmodifiable classes
via `@MarshalledPropApply` /
+ `applyAnnotations()`, just like any other `@MarshalledProp` attribute.
+- **Cross-format:** projection is model-level and behaves identically across
JSON, XML, HTML, MsgPack, and
+ all other Juneau formats.
+
+See the [View-Based Projection](/docs/topics/ViewProjection) topic page for
full documentation and a
+Jackson migration table.
+
### juneau-microservice-jetty
#### `JettyMicroservice` zero-config facade + bundled defaults
diff --git a/pages/topics/02.04.12.ViewProjection.md
b/pages/topics/02.04.12.ViewProjection.md
new file mode 100644
index 0000000000..2da73099b0
--- /dev/null
+++ b/pages/topics/02.04.12.ViewProjection.md
@@ -0,0 +1,182 @@
+---
+title: "View-Based Projection"
+slug: ViewProjection
+---
+
+View-based projection lets you define named **views** — property subsets — on
a bean and choose which view
+is active per serialize/parse call. Only the properties belonging to the
active view are emitted on
+serialization; on parsing, out-of-view properties are silently discarded (or
rejected, depending on your
+unknown-property policy).
+
+This is the Juneau equivalent of Jackson's `@JsonView` /
`MapperFeature.DEFAULT_VIEW_INCLUSION`.
+
+---
+
+## Declaring view membership on properties
+
+Use the
+<a
href="/site/apidocs/org/apache/juneau/annotation/MarshalledProp.html#view()"
target="_blank">@MarshalledProp(view)</a>
+member to tag a property with one or more string view names:
+
+```java
+public class Person {
+
+ // Untagged — visible in every view (default policy).
+ public String id;
+
+ // Visible only when the "summary" view is active.
+ @MarshalledProp(view = "summary")
+ public String name;
+
+ // Visible only when the "detail" view is active.
+ @MarshalledProp(view = "detail")
+ public String description;
+
+ // Visible in EITHER "summary" OR "detail" (union semantics).
+ @MarshalledProp(view = {"summary", "detail"})
+ public String email;
+}
+```
+
+View names are plain strings. No marker-class hierarchy is required.
+
+---
+
+## Activating a view on a serializer or parser
+
+Call
+<a
href="/site/apidocs/org/apache/juneau/MarshallingContext.Builder.html#activeView(java.lang.String)"
target="_blank">activeView(String)</a>
+on any serializer or parser builder to set a context-level default:
+
+```java
+// Serialize with the "summary" view.
+var s = Json5Serializer.DEFAULT.copy().activeView("summary").build();
+s.serialize(new Person());
+// → {id:'1',name:'Alice',email:'[email protected]'}
+```
+
+To override the view for a single call without rebuilding the serializer, use
+<a
href="/site/apidocs/org/apache/juneau/MarshallingSession.Builder.html#activeView(java.lang.String)"
target="_blank">activeView(String)</a>
+on a session:
+
+```java
+var session = serializer.createSession().activeView("detail").build();
+session.serialize(new Person());
+// → {description:'A person',email:'[email protected]',id:'1'}
+```
+
+Passing `null` clears the active view for that session, making all view-tagged
properties visible again.
+
+---
+
+## Default-view policy: untagged properties
+
+When an active view is set, **untagged properties are included by default** —
this matches Jackson's
+`MapperFeature.DEFAULT_VIEW_INCLUSION` default and the principle of least
surprise: only explicitly-tagged
+properties become view-restricted.
+
+To flip the policy so that only tagged properties are visible, call
+<a
href="/site/apidocs/org/apache/juneau/MarshallingContext.Builder.html#disableDefaultViewInclusion()"
target="_blank">disableDefaultViewInclusion()</a>:
+
+```java
+// Strict mode: only tagged properties appear.
+var s = Json5Serializer.DEFAULT.copy()
+ .activeView("summary")
+ .disableDefaultViewInclusion()
+ .build();
+s.serialize(new Person());
+// → {email:'[email protected]',name:'Alice'} (id omitted — it was untagged)
+```
+
+---
+
+## View semantics
+
+- **Flat string names.** No class-hierarchy inheritance; a view named
`"summary"` has no relationship to
+ any other view.
+- **Union membership.** A property tagged `{A, B}` is visible whenever the
active view is `A` **or** `B`.
+ There is no intersection requirement.
+- **No active view = all properties visible.** If no `activeView()` is set (or
it is set to `null`), the
+ feature is off and every property is serialized as normal.
+
+---
+
+## View membership on unmodifiable classes (`*Config` / `@ContextApply`)
+
+If you cannot annotate a class directly (third-party, generated, or sealed),
apply view membership via the
+<a href="/site/apidocs/org/apache/juneau/annotation/MarshalledPropApply.html"
target="_blank">@MarshalledPropApply</a>
+`*Config` mechanism:
+
+```java
+// Config annotation — does not require modifying ThirdPartyBean.
+@MarshalledPropApply(on = "ThirdPartyBean.name", value =
@MarshalledProp(view = "summary"))
+@MarshalledPropApply(on = "ThirdPartyBean.description", value =
@MarshalledProp(view = "detail"))
+public static class ThirdPartyBeanViewConfig {}
+
+// Apply the config when building the serializer.
+var s = Json5Serializer.DEFAULT.copy()
+ .applyAnnotations(ThirdPartyBeanViewConfig.class)
+ .activeView("summary")
+ .build();
+```
+
+---
+
+## Parse-side behavior
+
+When parsing, properties that are **not** in the active view are treated as
unknown properties. By default
+Juneau silently discards unknown properties, so out-of-view input is quietly
ignored:
+
+```java
+var p = Json5Parser.DEFAULT.copy().activeView("summary").build();
+// description is not in the "summary" view → ignored (default behavior).
+Person person = p.parse("{id:'1',name:'Alice',description:'ignored'}",
Person.class);
+```
+
+To make out-of-view input throw an exception, disable the unknown-property
tolerance with
+<a href="/site/apidocs/org/apache/juneau/MarshallingContext.Builder.html"
target="_blank">ignoreUnknownBeanProperties(false)</a>:
+
+```java
+var p = Json5Parser.DEFAULT.copy()
+ .activeView("summary")
+ .ignoreUnknownBeanProperties(false) // strict: out-of-view input throws
+ .build();
+p.parse("{id:'1',description:'oops'}", Person.class); // throws ParseException
+```
+
+---
+
+## Precedence with other property controls
+
+When multiple property-control mechanisms interact, the precedence is:
+
+1. `@MarshalledIgnore` / `@BeanIgnore` — hard exclude (wins over everything).
+2. `readOnly` / `writeOnly` — direction filter.
+3. Active view — membership filter.
+4. `beanProperties` / `beanPropertiesExcludes` — further narrowing.
+5. Swaps / value-level inclusion knobs — applied to surviving properties.
+
+Views and `beanProperties*` compose by **intersection**: a property must pass
both filters to appear.
+
+---
+
+## Cross-format invariance
+
+View selection lives in `MarshallingContext` / `BeanPropertyMeta` — the bean
model layer — not in any
+format-specific serializer. The same `activeView()` call produces consistent
projection across JSON, XML,
+HTML, MsgPack, YAML, and every other Juneau format.
+
+---
+
+## Migration from Jackson `@JsonView`
+
+| Jackson | Juneau |
+|---------|--------|
+| `@JsonView(MyView.class)` on a property | `@MarshalledProp(view = "MyView")`
|
+| `@JsonView({A.class, B.class})` | `@MarshalledProp(view = {"A", "B"})` |
+| `objectMapper.writerWithView(MyView.class).writeValue(...)` |
`serializer.createSession().activeView("MyView").build().serialize(...)` |
+| `objectMapper.readerWithView(MyView.class).readValue(...)` |
`parser.createSession().activeView("MyView").build().parse(...)` |
+| `MapperFeature.DEFAULT_VIEW_INCLUSION = true` (default) | default — untagged
properties included |
+| `MapperFeature.DEFAULT_VIEW_INCLUSION = false` |
`serializer.copy().activeView(...).disableDefaultViewInclusion().build()` |
+| Marker-class view tokens (`class SummaryView {}`) | Not needed — Juneau uses
plain strings |
+| View inheritance (`class DetailView extends SummaryView`) | No equivalent —
Juneau views are flat names; list both: `view = {"summary", "detail"}` |
diff --git a/pages/topics/02.20.JacksonComparison.md
b/pages/topics/02.20.JacksonComparison.md
index f36f9fa929..25faa57a7a 100644
--- a/pages/topics/02.20.JacksonComparison.md
+++ b/pages/topics/02.20.JacksonComparison.md
@@ -24,3 +24,4 @@ The following charts describe equivalent features between the
two libraries:
| `@JsonInclude` | No equivalent annotation but can be controlled via various
settings:<br/><a href="/site/apidocs/org/apache/juneau/MarshallingContext.html"
target="_blank">MarshallingContext</a><br/><a
href="/site/apidocs/org/apache/juneau/marshall/serializer/Serializer.html"
target="_blank">Serializer</a> |
| `@JsonPropertyOrder` | <a
href="/site/apidocs/org/apache/juneau/commons/bean/BeanType.html#properties()"
target="_blank">@BeanType(properties="...")</a> |
| `@JsonValue`<br/>`@JsonRawValue` | Can be replicated using swaps with
`Reader` swapped values. |
+| `@JsonView(MyView.class)` | <a
href="/site/apidocs/org/apache/juneau/annotation/MarshalledProp.html#view()"
target="_blank">@MarshalledProp(view="MyView")</a> — see [View-Based
Projection](/docs/topics/ViewProjection) |
diff --git a/sidebars.ts b/sidebars.ts
index 568cab0e4b..445ae49b74 100644
--- a/sidebars.ts
+++ b/sidebars.ts
@@ -171,13 +171,18 @@ const sidebars: SidebarsConfig = {
id:
'topics/02.04.10.PojoBuilders',
label:
'2.4.10. POJO Builders',
},
- {
- type:
'doc',
- id:
'topics/02.04.11.BypassSerialization',
- label:
'2.4.10. Bypass Serialization using `Readers` and `InputStreams`',
- },
- ],
- },
+ {
+ type: 'doc',
+ id:
'topics/02.04.11.BypassSerialization',
+ label: '2.4.11.
Bypass Serialization using `Readers` and `InputStreams`',
+ },
+ {
+ type: 'doc',
+ id:
'topics/02.04.12.ViewProjection',
+ label: '2.4.12.
View-Based Projection',
+ },
+ ],
+ },
{
type: 'doc',
id:
'topics/02.05.HttpPartSerializersParsers',