当前位置: 首页 > 知识库问答 >
问题:

Jackson不会用自定义序列化程序序列化null

晏卓君
2023-03-14

下面的代码再现了这个问题:

import java.io.IOException;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import lombok.Value;

public class Test {

  @Value
  public static class Contact {
    String first;
    String middle;
    String last;
    String email;
  }
  
  
  public static void main(String[] args) throws Exception {
    Contact contact = new Contact("Bob", null, "Barker", null);
    
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new SimpleModule() {
        @Override public void setupModule(SetupContext context) {
            super.setupModule(context);
            context.addBeanSerializerModifier(new BeanSerializerModifier() {
                @Override public JsonSerializer<?> modifySerializer(SerializationConfig config, BeanDescription desc, JsonSerializer<?> serializer) {
//                  return serializer;
                  return new JsonSerializer<Object>() {
                    @Override public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
                      ((JsonSerializer<Object>) serializer).serialize(value, gen, serializers);
                    }};
                }
            });
        }
    });
    
    
    System.out.println(
        mapper.writerWithDefaultPrettyPrinter().writeValueAsString(contact)
    );
    
  }
}

上面的代码不做其他注册“自定义”序列化程序的事情(只是委托回原始序列化程序),但它生成的JSON没有null属性:

{“第一个”:“鲍勃”,“最后一个”:“巴克”}

我读过许多看似相关的SO文章,但没有一篇能让我找到解决方案。我尝试在序列化时显式地将映射器设置为include.always,但没有成功。

我唯一的线索是JavaDoc for JsonSerializer中的一条注释:

注意:永远不能用空值调用各种serialize方法--调用者必须处理空值,通常通过调用{@link serializerprovider#findnullvalueserializer}来获得要使用的序列化程序。
这也意味着不能直接使用自定义序列化程序来更改序列化空值时要生成的输出

我的问题是:我如何编写一个自定义序列化程序,并让Jackson尊重其关于空属性序列化的常见Include指令?

上下文信息:我的实际自定义序列化程序的工作是有条件地隐藏属性以避免序列化。我有一个自定义注释,@jsonannotationsinside@jsoninclude(include.non_empty),它用@jsonannotationsinside@jsoninclude(include.non_empty)进行元注释,我的自定义序列化程序(contextualserializer)在重写的isempty方法中查找该注释,如果缺少授权,则返回true(视为空)。最终的结果是,我有了一个可以应用于属性的注释,如果客户机没有被授权,它将隐藏属性,使其不被序列化。除了...使用自定义序列化程序会产生删除所有空属性的意外副作用。

更新:如果值为NULL,Jackson的BeanPropertyWriter.SerializeasField(...)方法将完全忽略分配给该属性的任何自定义序列化程序。

我可以通过编写类的小扩展来覆盖这种行为,它允许我的“IsAuthorized”逻辑抢占空检查:

  public class JsonAuthPropertyWriter extends BeanPropertyWriter {
   
    private final Predicate<Object> authFilter; 
    
    private JsonAuthPropertyWriter(BeanPropertyWriter delegate, Predicate<Object> authFilter) {
      super(delegate);
      this.authFilter = authFilter;
      // set null serializer or authorized null values disappear
      super.assignNullSerializer(NullSerializer.instance);
    }
    
    @Override
    public void serializeAsField(
        Object bean,
        JsonGenerator gen,
        SerializerProvider prov) throws Exception {
      boolean authorized = authFilter.test(bean);
      if (!authorized) return;
      super.serializeAsField(bean, gen, prov);
    }
  }

我使用BeanSerializerModifier注入了这些自定义BeanPropertyWriter:

  private static class JsonAuthBeanSerializerModifier extends BeanSerializerModifier {
    
    @Override
    public List<BeanPropertyWriter> changeProperties(
        SerializationConfig config,
        BeanDescription beanDesc, 
        List<BeanPropertyWriter> beanProperties
        ) {
      
      for (int i = 0; i < beanProperties.size(); i++) {
        BeanPropertyWriter beanPropertyWriter = beanProperties.get(i);
        JsonAuth jsonAuth = beanPropertyWriter.findAnnotation(JsonAuth.class);
        if (jsonAuth != null) {
          Predicate<Object> authPredicate = ...
          beanProperties.set(i, new JsonAuthPropertyWriter(beanPropertyWriter, authPredicate));
        }
      }
      return beanProperties;
    }
    
  }

共有1个答案

杨海
2023-03-14

我可能误解了你想要什么,但这种方法似乎很有用:

import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.Map;

public class Test2 {

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface JsonAuth {

    }

    @JsonFilter("myFilter")
    public static class Contact {

        @JsonAuth
        String first;
        @JsonAuth
        String middle;
        @JsonAuth
        String last;
        String email;

        public Contact(String first, String middle, String last, String email) {
            this.first = first;
            this.middle = middle;
            this.last = last;
            this.email = email;
        }
        public String getFirst() {
            return first;
        }
        public void setFirst(String first) {
            this.first = first;
        }
        public String getMiddle() {
            return middle;
        }
        public void setMiddle(String middle) {
            this.middle = middle;
        }
        public String getLast() {
            return last;
        }
        public void setLast(String last) {
            this.last = last;
        }
        public String getEmail() {
            return email;
        }
        public void setEmail(String email) {
            this.email = email;
        }
    }
    public static Map<String,Boolean> fieldSerialisationCount = new HashMap<>();

    public static void main(String[] args) throws Exception {
        Contact contact = new Contact("Bob", null, "Barker", null);

        ObjectMapper mapper = new ObjectMapper();
        FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", new SimpleBeanPropertyFilter() {
            @Override
            protected boolean include(BeanPropertyWriter writer) {
                return super.include(writer) && isAuthed(writer);
            }
            @Override
            protected boolean include(PropertyWriter writer) {
                return super.include(writer) && isAuthed(writer);
            }

            private boolean isAuthed(PropertyWriter writer) {
                if (!writer.getMember().hasAnnotation(JsonAuth.class)) {
                    return true;
                } else {

                    return fieldSerialisationCount.compute(writer.getName(), (n, b) -> b == null ? true : !b); // check auth here
                }
            }
        });
        mapper.setFilterProvider(filters);
        ObjectWriter writer = mapper.writer(filters).withDefaultPrettyPrinter();

        System.out.println(
                writer.writeValueAsString(contact)
        );
        System.out.println(
                writer.writeValueAsString(contact)
        );
        System.out.println(
                writer.writeValueAsString(contact)
        );
    }
}

它每隔一段时间序列化带注释的字段,就像使用持久状态的筛选器一样。

请让我知道这是否对你有效。

 类似资料:
  • 情况如下:我已经设法让Jackson反序列化以下通用 作为HTTP客户端,并使用exchange

  • I'va是一个OID接口,可以由许多具体类型实现: 现在我有一个具有两个字段的对象,一个使用抽象接口类型(OID)定义,另一个使用具体类型(MyOID)定义 我想使用jackson以不同的方式序列化/反序列化字段,无论它们是使用抽象接口类型还是具体类型定义的: 注意,被序列化,包括类型信息(多态序列化),而被序列化为文本 为此,我将OID接口注释为: 并为每个具体类型分配了类型id: 最后,对容器

  • 我有一个Spring项目,我尝试添加一个自定义反序列化器来反序列化日期属性,具体取决于它们的格式。如果我将其用作Date属性的注释,则效果很好。但是,如果我将反序列化器添加到对象映射器中,当Jackson反序列化日期时,它不会调用。 我尝试这样应用我的自定义反序列化程序: 我不想每次都对Date属性应用注释,我想默认使用此反序列化器。我做错了什么?

  • 我想反序列化表单中的类: 其中文本是加密的,反序列化应该在重建TestFieldEncryptedMessage实例之前取消对值的加密。 我采用的方法非常类似于:https://github.com/codesqueak/jackson-json-crypto 也就是说,我正在构建一个扩展SimpleModule的模块: 如您所见,设置了两个修饰符:EncryptedSerializerModif

  • 问题内容: 我有两个要使用Jackson序列化为JSON的Java类: 我想将Item序列化为此JSON: 用户序列化为仅包含。我还将能够将所有用户对象序列化为JSON,例如: 所以我想我需要为此编写一个自定义的序列化程序并尝试过: 我使用来自Jackson How-to:Custom Serializers的 代码对JSON进行了序列化: 但是我得到这个错误: 如何在Jackson上使用自定义序

  • easyopen序列化使用fastjson处理json,xstream处理xml。现在我们来自定义实现一个json处理: 新建一个类JsonFormatter,实现ResultSerializer接口 public class JsonFormatter implements ResultSerializer { @Override public String serialize(