Feature: JSON Views
Idea
"Views" in this context mean ability to define subsets of logical properties (things accessed via getters or fields) to serialize. Views are defined statically (using annotations), but view to use for serialization is chosen dynamically (per serialization).
Design
- Views themselves are identified by using Classes, instead of Strings (or dedicated objects). Reasons:
- With classes, can use inheritance to naturally (?) represent View hierarchies (if a field is part of a View, it'll be also part of parent view)
- Classes can be used as annotation values: Enums not (Enums would have been one other obvious possibility)
- For future extensibility, classes can also be annotated if need be (no plans for such annotations yet)
- View membership defined using annotations: specify which view(s)property will be included in. If no view annotation, assumed to mean View identified by Object.class: that is, included in all views
View to use for serialization (and with 2.0, deserialization) is specified dynamically; active view is a property of SerializationConfig (and DeserializationConfig). Conceptually defaults to Object.class; as if no View functionality was used at all.
- Only single active view per serialization; but due to inheritance of Views, can combine Views via aggregation.
- All view membership inclusive, no need for exclusions? (however: could add an option to change default handling of 'unmarked' properties to mean "don't include unless specifically identified)
Implementation
1.4 implementation is used as follows.
First, defining views means declaring classes; you can reuse existing ones, or just create bogus classes -- they are just view identifiers with relationship information (child inherits view membership from parents):
// View definitions:
class Views {
static class Public { }
static class ExtendedPublic extends PublicView { }
static class Internal extends ExtendedPublicView { }
}
public class Bean {
// Name is public
@JsonView(Views.Public.class) String name;
// Address semi-public
@JsonView(Views.ExtendPublic.class) Address address;
// SSN only for internal usage
@JsonView(Views.Internal.class) SocialSecNumber ssn;
}With such view definitions, serialization would be done like so:
// short-cut: objectMapper.writeValueUsingView(out, beanInstance, ViewsPublic.class); // or fully exploded: objectMapper.getSerializationConfig().setSerializationView(Views.Public.class); // (note: can also pre-construct config object with 'mapper.copySerializationConfig'; reuse) objectMapper.writeValue(out, beanInstance); // will use active view set via Config // or, starting with 1.5, more convenient (ObjectWriter is reusable too) objectMapper.viewWriter(ViewsPublic.class).writeValue(out, beanInstance);
and result would only contain 'name', not 'address' or 'ssn'.
NOTE: even if you only want to use "default" view -- that is, just exclude things that are only to be included in specific "full" view -- you DO need to enable View processing by specifying a view. If you do not have explicit "basic" view setting, just use Object.class.
Handling of "view-less" properties
By default all properties without explicit view definition are included in serialization. But starting with Jackson 1.5 you can change this default by:
objectMapper.configure(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION, false);
where false means that such properties are NOT included when enabling with a view. Default for this property is 'true'.
Customization
Although default implementation is not very customizable, underlying design does allow for implementing elaborate custom filtering, using alternative means of defining custom views. Here is how.
Enabling "view" processing
Depending on exactly how custom serialization is implemented, you may (or may not) need to enable view processing. If it is needed, you will just do something like:
ObjectWriter w = objectMapper.viewWriter(SomeClass.class); // 1.8 and prior ObjectWriter w = objectMapper.writerWithView(SomeClass.class); // 1.9 and above
which offers same set of writeValue (and writeValueAsString, writeValueAsBytes, ...) write methods and can be reused easily (or passed).
As of 2.0 this is also available for deserialization, like so:
ObjectReader r = objectMapper.readerWithView(SomeClass.class); // 2.0 and above
Views with JAX-RS
With 2.3 (of JAX-RS module) it is also possible to annotate JAX-RS resources like so:
public class Resource {
@JsonView(Views.Public.class)
@GET
@Produces(MediaType.APPLICATION_JSON )
public List<Object> getElements() {
...
return someResultList;
}
}so that you need not try to configure active view via ObjectReader / ObjectWriter.
How to plug in custom View processing?
What you need to implement is a sub-class of org.codehaus.jackson.map.ser.BeanPropertyWriter: this is what standard JsonView does; and make SerializerFactory (usually BeanSerializerFactory) construct BeanSerializer with customized versions. Once this is done, you can control output during serialization as you want.
There is some sample code (src/sample/CustomSerializationView.java) to show how exactly this is done.
Or with 1.7, even better way would be to use JacksonFeatureJsonFilter, which finally gives full dynamic control for application. Annotation is needed for value types, to indicate logical filter id to use, but mapping from id to filter is fully dynamic (on per-call basis if necessary).