Принуждение Джексона к десериализации к определенному примитивному типу

Я сериализую и десериализую следующий объект домена в JSON, используя 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 */
}

Затем объект сериализуется и десериализуется с использованием следующего кода

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

И затем более поздняя десериализация с помощью

mapper.readValue(destination, Node.class);

Исходными значениями объекта являются строки, парные разряды, длинные или булевы. Однако во время сериализации и десериализации Джексон преобразует длинные значения (например, 4) в целые числа.

Как я могу заставить "Джексона" десериализовать числовые не десятичные значения в Long вместо Integer?

Ответ 1

Если тип объявлен как java.lang.Object, Jackson использует "естественное" отображение, которое использует Integer, если значение соответствует 32 бит. Помимо пользовательских обработчиков вам придется принудительно включить информацию о типе (либо добавив @JsonTypeInfo рядом с полем/получателем, либо включив так называемую "типизацию по умолчанию" ).

Ответ 2

В этом случае для Jackson 2.6 есть новая функция:

настройте ObjectMapper для использования DeserializationFeature.USE_LONG_FOR_INTS

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

cortowncoder нажал на фиксацию, которая закрыла эту проблему 19 мая 2015 года   Исправить # 504 и # 797

Ответ 3

Я закончил создание настраиваемого десериализатора, так как в моей логике приложения есть только четыре разных типа для значений (Double, Long, Integer и String).

Я не уверен, что это наилучшее решение, но оно работает пока.

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

Ответ 5

В моем случае я не хотел использовать DeserializationFeature.USE_LONG_FOR_INTS для ObjectMapper, потому что это повлияет на весь проект. Я использовал следующее решение: использовать собственный десериализатор:

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: public class 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

В Джексоне 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);