Peders
Peders

Reputation: 556

Forcing Jackson to deserialize to specific primitive type

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?

Upvotes: 32

Views: 62276

Answers (7)

Imesha Sudasingha
Imesha Sudasingha

Reputation: 3570

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.

Upvotes: 0

Pavel Shorokhov
Pavel Shorokhov

Reputation: 4974

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.

Upvotes: 0

Oleksandr Tarasenko
Oleksandr Tarasenko

Reputation: 597

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.

Upvotes: 2

Fabian Lange
Fabian Lange

Reputation: 1856

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

Upvotes: 45

ostergaard
ostergaard

Reputation: 3507

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

Upvotes: 2

StaxMan
StaxMan

Reputation: 116472

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

Upvotes: 17

Peders
Peders

Reputation: 556

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

Upvotes: 4

Related Questions