【问题标题】:Jackson custom deserializer breaks deserialization of other fieldsJackson 自定义反序列化器打破了其他字段的反序列化
【发布时间】:2018-03-06 20:55:34
【问题描述】:

我需要将 JsonArray 反序列化为布尔值。如果数组存在且不为空,则该值应设置为“true”。 问题是,我的自定义反序列化器虽然可以正常工作,但会破坏其余字段的反序列化 - 它们被设置为 null。

对象:

private static class TestObject {
    private String name;

    @JsonProperty("arr")
    @JsonDeserialize(using = Deserializer.class)
    private Boolean exists = null;

    private Integer logins;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Boolean getExists() {
        return exists;
    }

    public void setExists(boolean exists) {
        this.exists = exists;
    }

    public Integer getLogins() {
        return logins;
    }

    public void setLogins(Integer logins) {
        this.logins = logins;
    }

    @Override
    public String toString() {
        return "TestObject{" +
                "name='" + name + '\'' +
                ", exists=" + exists +
                ", logins=" + logins +
                '}';
    }
}

反序列化器:

public class Deserializer extends JsonDeserializer<Boolean> {
    @Override
    public Boolean deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException {
        if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
            return true;
        }
        return false;
    }
}

测试

@Test
public void test() throws JsonParseException, IOException {
    Boolean result = deserialize();
}

private Boolean deserialize() throws IOException, JsonParseException,
        JsonProcessingException {
    TestObject testObject = mapper.readValue("{\n" +
                    "  \"arr\": [\n" +
                    "     {\"value\": \"New\"}\n" +
                    "  ],\n" +
                    "  \"name\": \"name\",\n" +
                    "  \"logins\": 36" +
                    "}",
                    TestObject.class);

    System.out.println(testObject.toString());

    return testObject.getExists();
}

如果我删除“arr”数组或将其移动到 Json 的底部,一切都很好。如果我把它放在顶部 - TestObject{name='null', exists=true, logins=null}

有一个类似的问题 (Jackson Custom Deserializer breaks default ones),但不幸的是它的答案为零。 由于当我重新排列 Json 时代码可以正常工作,所以看起来自定义反序列化器并非用于所有字段,而是 Jackson 在执行自定义反序列化器时停止反序列化。

【问题讨论】:

  • 我的建议是不要将数组反序列化为布尔值。将数组反序列化为数组/列表/集并让getExists 返回arr != null 或类似的。
  • @Paul 这确实有效,感谢您的想法,不知何故我没有想到这一点。问题是它看起来有点丑陋和晦涩,但如果没有其他方法我会这样做。
  • 有时您必须在两个丑陋的解决方案中选择更漂亮的一个 :) 很高兴我能提供帮助...我会让我的评论成为答案。

标签: java json serialization jackson


【解决方案1】:

您的反序列化器可能对数组的内容不感兴趣,但仍必须推进解析器上的读取标记。

你可以一次读取arr的值,根据集合的大小来决定:

@Override
public Boolean deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException {
    JsonNode node = jp.getCodec().readTree(jp);
    return node.size() != 0;
}

决定集合的 size 而不是集合的 存在 是必要的,因为您的字符串化对象包含 arr 或 Deserializer.deserialize() 是从未被执行。在这种情况下,属性exist 将是null。所以唯一可能表达不存在的语义是一个空集合。

出于好奇,我尝试了第二种更明确的方法来保持解析器正常运行。对于实际使用,上述版本绝对是更可取的。

@Override
public Boolean deserialize(JsonParser jp, DeserializationContext ctxt)
        throws IOException {
    if (jp.currentToken() == JsonToken.START_ARRAY) {
        jp.nextToken();
        int recursionLevel = 1;
        while(recursionLevel > 0) {
            switch (jp.currentToken()) {
                case START_ARRAY:
                    // just in case of nested arrays
                    recursionLevel++;
                    break;
                case END_ARRAY:
                    recursionLevel--;
                    break;
            }
            jp.nextToken();
        }
        return true;
    }
    return false;
}

【讨论】:

  • 这是正确的解决方案。 JsonDeserializer javadoc 提到您必须在从反序列化器返回之前将读取标记移动到下一个元素,以便杰克逊可以开始处理下一个元素。在我的情况下,它是一个数组,所以像我尝试过的那样移动它一次是不够的,你必须移动尽可能多的次数才能到达END_ARRAYreadTree,据我所知,javadoc 读取整个数组,构建一棵树并在一个包中推进读取标记。
【解决方案2】:

我的建议是不要将数组反序列化为布尔值。将数组反序列化为数组/列表/集,并让getExists 返回arr != null 或类似的东西。

例如,如果您要反序列化的 JSON 如下所示:

{
  "arr": [{"value": "New"}],
  "name": "name",
  "logins": 36
}

这样写你的实体:

private static class TestObject
{
  private List<Map<String, String>> arr;

  // other getters/setters removed for brevity

  public boolean getExists()
  {
    return arr !=null && !arr.isEmpty();
  }
}

【讨论】:

    【解决方案3】:

    实际上有第三种方法可以做到这一点,这适合我的情况 - JsonArray 将始终包含至少一个元素,当它没有丢失时,所以我只需要正确推进读取标记,然后返回 true:

    if (jp.getCurrentToken().equals(JsonToken.START_ARRAY)) {
        while (!jp.getCurrentToken().equals(JsonToken.END_ARRAY)){
            jp.nextToken();
        }
    }
    return true;
    

    除非你不需要数组的内容来做一些额外的工作,否则仅仅移动标记比readTree快几毫秒。

    【讨论】:

      猜你喜欢
      • 2017-09-10
      • 2016-02-12
      • 1970-01-01
      • 2019-08-10
      • 2016-02-13
      • 2011-04-12
      • 2016-01-29
      • 1970-01-01
      • 2018-07-15
      相关资源
      最近更新 更多