Forcing Jackson to deserialize to specific primiti

2020-02-05 07:08发布

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?

7条回答
▲ chillily
2楼-- · 2020-02-05 07:13

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());
  }
}
查看更多
戒情不戒烟
3楼-- · 2020-02-05 07:17

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.

查看更多
Emotional °昔
4楼-- · 2020-02-05 07:25

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.

查看更多
倾城 Initia
5楼-- · 2020-02-05 07:26

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").

查看更多
何必那么认真
6楼-- · 2020-02-05 07:30

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.

查看更多
smile是对你的礼貌
7楼-- · 2020-02-05 07:31

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

查看更多
登录 后发表回答