javaspringjacksonjson-view

@JsonView not propagated to ("nested") custom serializer


In the unit test below there are two beans: beanA and beanB, where beanA contains a reference to beanB.

When serializing the beans providing a JSON View with custom serializers the custom serializer of beanB does not know about the provided JSON View, as the output of the test is:

{
  "clazz" : "BeanA",
  "activeView" : "ViewTest",
  "beanB" : {
    "clazz" : "BeanB",
    "activeView" : null
  }
}

The only way I found to propagate the activeView is: (see full source code below)

ObjectMapper mapper = (ObjectMapper) gen.getCodec();
mapper.setConfig(provider.getConfig());

But the JavaDoc of setConfig() says "only use this method if you know what you are doing" - which I obviously don't do...

So my questions are:

  1. Is this way of propagating the JSON View ok?
  2. Is there a better way?
  3. Can someone explain the default behaviour?

(there are some line commented out marked by *** - these are only interesting when playing around without custom serializers (in which case the JSON View seems to get propagated))

public class ViewTest {

    @Test
    public void hello() throws JsonProcessingException {

        BeanA beanA = new BeanA();
        beanA.beanB = new BeanB();

        String result = new ObjectMapper()
//            .disable(MapperFeature.DEFAULT_VIEW_INCLUSION) // ***
            .writerWithView(ViewTest.class)
            .withDefaultPrettyPrinter()
            .writeValueAsString(beanA);

        System.out.println(result);
    }

    @JsonSerialize(using = BeanASerializer.class)
    static public class BeanA {

//        @JsonView(ViewTest.class) // ***
        public String clazz = this.getClass().getSimpleName();

//        @JsonView(ViewTest.class) // ***
        public BeanB beanB;
    }

    @JsonSerialize(using = BeanBSerializer.class)
    static public class BeanB {

//        @JsonView(ViewTest.class) // ***
        public String clazz = this.getClass().getSimpleName();
    }

    static void writeClazzAndActiveView(String clazz, JsonGenerator gen, SerializerProvider provider) throws IOException {
        gen.writeStringField("clazz", clazz);
        gen.writeFieldName("activeView");
        if (provider.getActiveView() == null) {
            gen.writeNull();
        } else {
            gen.writeString(provider.getActiveView().getSimpleName());
        }
    }

    static class BeanASerializer extends JsonSerializer<BeanA> {

        @Override
        public void serialize(BeanA bean, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {

            gen.writeStartObject();
            writeClazzAndActiveView(bean.clazz, gen, provider);

            // the only way I found to propagate the activeView:
//            ObjectMapper mapper = (ObjectMapper) gen.getCodec();
//            mapper.setConfig(provider.getConfig());

            gen.writeObjectField("beanB", bean.beanB);
            gen.writeEndObject();
        }
    }

    static class BeanBSerializer extends JsonSerializer<BeanB> {

        @Override
        public void serialize(BeanB bean, JsonGenerator gen, SerializerProvider provider) throws IOException, JsonProcessingException {

            gen.writeStartObject();
            writeClazzAndActiveView(bean.clazz, gen, provider);
            gen.writeEndObject();
        }
    }
}

Solution

  • Active should be properly propagated via SerializerProvider. And you are right, you should not set (nor have to set) view that way -- it is not thread-safe, for one, and all configuration of ObjectMapper is to be done before any and all use (ObjectReader and ObjectWriter allow per-call changes via various factory methods).

    Why this is happening is unclear: I think filing a bug with version info and full reproduction (if there's more to it than code) makes sense.