Forcing Jackson to deserialize to specific primitive type

假如想象 提交于 2019-11-27 03:21:30

问题


I am serializing and deserializing following domain object to JSON using Jackson 1.8.3

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

Object is then serialized and deserialized using following code

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

And then later deserialized with

mapper.readValue(destination, Node.class);

The original values of the object are either Strings, Doubles, Longs or Booleans. However, during serialization and deserialization Jackson transforms Long values (such as 4) to Integers.

How can I "force" Jackson to deserialize numeric non-decimal values to Long instead of Integer?


回答1:


If type is declared as java.lang.Object, Jackson uses 'natural' mapping which uses Integer if value fits in 32 bits. Aside from custom handlers you would have to force inclusion of type information (either by adding @JsonTypeInfo next to field / getter; or by enabling so-called "default typing").




回答2:


There is a new feature in Jackson 2.6 specifically for this case:

configure the ObjectMapper to use DeserializationFeature.USE_LONG_FOR_INTS

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

cowtowncoder pushed a commit that closed this issue on May 19, 2015 Fix #504 and #797




回答3:


I ended up creating a custom deserializer, since in my application logic there are only four different types for values (Double, Long, Integer and String).

I'm not sure if this is the best possible solution but it works for now.

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:


I've used something like the below to work around this problem.

@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();
        }
    }
}



回答5:


In my case I did not want to use DeserializationFeature.USE_LONG_FOR_INTS for ObjectMapper, because it would affect all the project. I used the next solution: use a custom deserializer:

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);
    }
}

And add it to the field of type Object: public class SomeTOWithObjectField {

    //...  other fields

    @JsonDeserialize(using = LongInsteadOfIntegerDeserializer.class)
    private Object value;

    //...  other fields
}

And it deserialized integers as longs, but other types like String, boolean, double etc. were deserialized as they should be by default.




回答6:


If you want to wrap a primitive into specific class, you can do follow (example in Kotlin):

data class Age(
    @JsonValue
    val value: Int
)

And now, your Int primitives will be parsed into Age class and vice versa - Age class into Int primitive.




回答7:


In jackson 2 we can use TypeReference to specify the generic type in detail. There is and overloaded method for readValue() which takes the TypeReference as the 2nd parameter:

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

If you want to get a list of Long instead of Integer, you can do the following.

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

This works for maps as well:

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

In your case, you can convert your class to a generic one. i.e Node<T>. When creating nodes, do as Node<String/Integer/etc> And use the type reference to read the value.



来源:https://stackoverflow.com/questions/7316689/forcing-jackson-to-deserialize-to-specific-primitive-type

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!