Принуждение Джексона к десериализации к определенному примитивному типу
Я сериализую и десериализую следующий объект домена в 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);