如何使用Jackson JSON注释来抑制空对象?

时间:2015-05-07 19:18:47

标签: java json jackson

我正在尝试序列化类似于以下内容的对象:

class Header {
    public String title;
    public String author;
}
class Document {
    public String data;
    public Header header = new Header();
}

没有任何注释,杰克逊将序列化为:

{"data":null,"header":{"title":null,"author":null}}

@JsonInclude(NOT_NULL)@JsonInclude(NOT_EMPTY)上使用HeaderDocument,将序列化为:

{"header":{}}

我想压缩header属性,如果它是空的,但这只支持集合和字符串。理想情况下,这可以通过注释来解决,因为默认BeanSerializer可以轻松实现这一点。

我可以通过编写自定义序列化程序来实现我的需求,但是我失去了默认序列化程序中所有高级逻辑的好处。

有人能想出更好的解决方法吗?上面的结构只是一个例子,因为我正在寻找一个通用的解决方案。

1 个答案:

答案 0 :(得分:1)

它并不是那么简单 - 基本上,bean序列化程序必须暂停写入属性的字段名称,直到属性序列化程序写入某些东西 - 一旦你有了这些东西,它就会变得特别毛茸茸几个这样的嵌套在一起。

最简单的解决方法是让属性序列化程序将其值序列化为令牌缓冲区,然后如果令牌缓冲区仅包含" {"和"}"。但这意味着对象图最终会被读入和读出每个级别的缓冲区,从而使得流式生成器无法构建与生成的输出大小成比例的内容。 / p>

如果您可以使用缓冲区复制,这将大致符合您的要求:

@Test
public void suppress_empty_objects() throws Exception {
    mapper.registerModule(new SimpleModule() {
        @Override
        public void setupModule(SetupContext context) {
            context.addBeanSerializerModifier(new SuppressEmptyBean());
        }

        class SuppressEmptyBean extends BeanSerializerModifier {
            @Override
            public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
                    BeanDescription beanDesc,
                    List<BeanPropertyWriter> beanProperties) {
                // TODO: examine bean description for annotations to enable/disable suppression
                ListIterator<BeanPropertyWriter> iter = beanProperties.listIterator();
                while (iter.hasNext()) {
                    BeanPropertyWriter beanPropertyWriter = iter.next();
                    // TODO: only relevant to suppress properties that are themselves beans
                    iter.set(new SuppressEmptyPropertyWriter(beanPropertyWriter));
                }
                return beanProperties;
            }
        }

        class SuppressEmptyPropertyWriter extends BeanPropertyWriter {
            private final BeanPropertyWriter underlying;

            SuppressEmptyPropertyWriter(BeanPropertyWriter underlying) {
                super(underlying);
                this.underlying = underlying;
            }

            @Override
            public void serializeAsField(Object bean, JsonGenerator output, SerializerProvider prov)
                    throws Exception {
                TokenBuffer tokenBuffer = new TokenBuffer(output.getCodec(), false);
                underlying.serializeAsField(bean, tokenBuffer, prov);
                if (!suppress(tokenBuffer, output)) {
                    tokenBuffer.serialize(output);
                }
            }

            private boolean suppress(TokenBuffer tokenBuffer, JsonGenerator output) throws JsonParseException,
                    IOException {
                if (tokenBuffer.firstToken() != JsonToken.FIELD_NAME) return false; // nope
                JsonParser bufferParser = tokenBuffer.asParser();
                bufferParser.nextToken(); // on field name
                JsonToken valueToken1 = bufferParser.nextToken(); // on start object
                if (valueToken1 != JsonToken.START_OBJECT) return false;
                JsonToken valueToken2 = bufferParser.nextToken(); // on first thing inside object
                return valueToken2 == JsonToken.END_OBJECT;
            }
        }

    });
    Document document = new Document();
    document.data = "test";
    assertThat(mapper.writeValueAsString(document), equivalentTo("{ data: 'test' }"));
    document.header = new Header();
    assertThat(mapper.writeValueAsString(document), equivalentTo("{ data: 'test' }"));
    document.header.title = "the title";
    assertThat(mapper.writeValueAsString(document),
            equivalentTo("{ data: 'test', header: { title: 'the title' } }"));
}