【问题标题】:Using Jackson ObjectMapper with Java 8 Optional values将 Jackson ObjectMapper 与 Java 8 可选值一起使用
【发布时间】:2014-10-30 20:18:04
【问题描述】:

我试图使用 Jackson 将类值写入 JSON,其中包含 Optional 作为字段:

public class Test {
    Optional<String> field = Optional.of("hello, world!");

    public Optional<String> getField() {
        return field;
    }

    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        System.out.println(mapper.writeValueAsString(new Test()));
    }
}

执行时,该类生成以下输出:

{"field":{"present":true}}

我了解包含的当前/不存在字段,并且可以在读取 JSON 数据时解决它,但是我无法解决可选的实际内容从未写入输出的事实。 :(

除了不使用 ObjectMapper 之外,这里有什么解决方法吗?

【问题讨论】:

  • Optionals 并不意味着用作字段(或就此而言的属性)。它们只能用作返回值。
  • @zeroflagL 您能否提供任何可靠的来源来描述 Optional 的含义?
  • @Jonas this answer 例如:我们的目的是为库方法返回类型提供有限的机制,在这种情况下需要一种明确的方式来表示“无结果”。不实现Serializable 也是不言而喻的。
  • 即使将Optional 仅视为返回类型,我们仍可能希望将返回的Optional 放在JSON 表示中。例如您有一项服务返回给定文本的语言。服务方法调用返回一个Optional,这就是你想要的json。

标签: java jackson java-8


【解决方案1】:

您可以使用jackson-datatype-jdk8,其描述为:

支持新的 JDK8 特定类型,例如 Optional

为了做到这一点:

  • com.fasterxml.jackson.datatype:jackson-datatype-jdk8 添加为依赖项
  • 向您的对象映射器注册模块:objectMapper.registerModule(new Jdk8Module());

【讨论】:

  • 根据链接的 GitHub 存储库:NOTE: This module has become part of Jackson Java 8 Modules as of Jackson 2.8.5 This repo still exists to allow release of patch versions of older versions; it will be hidden (made private) in near future.
  • 你还需要注册一个像ObjectMapper objectMapper = new ObjectMapper(); objectMapper.registerModule(new Jdk8Module());这样的模块
【解决方案2】:

您只需要注册模块Jdk8Module。然后它将任何Optional&lt;T&gt; 序列化为T(如果存在)或null,否则。

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jdk8Module());

让我们稍微修改一下Test 类,以便我们可以测试Optional.empty() 的值。这与序列化时的原始Test 类相似,因为对象映射器正在寻找getter(因为该字段是private)。不过,使用原始的 Test 类也可以。

class Test {
    private final String field;

    public Test(String field) {
        this.field = field;
    }

    public Optional<String> getField() {
        return Optional.ofNullable(field);
    }
}

然后在我们的主类中:

Test testFieldNull = new Test(null);
Test testFieldNotNull = new Test("foo");

// output: {"field":null}
System.out.println(objectMapper.writeValueAsString(testFieldNull)); 

// output: {"field":"foo"}
System.out.println(objectMapper.writeValueAsString(testFieldNotNull)); 

【讨论】:

    【解决方案3】:

    尝试将选项传递给@JsonInclude 注释。
    例如,如果您不想在值为null 时显示field您可能需要使用 Jackson-Modules >2.8.5

    import com.fasterxml.jackson.annotation.JsonInclude;
    
    public class Test {
        @JsonInclude(JsonInclude.Include.NON_NULL)
        Optional<String> field;
    }
    

    【讨论】:

    • 这正是我一直在寻找的,可能应该是答案。
    【解决方案4】:

    如果您使用的是最新版本的 Spring-boot,那么您可以通过在 pom 文件中添加以下依赖项来实现此目的

    <dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-jdk8</artifactId>
    </dependency>
    

    然后自动连接 JacksonObjectMapper。

    @Autowired
    private ObjectMapper jacksonObjectMapper;
    

    然后使用上面Spring容器的mapper实例将Object转为String

    jacksonObjectMapper.writeValueAsString(user);
    

    Spring blog says :

    如果在类路径中检测到一些知名的 Jackson 模块,则会自动注册:

    • jackson-datatype-jdk7:Java 7 类型,如 java.nio.file.Path(截至 4.2.1 版本)
    • jackson-datatype-joda:Joda-Time 类型
    • jackson-datatype-jsr310:Java 8 日期和时间 API 数据类型
    • jackson-datatype-jdk8:其他 Java 8 类型,如 Optional(截至 4.2.0 版本)

    【讨论】:

      【解决方案5】:

      与@Manikandan 的回答类似,但将@JsonProperty 添加到私有字段而不是getter,这样您就不会在公共api 上公开您的工作。

      public class Test {
      
          @JsonProperty("field")
          private String field;
      
          @JsonIgnore
          public Optional<String> getField() {
              return Optional.of(field); // or Optional.ofNullable(field);
          }
      }
      

      【讨论】:

        【解决方案6】:

        Optional 类有一个 value 字段,但没有标准的 getter/setter。默认情况下,Jackson 会查找 getter/setter 来查找类属性。

        您可以添加自定义 Mixin 以将字段标识为属性

        final class OptionalMixin {
            private Mixin(){}
            @JsonProperty
            private Object value;
        }
        

        并将其注册到您的ObjectMapper

        ObjectMapper mapper = new ObjectMapper();
        mapper.addMixInAnnotations(Optional.class, OptionalMixin.class);
        

        您现在可以序列化您的对象。

        System.out.println(mapper.writeValueAsString(new Test()));
        

        将打印

        {"field":{"value":"hello, world!","present":true}}
        

        考虑同时查看jackson-datatype-guava。有一个用于 Guava 类型的 Jackson Module 实现,包括它们的 Optional。它可能比我上面显示的更完整。

        【讨论】:

        • 行得通,谢谢。有没有办法让 Mixin 成为所有新映射器的默认值?
        • @asieira 我相信你需要注册 Module 来注册 mixin。然后您必须使用此Module 创建映射器。 (我不知道这是否比只注册 mixin 更适合你。)
        【解决方案7】:

        定义新的 getter,它将返回 String 而不是 Optional。

        public class Test {
            Optional<String> field = Optional.of("hello, world!");
        
            @JsonIgnore
            public Optional<String> getField() {
                return field;
            }
        
            @JsonProperty("field")
            public String getFieldName() {
                return field.orElse(null);
            }
        
        }
        

        【讨论】:

        • 可以,是的,但会破坏目的或首先返回可选值。
        猜你喜欢
        • 2014-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-09
        • 1970-01-01
        • 2021-07-28
        • 2017-02-18
        • 1970-01-01
        • 2023-04-08
        相关资源
        最近更新 更多