Reputation: 141
I'm using Jackson 2.4 to convert POJOs to/from Maps. I wrote a small test program as below.
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;
public class TestObjectMapper {
private Object byteVal;
private Object shortVal;
private Object intVal;
private Object longVal;
private Object floatVal;
private Object doubleVal;
public TestObjectMapper() {
byteVal = (byte) 127;
shortVal = (short) 255;
intVal = 10;
longVal = 20L;
floatVal = 1.2F;
doubleVal = 1.4;
System.out.println("Constructor");
System.out.println("byteVal.getClass() = " + byteVal.getClass());
System.out.println("shortVal.getClass() = " + shortVal.getClass());
System.out.println("intVal.getClass() = " + intVal.getClass());
System.out.println("longVal.getClass() = " + longVal.getClass());
System.out.println("floatVal.getClass() = " + floatVal.getClass());
System.out.println("doubleVal.getClass() = " + doubleVal.getClass());
System.out.println();
}
public Object getByteVal() {
return byteVal;
}
public Object getShortVal() {
return shortVal;
}
public Object getIntVal() {
return intVal;
}
public Object getLongVal() {
return longVal;
}
public Object getFloatVal() {
return floatVal;
}
public Object getDoubleVal() {
return doubleVal;
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
TestObjectMapper t = new TestObjectMapper();
Map map = mapper.convertValue(t, Map.class);
System.out.println("map = " + map);
System.out.println();
for (Object key : map.keySet()) {
System.out.format("map.get(\"%s\").getClass() = %s\n", key, map.get(key).getClass());
}
String k = "byteVal";
System.out.format("((Integer) map.get(\"%s\")).byteValue() = %d\n",
k, ((Integer) map.get(k)).byteValue());
k = "floatVal";
System.out.format("((Double) map.get(\"%s\")).floatValue() = %f\n",
k, ((Double) map.get(k)).floatValue());
}
}
which generates the following output:
Constructor
byteVal.getClass() = class java.lang.Byte
shortVal.getClass() = class java.lang.Short
intVal.getClass() = class java.lang.Integer
longVal.getClass() = class java.lang.Long
floatVal.getClass() = class java.lang.Float
doubleVal.getClass() = class java.lang.Double
map = {byteVal=127, shortVal=255, intVal=10, longVal=20, floatVal=1.2000000476837158, doubleVal=1.4}
map.get("byteVal").getClass() = class java.lang.Integer
map.get("shortVal").getClass() = class java.lang.Short
map.get("intVal").getClass() = class java.lang.Integer
map.get("longVal").getClass() = class java.lang.Long
map.get("floatVal").getClass() = class java.lang.Double
map.get("doubleVal").getClass() = class java.lang.Double
((Integer) map.get("byteVal")).byteValue() = 127
((Double) map.get("floatVal")).floatValue() = 1.200000
Why is the mapping of types correct in some cases but not in others? Is there a way of controlling this without doing any changes to my classes?
Upvotes: 1
Views: 6294
Reputation: 8201
Why is the mapping of types correct in some cases but not in others?
This is an expected behavior from Jackson
. If you take account of data types supported by JSON
, which are as below, it makes complete sense for Jackson to convert the data types of values accordingly.
Number
— a signed decimal number that may contain a fractional part and may use exponential E notation. JSON does not allow non-numbers like NaN, nor does it make any distinction between integer and floating-point. (Even though JavaScript uses a double-precision floating-point format for all its numeric values, other languages implementing JSON may encode numbers differently)String
— a sequence of zero or more Unicode characters, though characters outside the BMP must be represented as a surrogate pair. Strings are delimited with double-quotation marks and support a backslash escaping syntax.Boolean
— either of the values true or falseArray
— an ordered list of zero or more values, each of which may be of any type. Arrays use square bracket notation with elements being comma-separated.Object
— an unordered associative array (name/value pairs). Objects are delimited with curly brackets and use commas to separate each pair, while within each pair the colon ':' character separates the key or name from its value. All keys must be strings and should be distinct from each other within that object.null
— An empty value, using the word nullIs there a way of controlling this without doing any changes to my classes?
It can be controlled while Jackson is used to deserialization but not at the time of serialization. Following Stack Overflow answer may be of some help to you.
Java Jackson - prevent float to int conversion when deserializing
UPDATE
In order to convert one object to another, ObjectMapper
first serializes the source object into a JsonParser
object and then deserializes this JsonParser
object into the destination type object. Hence, it makes this behavior of ObjectMapper
quite obvious.
/
**
* Actual conversion implementation: instead of using existing read
* and write methods, much of code is inlined. Reason for this is
* that we must avoid wrapping/unwrapping both for efficiency and
* for correctness. If wrapping/unwrapping is actually desired,
* caller must use explicit <code>writeValue</code> and
* <code>readValue</code> methods.
*/
protected Object _convert(Object fromValue, JavaType toValueType)
throws IllegalArgumentException
{
// sanity check for null first:
if (fromValue == null) return null;
/* Then use TokenBuffer, which is a JsonGenerator:
* (see [JACKSON-175])
*/
TokenBuffer buf = new TokenBuffer(this);
try {
// inlined 'writeValue' with minor changes:
// first: disable wrapping when writing
SerializationConfig config = getSerializationConfig().without(SerializationFeature.WRAP_ROOT_VALUE);
// no need to check for closing of TokenBuffer
_serializerProvider(config).serializeValue(buf, fromValue);
// then matching read, inlined 'readValue' with minor mods:
final JsonParser jp = buf.asParser();
Object result;
// ok to pass in existing feature flags; unwrapping handled by mapper
final DeserializationConfig deserConfig = getDeserializationConfig();
JsonToken t = _initForReading(jp);
if (t == JsonToken.VALUE_NULL) {
DeserializationContext ctxt = createDeserializationContext(jp, deserConfig);
result = _findRootDeserializer(ctxt, toValueType).getNullValue();
} else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
result = null;
} else { // pointing to event other than null
DeserializationContext ctxt = createDeserializationContext(jp, deserConfig);
JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, toValueType);
// note: no handling of unwarpping
result = deser.deserialize(jp, ctxt);
}
jp.close();
return result;
} catch (IOException e) { // should not occur, no real i/o...
throw new IllegalArgumentException(e.getMessage(), e);
}
}
Upvotes: 3