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

Java 如何使用Jackson JSON注释来抑制空对象?,java,json,jackson,Java,Json,Jackson,我正在尝试序列化类似以下内容的对象: class Header { public String title; public String author; } class Document { public String data; public Header header = new Header(); } 如果没有任何注释,jackson将序列化为: {"data":null,"header":{"title":null,"author":null}} {"

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

class Header {
    public String title;
    public String author;
}
class Document {
    public String data;
    public Header header = new Header();
}
如果没有任何注释,jackson将序列化为:

{"data":null,"header":{"title":null,"author":null}}
{"header":{}}
使用
@JsonInclude(非空)
@JsonInclude(非空)
标题和
文档上的
@JsonInclude(非空)
,它将序列化为:

{"data":null,"header":{"title":null,"author":null}}
{"header":{}}
如果
header
属性为空,我想将其取消,但这仅支持集合和字符串。理想情况下,这可以通过注释来解决,因为默认的
BeanSerializer
可以轻松实现这一点

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


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

其实并不那么容易——基本上,bean序列化程序将不得不推迟编写属性的字段名,直到属性序列化程序编写了某些内容——一旦有多个这样的字段相互嵌套,这可能会变得特别棘手

解决此问题的最简单方法是,属性序列化程序将其值序列化为令牌缓冲区,然后在令牌缓冲区仅包含“{”和“}”时跳过该属性。但这意味着对象的图形最终会在每一级的缓冲区中被读入或读出,这就破坏了流生成器的功能,因为流生成器不能生成与生成的输出大小成比例的内容

如果您可以接受缓冲区复制,这将大致满足您的要求:

@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' } }"));
}
@测试
public void suppress\u empty\u objects()引发异常{
registerModule(新的SimpleModule(){
@凌驾
公共无效设置模块(设置上下文上下文){
addBeanSerializerModifier(新的SuppressEmptyBean());
}
类SuppressEmptyBean扩展BeanSerializerModifier{
@凌驾
公共列表changeProperties(SerializationConfig配置,
BEAN说明beanDesc,
列表(不动产){
//TODO:检查bean描述中的注释以启用/禁用抑制
ListIterator iter=beanProperties.ListIterator();
while(iter.hasNext()){
BeanPropertyWriter BeanPropertyWriter=iter.next();
//TODO:仅与抑制本身是bean的属性相关
iter.set(新的SuppressEmptyPropertyWriter(beanPropertyWriter));
}
返回Bean属性;
}
}
类SuppressEmptyPropertyWriter扩展BeanPropertyWriter{
私有最终BeanPropertyWriter基础;
SuppressEmptyPropertyWriter(BeanPropertyWriter基础){
超级(基础);
这个。潜在的=潜在的;
}
@凌驾
public void serializeAsField(对象bean、JsonGenerator输出、SerializerProvider prov)
抛出异常{
TokenBuffer TokenBuffer=新的TokenBuffer(output.getCodec(),false);
serializeAsField(bean、tokenBuffer、prov);
如果(!抑制(令牌缓冲区,输出)){
序列化(输出);
}
}
私有布尔抑制(TokenBuffer TokenBuffer,JsonGenerator输出)抛出JsonParseException,
IOException{
if(tokenBuffer.firstToken()!=JsonToken.FIELD\u NAME)返回false;//否
JsonParser bufferParser=tokenBuffer.aspaser();
bufferParser.nextToken();//关于字段名
JsonToken valueToken1=bufferParser.nextToken();//在开始对象上
if(valueToken1!=JsonToken.START\u对象)返回false;
JsonToken valueToken2=bufferParser.nextToken();//在对象内部的第一件事上
返回值token2==JsonToken.END_对象;
}
}
});
文档=新文档();
document.data=“测试”;
assertThat(mapper.writeValueAsString(document),等价于(“{data:'test'}”);
document.header=新标题();
assertThat(mapper.writeValueAsString(document),等价于(“{data:'test'}”);
document.header.title=“标题”;
资产(mapper.writeValueAsString(文档),
等价于(“{data:'test',header:{title:'thetitle'}”);
}

如果header=null没有值,您是否尝试过将其设置为null?是的,这是我当前的解决方案,但在填充时,它会使服务器代码充满恼人的null检查。类似的解决方案是使用@JsonIgnore标记头,并添加一个getter,该getter仅在头为空时返回非null头。