如何不使用Jackson objectMapper编写Option.None(并读取它)?

我使用杰克逊ObjectMapper对我的某些数据进行序列化和反序列化,这些数据具有javaslang Option类型的字段。我使用JavaslangModule(和Jdk8Module)。并且当它写入json时,Option.None值字段被写为null

要减小json的大小并在以后添加新字段时提供一些简单的向后兼容性,我想要的是:

  1. 带有Option.None值的字段不会被写入
  2. 缺少与Option类型的数据模型相对应的json字段,请设置为Option。读取后无提示

=>那有可能,怎么办?

注意: 我认为不写/删除null json字段将解决(1)。可能吗?然后,读取它是否可以正常工作(即,如果json中缺少具有Option值的model字段,请将其设置为None?

seasun19880923 回答:如何不使用Jackson objectMapper编写Option.None(并读取它)?

幸运的是,有一个更简单的解决方案。

1)在ObjectMapper配置中,将序列化包含设置为仅包含不存在的字段:

  @Bean
  public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModules(vavr());
    objectMapper.setSerializationInclusion(NON_ABSENT);

    return objectMapper;
  }

2)将可选字段的默认值设置为Option.none

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Foo {
  private Option<String> bar = Option.none(); // If the JSON field is null or not present,the field will be initialized with none
}

就是这样!

更好的消息是它适用于所有Iterables,而不仅仅是Option。特别是它也适用于Vavr List类型!

,

我找到了与不相容(lombok @Value)模型一起使用的解决方案:

  1. 使用未写入Op​​tion.None的mixIn在所有Object上添加过滤器(请参见下面的“解决方案”)
  2. 当缺少相应的json条目时,我现有的ObjectMapper(带有JavaslangModule)已将“无”设置为“选项”字段

代码

import static org.assertj.core.api.Assertions.assertThat;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import javaslang.control.Option;
import javaslang.jackson.datatype.JavaslangModule;
import lombok.AllArgsConstructor;
import lombok.Value;
import org.junit.Test;

import java.io.IOException;
import java.lang.reflect.Field;

public class JsonModelAndSerialization {

  // Write to Json
  // =============

  private static ObjectMapper objectMapper = new ObjectMapper()
      .registerModule(new Jdk8Module())
      .registerModule(new JavaslangModule())

      // not required but provide forward compatibility on new field
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);


  static String write(Object data) throws JsonProcessingException {
    SimpleBeanPropertyFilter filter = new NoneOptionPropertyFilter();
    objectMapper.addMixIn(Object.class,NoneOptionFilter.class);
    final SimpleFilterProvider filters = new SimpleFilterProvider().setDefaultFilter(filter);
    ObjectWriter writer = objectMapper.writer(filters);

    return writer.writeValueAsString(data);
  }

  // Filter classes
  // ==============

  @JsonFilter("Filter None")
  private static class NoneOptionFilter {}

  private static class NoneOptionPropertyFilter extends SimpleBeanPropertyFilter {
    @Override
    public void serializeAsField(
        Object pojo,JsonGenerator jgen,SerializerProvider provider,PropertyWriter writer) throws Exception{
      Field field = pojo.getClass().getDeclaredField(writer.getName());
      if(field.getType().equals(Option.class)){
        field.setAccessible(true);
        Option<?> value = (Option<?>) field.get(pojo);
        if(value.isEmpty()) return;
      }
      super.serializeAsField(pojo,jgen,provider,writer);
    }
  }

  // Usage example
  // =============

  // **important note**
  // For @Value deserialization,a lombok config file should be added
  // in the source folder of the model class definition
  // with content:
  //    lombok.anyConstructor.addConstructorProperties = true

  @Value
  @AllArgsConstructor(onConstructor_={@JsonCreator})
  public static class StringInt {
    private int intValue;
    private Option<String> stringValue;
  }

  @Value
  @AllArgsConstructor(onConstructor_={@JsonCreator})
  public static class StringIntPair {
    private StringInt item1;
    private StringInt item2;
  }

  @Test
  public void readWriteMyClass() throws IOException {
    StringIntPair myClass = new StringIntPair(
      new StringInt(6 * 9,Option.some("foo")),new StringInt( 42,Option.none()));

    String json = write(myClass);
    // {"item1":{"intValue":54,"stringValue":"foo"},"item2":{"intValue":42}}

    StringIntPair myClass2 = objectMapper.readValue(json,StringIntPair.class);

    assertThat(myClass2).isEqualTo(myClass);
  }
}

优点:

  • 使用Option.None时减小json的大小(因此,在模型中添加Option字段时不使用时不会消耗大小)
  • 在以后在模型中添加选项类型的字段时,它提供向后读取兼容性(默认为None

缺点:

  • 无法区分无字段值的正确数据和错误地忘记了该字段的错误数据。我认为这是可以接受的。
本文链接:https://www.f2er.com/3059396.html

大家都在问