【问题标题】:Using Jackson to implement toString() without annotations使用Jackson实现无注解的toString()
【发布时间】:2018-12-01 08:24:17
【问题描述】:

我想使用 Jackson 来实现 toString() 以返回对象的 JSON 表示,但我不想在我的代码中使用任何 Jackson 注释。

我尝试了如下实现:

public String toString()
{
    Map<String,Object> ordered = ImmutableMap.<String, Object>builder().
        put("createdAt", createdAt.toString()).
        put("address", address.toString()).
        build();

    ObjectMapper om = new ObjectMapper();
    om.enable(SerializationFeature.INDENT_OUTPUT);
    try
    {
        return om.writeValueAsString(object);
    }
    catch (JsonProcessingException e)
    {
        // Unexpected
        throw new AssertionError(e);
    }
}

这适用于简单的字段,但如果“地址”有自己的字段,那么不要得到这个:

{
  "address" : {
    "value" : "AZ4RPBb1kSkH4RNewi4NXNkBu7BX9DmecJ",
    "tag" : null
}

我得到了这个输出:

{
  "address" : "{\n\"value\" : \"AZ4RPBb1kSkH4RNewi4NXNkBu7BX9DmecJ\",\n        \"tag\" : null"
}

换句话说,地址值被视为字符串,而不是JsonNode

澄清一下:

  • 一方面,我想控制简单的类字段如何转换为字符串。我不想使用 Jackson 的内置转换器。
  • 另一方面,对于复杂字段,将 String 值返回给 Jackson 会导致错误行为。

我相信我可以通过在我的所有课程中添加公共toJson() 方法来解决这个问题。该方法将返回一个Map&lt;String, JsonNode&gt;,其中值是简单字段的字符串节点和复杂字段的toJson() 的输出。不幸的是,这会污染我的公共 API 的实现细节。

如何在不污染类的公共 API 的情况下实现所需的行为?

更新:我刚刚在https://stackoverflow.com/a/9599585/14731 看到了一个有趣的答案...也许我可以将复杂字段的字符串值转换回JsonNode,然后再将它们传递给Jackson。

【问题讨论】:

  • 问题是您的地图存储的是字符串,而不是对象。存储在那里的字符串是{\n\"value\" : \"AZ4RPBb1kSkH4RNewi4NXNkBu7BX9DmecJ\",\n \"tag\" : null...
  • @LuiggiMendoza 我理解,但我不想让杰克逊将对象转换为字符串。我已经用这个澄清更新了这个问题。
  • 我还是不明白你的目的。根据描述,您不想使用put("address", address),而是以某种方式继续使用put("address", address.toString())。也许您可以添加地图而不是您的对象,例如 put("address", ImmutableMap.&lt;String, Object&gt;builder().put("value", address.getValue()). put("tag", address.getTag()).build())
  • @LuiggiMendoza 对于简单的字段,我想直接控制转换。对于某些字段,我调用String.value()。对于像BigDecimal 这样的其他人,我调用BigDecimal.toPlainString()。对于复杂的字段,我希望它们能够控制自己的转换,因此我需要它们返回一些值,我可以不加修改地传递给 Jackson。

标签: java jackson


【解决方案1】:

我认为你应该在每个类中实现两种方法——一种是转储数据,第二种是从原始数据结构中构建 JSON。这个需要分开,否则每次封装嵌套的toString()调用都会嵌套越来越深。

一个例子:

class Address {
    private BigDecimal yourField;

    /* …cut… */

    public Map<String, Object> toMap() {
        Map<String, Object> raw = new HashMap<>();
        raw.put("yourField", this.yourField.toPlainString());
        /* more fields */
        return raw;
    }

    @Override
    public String toString() {
        // add JSON processing exception handling, dropped for readability
        return new ObjectMapper().writeValueAsString(this.toMap());
    }
}

class Employee {
    private Address address;

    /* …cut… */

    public Map<String, Object> toMap() {
        Map<String, Object> raw = new HashMap<>();
        raw.put("address", this.address.toMap());
        /* more fields */
        return raw;
    }

    @Override
    public String toString() {
        // add JSON processing exception handling, dropped for readability
        return new ObjectMapper().writeValueAsString(this.toMap());
    }
}

【讨论】:

  • 您缺少一项要求:我想控制简单字段的字符串表示,而不是让杰克逊处理转换。例如,我想为BigDecimal 输出BigDecimal.toPlainString(),为Optional 输出Optional.orElse(null),而不是杰克逊的默认表示。我假设我应该返回我想要的任何字符串表示形式(仅适用于简单类型),而不是 toMap() 返回原始值,这将解决我的问题。你同意吗?
  • 这就是这种分离的重点——在toMap()中你格式化字段表示;在toString() 中,您会从中输出 JSON。您应该从toMap() 返回您想要的任何字符串表示形式。 toMap() 可以在您的情况下返回:raw.put("decimalField", BigDecimal.toPlainString());toString() 这将被格式化为 JSON。您可以将其视为关注点分离的方法。我更新了答案来展示这个例子。
猜你喜欢
  • 1970-01-01
  • 2017-09-06
  • 2018-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-01
  • 1970-01-01
相关资源
最近更新 更多