View-Based Projection
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 @MarshalledProp(view) member to tag a property with one or more string view names:
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 activeView(String) on any serializer or parser builder to set a context-level default:
// Serialize with the "summary" view.
var s = Json5Serializer.DEFAULT.copy().activeView("summary").build();
s.write(new Person());
// → {id:'1',name:'Alice',email:'alice@example.com'}
To override the view for a single call without rebuilding the serializer, use activeView(String) on a session:
var session = serializer.createSession().activeView("detail").build();
session.write(new Person());
// → {description:'A person',email:'alice@example.com',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 disableDefaultViewInclusion():
// Strict mode: only tagged properties appear.
var s = Json5Serializer.DEFAULT.copy()
.activeView("summary")
.disableDefaultViewInclusion()
.build();
s.write(new Person());
// → {email:'alice@example.com',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 isAorB. There is no intersection requirement. - No active view = all properties visible. If no
activeView()is set (or it is set tonull), 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
@MarshalledPropApply
*Config mechanism:
// 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:
var p = Json5Parser.DEFAULT.copy().activeView("summary").build();
// description is not in the "summary" view → ignored (default behavior).
Person person = p.read("{id:'1',name:'Alice',description:'ignored'}", Person.class);
To make out-of-view input throw an exception, disable the unknown-property tolerance with ignoreUnknownBeanProperties(false):
var p = Json5Parser.DEFAULT.copy()
.activeView("summary")
.ignoreUnknownBeanProperties(false) // strict: out-of-view input throws
.build();
p.read("{id:'1',description:'oops'}", Person.class); // throws ParseException
Precedence with other property controls
When multiple property-control mechanisms interact, the precedence is:
@MarshalledIgnore/@BeanIgnore— hard exclude (wins over everything).readOnly/writeOnly— direction filter.- Active view — membership filter.
beanProperties/beanPropertiesExcludes— further narrowing.- 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().write(...) |
objectMapper.readerWithView(MyView.class).readValue(...) | parser.createSession().activeView("MyView").build().read(...) |
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"} |