Given the following class hierarchy, I would like Foo to be serialized differently depending on the context it is used in my class hierarchy.
public class Foo {
public String bar;
public String biz;
}
public class FooContainer {
public Foo fooA;
public Foo fooB;
}
I would like for the biz attribute to not show up in fooB when I serialize FooContainer. So the output would look something like the following.
{
"fooA": {"bar": "asdf", "biz": "fdsa"},
"fooB": {"bar": "qwer"}
}
I was going to use something JsonView, but that has to be applied at the mapper layer for all instances of a class, and this is context dependent.
On the Jackson user mailing list, Tatu gave the simplest solution (works in 2.0), which I will probably end up using for now. Awarding the bounty to jlabedo because the answer is an awesome example of how to extend Jackson using custom annotations.
public class FooContainer {
public Foo fooA;
@JsonIgnoreProperties({ "biz" })
public Foo fooB;
}
You could use a combination of a custom serializer with a custom property filter using JsonViews. Here is some code working with Jackson 2.0
Define a custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FilterUsingView {
Class<?>[] value();
}
Define some Views:
// Define your views here
public static class Views {
public class Public {};
public class Internal extends Public{};
}
Then you can write your entities like this. Note that you may define your own annotation instead of using @JsonView
:
public class Foo {
@JsonView(Views.Public.class)
public String bar;
@JsonView(Views.Internal.class)
public String biz;
}
public class FooContainer {
public Foo fooA;
@FilterUsingView(Views.Public.class)
public Foo fooB;
}
Then, here is where the code begins :) First your custom filter:
public static class CustomFilter extends SimpleBeanPropertyFilter {
private Class<?>[] _nextViews;
public void setNextViews(Class<?>[] clazz){
_nextViews = clazz;
}
@Override
public void serializeAsField(Object bean, JsonGenerator jgen,
SerializerProvider prov, BeanPropertyWriter writer)
throws Exception {
Class<?>[] propViews = writer.getViews();
if(propViews != null && _nextViews != null){
for(Class<?> propView : propViews){
System.out.println(propView.getName());
for(Class<?> currentView : _nextViews){
if(!propView.isAssignableFrom(currentView)){
// Do the filtering!
return;
}
}
}
}
// The property is not filtered
writer.serializeAsField(bean, jgen, prov);
}
}
Then a custom AnnotationIntrospector
that will do two things:
@FilterUsingView
annotation.Here is the code
public class CustomAnnotationIntrospector extends AnnotationIntrospector {
@Override
public Version version() {
return DatabindVersion.instance.version();
}
@Override
public Object findFilterId(AnnotatedClass ac) {
// CustomFilter is used for EVERY Bean, unless another filter is defined
Object id = super.findFilterId(ac);
if (id == null) {
id = "CustomFilter";
}
return id;
}
@Override
public Object findSerializer(Annotated am) {
FilterUsingView annotation = am.getAnnotation(FilterUsingView.class);
if(annotation == null){
return null;
}
return new CustomSerializer(annotation.value());
}
}
Here is your custom serializer. The only thing it does is passing your annotation's value to your custom filter, then it let the default serializer do the job.
public class CustomSerializer extends JsonSerializer<Object> {
private Class<?>[] _activeViews;
public CustomSerializer(Class<?>[] view){
_activeViews = view;
}
@Override
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider) throws IOException,
JsonProcessingException {
BeanPropertyFilter filter = provider.getConfig().getFilterProvider().findFilter("CustomFilter");
if(filter instanceof CustomFilter){
CustomFilter customFilter = (CustomFilter) filter;
// Tell the filter that we will filter our next property
customFilter.setNextViews(_activeViews);
provider.defaultSerializeValue(value, jgen);
// Property has been filtered and written, do not filter anymore
customFilter.setNextViews(null);
}else{
// You did not define a CustomFilter ? Well this serializer is useless...
provider.defaultSerializeValue(value, jgen);
}
}
}
Finally ! Let's put this all together :
public class CustomModule extends SimpleModule {
public CustomModule() {
super("custom-module", new Version(0, 1, 0, "", "", ""));
}
@Override
public void setupModule(SetupContext context) {
super.setupModule(context);
AnnotationIntrospector ai = new CustomAnnotationIntrospector();
context.appendAnnotationIntrospector(ai);
}
}
@Test
public void customField() throws Exception {
FooContainer object = new FooContainer();
object.fooA = new Foo();
object.fooA.bar = "asdf";
object.fooA.biz = "fdsa";
object.fooB = new Foo();
object.fooB.bar = "qwer";
object.fooB.biz = "test";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new CustomModule());
FilterProvider fp = new SimpleFilterProvider().addFilter("CustomFilter", new CustomFilter());
StringWriter writer = new StringWriter();
mapper.writer(fp).writeValue(writer, object);
String expected = "{\"fooA\":{\"bar\":\"asdf\",\"biz\":\"fdsa\"},\"fooB\":{\"bar\":\"qwer\"}}";
Assert.assertEquals(expected, writer.toString());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With