1

我使用jacksonObjectMapper对我的一些数据进行序列化和反序列化,这些数据具有javaslangOption类型的字段。我使用JavaslangModule(和Jdk8Module)。当它写入 json 时,Option.None值字段被写为null.

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

  1. 具有 Option.None 值的字段根本不会被写入,
  2. 缺少与 Option 类型的数据模型对应的 json 字段,读取时设置为 Option.None

=> 这可能吗,怎么做?

注意:我认为不写入/删除空 json 字段可以解决 (1)。可能吗?然后,读取它是否有效(即,如果 json 中缺少具有 Option 值的模型字段,请将其设置为无?

4

2 回答 2

1

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

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。特别是它也适用于 VavrList类型!

于 2019-12-01T16:56:46.280 回答
0

我找到了一个适用于 immuatble (lombok @Value) 模型的解决方案:

  1. Object使用不写 Option.None的所有使用 mixIn 的过滤器(请参阅下面的“解决方案”)
  2. 当缺少相应的 json 条目时,我现有的ObjectMapper(带有JavaslangModule)已经将 None 设置为 Option 字段

编码

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

优点:

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

缺点:

  • 无法区分具有 None 字段值的正确数据和错误忘记字段的不正确数据。我认为这是完全可以接受的。
于 2019-11-21T16:04:28.327 回答