【问题标题】:Jackson - combine @JsonValue and @JsonSerialize杰克逊 - 结合 @JsonValue 和 @JsonSerialize
【发布时间】:2016-01-08 09:13:03
【问题描述】:

我正在尝试 @JsonValue@JsonSerialize 的组合。让我们从我当前的容器类开始:

public class Container {
    private final Map<SomeKey, Object> data;

    @JsonValue
    @JsonSerialize(keyUsing = SomeKeySerializer.class)
    public Map<SomeKey, Object> data() {
        return data;
    }
}

在这种情况下,不使用自定义序列化程序SomeKeySerializer

如果我按以下方式更改容器,则调用序列化程序:

public class Container {
    @JsonSerialize(keyUsing = SomeKeySerializer.class)
    private final Map<SomeKey, Object> data;
}

但是,这不是我想要的,因为这会在输出 JSON 中引入另一个“数据”级别。

是否可以以某种方式组合@JsonValue@JsonSerialize

我总是可以为Container 编写另一个自定义序列化程序,它或多或少与@JsonValue 背后的功能相同。在我看来,这或多或少是一种黑客行为。

杰克逊版本:2.6.2

【问题讨论】:

  • 如何在private final Map&lt;SomeKey, Object&gt; data;中放入数据?
  • 实际上,我只是想重现这一点,在我看来,使用@JsonValue 不是调用序列化程序,它只是调用 SomeKey.toString()。这不是你看到的吗?
  • 没错,这是我的问题:)。
  • 值得提交一个错误——组合注释很棘手,@JsonValue 的处理与常规 getter 非常不同......但无论如何,理想情况下这种组合应该有效。它实际上似乎可以发挥作用。

标签: java json dictionary jackson serialization


【解决方案1】:

您是否尝试过使用@JsonSerialize(using = SomeKeySerializer.class) 而不是keyUsing

Doc for using() 说:

用于序列化关联值的序列化器类。

...while for keyUsing 你得到:

用于序列化带注释属性的 Map 键的序列化器类

我自己测试了一下,效果很好……

public class Demo {

  public static class Container {

    private final Map<String, String> data = new HashMap<>();

    @JsonValue
    @JsonSerialize(using = SomeKeySerializer.class)
    public Map<String, String> data() {
      return data;
    }
  }

  public static class SomeKeySerializer extends JsonSerializer<Map> {

    @Override
    public void serialize(Map value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
      jgen.writeStartObject();
      jgen.writeObjectField("aKeyInTheMap", "theValueForThatKey");
      jgen.writeEndObject();
    }
  }

  public static void main(String[] args) throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
    String s = objectMapper.writeValueAsString(new Container());
    System.out.println(s);
  }
}

这是我不使用com.fasterxml.jackson.annotation.JsonValue时的输出

{
  "data" : {
    "aKeyInTheMap" : "theValueForThatKey"
  }
}

这是我使用com.fasterxml.jackson.annotation.JsonValue时的输出

{
  "aKeyInTheMap" : "theValueForThatKey"
}

【讨论】:

  • 谢谢。这确实有效,但感觉不对。我们不是简单地用我们的自定义序列化器替换默认的地图序列化器吗?
  • 我想是的......如果工程中有任何“形而上学”的空间:)
  • 我也偶然发现了JsonAnyGetter doesn't work with JsonSerialize (except with keyUsing),它可能适用于您的特定问题,但是它涉及的方法略有不同,因为它使用com.fasterxml.jackson.databind.util.Converter而不是com.fasterxml.jackson.databind.JsonSerializer...
  • 谢谢,我也看到了。这个例子不包括@JsonValue。我会等待其他解决方案,同时继续尝试。用我们自己的序列化程序替换所有内容似乎有点过头了。
  • 我认为@JsonValue的范围非常有限。我在这里添加了另一个类似的问题:stackoverflow.com/questions/33056520/….
【解决方案2】:

这种组合似乎可以满足您的要求:制作一个转换器以从容器中提取 Map,并将 @JsonValue 添加到 SomeKey 本身以对其进行序列化:

@JsonSerialize(converter = ContainerToMap.class)
public class ContainerWithFieldData {
    private final Map<SomeKey, Object> data;

    public ContainerWithFieldData(Map<SomeKey, Object> data) {
        this.data = data;
    }
}

public static final class SomeKey {
    public final String key;

    public SomeKey(String key) {
        this.key = key;
    }

    @JsonValue
    public String toJsonValue() {
        return "key:" + key;
    }

    @Override
    public String toString() {
        return "SomeKey:" + key;
    }
}

public static final class ContainerToMap extends StdConverter<ContainerWithFieldData, Map<SomeKey, Object>> {
    @Override
    public Map<SomeKey, Object> convert(ContainerWithFieldData value) {
        return value.data;
    }
}

@Test
public void serialize_container_with_custom_keys_in_field_map() throws Exception {
    ObjectMapper mapper = new ObjectMapper();
    assertThat(
            mapper.writeValueAsString(new ContainerWithFieldData(ImmutableMap.of(new SomeKey("key1"), "value1"))),
            equivalentTo("{ 'key:key1' : 'value1' }"));
}

我根本无法轻松地将 Container 的访问器方法注释到 DTRT,而不是与 @JsonValue 结合使用。鉴于容器上的 @JsonValue 基本上无论如何都指定了一个转换器(通过调用带注释的方法来实现),这实际上就是您所追求的,尽管不像看起来那么令人愉快。 (用 Jackson 2.6.2 试过)

(我从中学到的一点:密钥序列化器不像普通的序列化器,尽管它们实现 JsonSerializer 的方式相同。例如,它们需要在 JsonGenerator 上调用 writeFieldName,而不是 writeString。在反序列化方面,区别在 JsonDeserializer 和 KeyDeserializer 之间进行了说明,但不是在序列化方面。您可以使用 @JsonValue 从 SomeKey 制作密钥序列化程序,但不能通过使用 @JsonSerialize(using=...) 注释 SomeKey,这让我很惊讶)。

【讨论】:

  • 谢谢,这更有意义。我会将我的问题报告给 Jackson 问题跟踪器,因为我们正在解决@JsonValue 的限制以及我认为的其他注释。
  • 关于您的编辑:确实!我注意到了同样的事情。到目前为止,我不知道为什么没有KeySerializerKeyDeserializer 的倒数。
  • 我可以帮助解决这个谜团——在添加自定义密钥处理程序时,可以将JsonSerializer 用于密钥序列化程序(可以以相同的方式调用),但同样不是JsonDeserializer 为真。为了避免添加许多新类,只添加了一个。回想起来,添加KeySerializer 可能更有意义,只是为了防止意外。
  • +1 表示:“您可以使用 @JsonValue 从 SomeKey 制作密钥序列化程序,但不能使用 @JsonSerialize 注释 SomeKey(使用=...)
猜你喜欢
  • 2023-04-03
  • 2017-05-03
  • 2016-09-25
  • 2020-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-05
相关资源
最近更新 更多