Skip to main content

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 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 @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:

  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​

JacksonJuneau
@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 = falseserializer.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"}