简体   繁体   English

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

[英]Jackson won't serialize null with a custom Serializer

I have a custom bean serializer that I'd like to apply, but when I do, Jackson no longer includes null properties.我有一个我想应用的自定义 bean 序列化程序,但是当我这样做时,Jackson 不再包含 null 属性。

The following code reproduces the issue:以下代码重现了该问题:

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

The above code does nothing other that register a 'custom' serializer (that just delegates back to the original serializer), yet it produces JSON without the null properties:上面的代码除了注册一个“自定义”序列化器(只是委托给原始序列化器)之外什么都不做,但它生成的 JSON 没有 null 属性:

{ "first" : "Bob", "last" : "Barker" } {“第一”:“鲍勃”,“最后”:“巴克”}

If you comment out the return new JsonSerializer<Object>() {... and return the passed in serializer as is return serializer;如果您注释掉return new JsonSerializer<Object>() {...并按原样return serializer;传入的序列return serializer; , then Jackson serializes the null properties: ,然后 Jackson 序列化 null 属性:

{ "first" : "Bob", "middle" : null, "last" : "Barker", "email" : null } {“第一”:“鲍勃”,“中间”:空,“最后”:“巴克”,“电子邮件”:空}


I have read over many seemingly related SO articles, but none have led me to a solution yet.我已经阅读了许多看似相关的 SO 文章,但没有一篇文章让我找到了解决方案。 I've tried explicitly setting the mapper to Include.ALWAYS on serialization, with no luck.我已经尝试在序列化时将映射器显式设置为Include.ALWAYS ,但没有运气。

My only lead is a comment in the JavaDoc for JsonSerializer:我唯一的线索是 JavaDoc 中 JsonSerializer 的评论:

NOTE: various serialize methods are never (to be) called with null values -- caller must handle null values, usually by calling {@link SerializerProvider#findNullValueSerializer} to obtain serializer to use.注意:从不(将)使用空值调用各种serialize方法——调用者必须处理空值,通常通过调用 {@link SerializerProvider#findNullValueSerializer} 来获取要使用的序列化器。
This also means that custom serializers cannot be directly used to change这也意味着不能直接使用自定义序列化器来改变
the output to produce when serializing null values.序列化空值时产生的输出。

I am using Jackson version 2.11.2.我正在使用 Jackson 版本 2.11.2。


My question is: How can I write a custom serializer and have Jackson respect its usual Include directives with regard to null property serialization?我的问题是:如何编写自定义序列化程序并让 Jackson 尊重其通常的包含关于空属性序列化的指令?

Context Info: My actual custom serializer's job is to conditionally hide properties from serialization.上下文信息:我的实际自定义序列化程序的工作是有条件地隐藏序列化的属性。 I have a custom annotation, @JsonAuth that is meta-annotated with @JacksonAnnotationsInside @JsonInclude(Include.NON_EMPTY) which my custom serializer (a ContextualSerializer ) looks for in an overriden isEmpty method and returns true (treat as empty) if authorization is lacking.我有一个自定义注释, @JsonAuth ,它是用@JacksonAnnotationsInside @JsonInclude(Include.NON_EMPTY)元注释的,我的自定义序列化程序(一个ContextualSerializer )在重写的isEmpty方法中查找,如果缺少授权,则返回true (视为空) . The end result is that I have an annotation that can be applied to properties which will hide the property from serialization if the client is not authorized.最终结果是我有一个可以应用于属性的注释,如果客户端未经授权,它将隐藏序列化的属性。 Except ... usage of the custom serializer has the unintended side effect of dropping all null properties.除了...自定义序列化程序的使用具有删除所有空属性的意外副作用。


Update: Jackson's BeanPropertyWriter.serializeAsField(...) method will completely ignore any custom serializer assigned to the property if the value is null.更新:如果值为 null,Jackson 的BeanPropertyWriter.serializeAsField(...)方法将完全忽略分配给该属性的任何自定义序列化程序。

I was able to override this behavior by writing a small extension to the class, which allowed my "isAuthorized" logic to preempt the null check:我能够通过为类编写一个小的扩展来覆盖这种行为,这允许我的“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);
    }
  }

And I injected these custom BeanPropertyWriters using a BeanSerializerModifier :我使用BeanSerializerModifier注入了这些自定义 BeanPropertyWriters :

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

I may be misunderstanding what you want, but this approach seems useful:我可能误解了你想要什么,但这种方法似乎很有用:

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

It serialises annotated fields every other time, just as an example of a filter using persistent state.它每隔一次序列化带注释的字段,就像使用持久状态的过滤器的一个例子。

Please let me know whether this works for you.请让我知道这是否适合您。

By the way, I agree that Jackson has the problem you describe, and I don't know how to solve it, so this is a work-around rather than an answer to your original question.顺便说一下,我同意杰克逊有你描述的问题,我不知道如何解决它,所以这是一个变通方法,而不是你最初问题的答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM