【问题标题】:Forcing Jackson to deserialize to specific primitive type强制杰克逊反序列化为特定的原始类型
【发布时间】:2011-11-11 02:45:22
【问题描述】:

我正在使用 Jackson 1.8.3 将以下域对象序列化和反序列化为 JSON

public class Node {
    private String key;
    private Object value;
    private List<Node> children = new ArrayList<Node>();
    /* getters and setters omitted for brevity */
}

然后使用以下代码对对象进行序列化和反序列化

ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(destination, rootNode);

然后用反序列化

mapper.readValue(destination, Node.class);

对象的原始值是字符串、双精度、长整型或布尔值。但是,在序列化和反序列化期间,Jackson 将 Long 值(例如 4)转换为整数。

如何“强制”Jackson 将数字非十进制值反序列化为 Long 而不是 Integer?

【问题讨论】:

  • 请注意,您可能不会完全反序列化 Javascript 中的大长值,因为 Javascript 数字始终是 64 位浮点数,(仅)52 位尾数。

标签: java json jackson


【解决方案1】:

Jackson 2.6 中有一个专门针对这种情况的新功能:

将 ObjectMapper 配置为使用 DeserializationFeature.USE_LONG_FOR_INTS

https://github.com/FasterXML/jackson-databind/issues/504

cowtowncoder 于 2015 年 5 月 19 日推送了一个关闭此问题的提交 修复 #504 和 #797

【讨论】:

  • 这是最简单的解决方案,因为它可以反序列化已经是 json 字符串格式的数据。谢谢。
【解决方案2】:

如果类型被声明为 java.lang.Object,Jackson 使用“自然”映射,如果值适合 32 位,则使用整数。除了自定义处理程序之外,您还必须强制包含类型信息(通过在字段/getter 旁边添加 @JsonTypeInfo;或启用所谓的“默认类型”)。

【讨论】:

  • 这不是很令人满意的行为。这意味着任何时候你反序列化一个无类型的数字,你总是必须转换它并调用 longValue() 或 intValue()。如果杰克逊有办法将所有数字强制为 Long,那就太好了;至少行为是可以预测的。
  • @stickfigure 这不会更好——记住 JSON 允许值大于 64 位的值,所以你仍然可以得到 BigInteger(顺便说一下,有 DeserializationFeature.USE_BIG_INTEGER_FOR_INTS强制将所有整数转换为BigInteger)。但是,如果您需要任何特定的强制规则,您可能希望为 java.lang.Object 编写自定义反序列化程序并以您想要的方式 100% 获取规则——每个人都有自己的偏好。
  • 虽然这在理论上可能是正确的,但在实践中并不常见。大多数 Java 应用程序不使用 BigInteger,并且大多数 Java 应用程序确实使用 Long 作为数字 id。这意味着每当您遇到无类型的情况(例如地图)时,您都需要编写像((Number)map.get("key")).longValue() 这样的代码。如果您担心空检查,则代码会更加乏味。我在 jackson databinding git repo 中打开了一个功能请求。
  • 顺便说一句。我认为此解决方案仅在您能够重新序列化所有遇到问题的数据时才有效。
【解决方案3】:

我最终创建了一个自定义反序列化器,因为在我的应用程序逻辑中只有四种不同类型的值(DoubleLongIntegerString)。

我不确定这是否是最好的解决方案,但它现在有效。

public class MyDeserializer extends JsonDeserializer<Object> {

@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt)
        throws IOException, JsonProcessingException {
    try {
        Long l = Long.valueOf(p.getText());
        return l;
    } catch (NumberFormatException nfe) {
      // Not a Long
    }
    try {
      Double d = Double.valueOf(p.getText());
      return d;
    } catch (NumberFormatException nfe) {
      // Not a Double
    }
    if ("TRUE".equalsIgnoreCase(p.getText())
          || "FALSE".equalsIgnoreCase(p.getText())) {
      // Looks like a boolean
      return Boolean.valueOf(p.getText());
    }
    return String.valueOf(p.getText());
  }
}

【讨论】:

    【解决方案4】:

    我使用了类似下面的方法来解决这个问题。

    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Message {
        public Long ID;
    
        @JsonCreator
        private Message(Map<String,Object> properties) {
            try {
                this.ID = (Long) properties.get("id");
            } catch (ClassCastException e) {
                this.ID = ((Integer) properties.get("id")).longValue();
            }
        }
    }
    

    【讨论】:

    • 这将抛出 ClassCastExcpetion (您试图将其转换为 long;除非您的值真的大于 Integer 的最大值。
    • @TyagiAkhilesh 你确定吗?在我看来不太可能。
    • 试一试...至少对我来说是这样。当我在测试用例中有一个 json 字符串并尝试创建对象时,它确实给出了该异常。
    • 等一下。不过,您上面的代码确实可以正常工作,因为您正在捕捉......对不起..我强调铸造并不是最好的方法。更好地使用covertValue。
    • 更好是主观的。
    【解决方案5】:

    就我而言,我不想对 ObjectMapper 使用 DeserializationFeature.USE_LONG_FOR_INTS,因为它会影响所有项目。我使用了下一个解决方案:使用自定义反序列化器:

    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.ObjectCodec;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.JsonNode;
    
    import java.io.IOException;
    
    public class LongInsteadOfIntegerDeserializer extends JsonDeserializer<Object> {
    
        @Override
        public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
            ObjectCodec codec = jsonParser.getCodec();
            JsonNode jsonNode = codec.readTree(jsonParser);
            if (jsonNode.isInt()) {
                return jsonNode.asLong();
            }
            return codec.treeToValue(jsonNode, Object.class);
        }
    }
    

    并将其添加到 Object 类型的字段中: 公共类 SomeTOWithObjectField {

        //...  other fields
    
        @JsonDeserialize(using = LongInsteadOfIntegerDeserializer.class)
        private Object value;
    
        //...  other fields
    }
    

    它将整数反序列化为 long,但其他类型(如 String、boolean、double 等)被反序列化,因为它们应该是默认的。

    【讨论】:

      【解决方案6】:

      如果你想将一个原语包装到特定的类中,你可以按照(Kotlin 中的示例):

      data class Age(
          @JsonValue
          val value: Int
      )
      

      现在,您的 Int 原语将被解析为 Age 类,反之亦然 - Age 类被解析为 Int 原语。

      【讨论】:

        【解决方案7】:

        在 jackson 2 中,我们可以使用TypeReference 来详细指定泛型类型。 readValue() 有一个重载方法,它将 TypeReference 作为第二个参数:

        readValue([File|String|etc], com.fasterxml.jackson.core.type.TypeReference))

        如果您想获得Long 的列表而不是Integer,您可以执行以下操作。

        ObjectMapper mapper = new ObjectMapper();
        TypeReference ref = new TypeReference<List<Integer>>() { };
        List<Integer> list = mapper.readValue(<jsonString>, ref);
        

        这也适用于地图:

        TypeReference ref = new TypeReference<Map<String,Long>>() { };
        Map<String, Long> map = mapper.readValue(<jsonString>, ref);
        

        在您的情况下,您可以将您的类转换为通用类。即Node&lt;T&gt;。在创建节点的时候,按照Node&lt;String/Integer/etc&gt;的方式做,并使用类型引用来读取值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-07
          • 2016-11-11
          • 2014-06-08
          相关资源
          最近更新 更多