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

j5fpnvbx  于 2021-07-06  发布在  Java
关注(0)|答案(1)|浏览(376)

我有一个定制的bean序列化程序,我想应用它,但是当我应用它时,jackson不再包含null属性。
以下代码复制了问题:

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没有空属性:
{“first”:“bob”,“last”:“barker”}
如果你把这句话注解掉 return new JsonSerializer<Object>() {... 并按原样返回传入的序列化程序 return serializer; ,然后jackson序列化空属性:
{“first”:“bob”,“middle”:null,“last”:“barker”,“email”:null}
我读过很多看似相关的so文章,但都没有找到解决方案。我已经尝试显式地将Map器设置为 Include.ALWAYS 一点运气都没有。
我唯一的线索是javadoc for jsonserializer中的一条评论:
注:各种 serialize 永远不会用空值调用方法——调用方必须处理空值,通常通过调用{@link serializerprovider#findnullvalueserializer}来获取要使用的序列化程序。
这也意味着自定义序列化程序不能直接用于更改
序列化空值时要产生的输出。
我使用的是Jackson2.11.2版。
我的问题是:如何编写自定义序列化程序并使jackson遵守与null属性序列化相关的include指令?
上下文信息:我的实际自定义序列化程序的工作是有条件地隐藏序列化的属性。我有一个自定义注解, @JsonAuth 这是元注解 @JacksonAnnotationsInside @JsonInclude(Include.NON_EMPTY) 我的自定义序列化程序 ContextualSerializer )在覆盖的 isEmpty 方法和返回 true (视为空)如果没有授权。最终的结果是,我有一个可以应用于属性的注解,如果客户机未经授权,该注解将隐藏该属性,使其无法序列化。除了。。。使用自定义序列化程序会产生意外的副作用,即删除所有空属性。
更新:Jackson的 BeanPropertyWriter.serializeAsField(...) 方法将完全忽略指定给属性的任何自定义序列化程序(如果值为null)。
我可以通过向类编写一个小扩展来重写此行为,从而允许我的“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 :

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;
    }

  }
x4shl7ld

x4shl7ld1#

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

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有你描述的问题,我不知道如何解决它,所以这是一个解决办法,而不是你原来的问题的答案。

相关问题