Marcus Junius Brutus Marcus Junius Brutus - 7 days ago 6
Java Question

JSON deserialization: java.lang.Double cannot be cast to java.lang.Long

The below works and prints 42 on the console:

long l = (new Gson()).fromJson("42", Long.class);
System.out.printf("[%d]\n", l);


The below also works and prints the same:

final Type TYPE = new TypeToken<Long>() {}.getType();
long l = (new Gson()).fromJson("42", TYPE);


… however the below doesn't work:

private static <T> T fromJSON(String json, Class<T> klass) {
final Type TYPE = new TypeToken<T>() {}.getType();
T rv = (new Gson()).fromJson(json, TYPE);
return rv;
}

public static void main(String args[]) throws Exception {
long l = fromJSON("42", Long.class); // line where the exception is thrown
}


… the above throws the following exception:

Exception in thread "main" java.lang.ClassCastException: java.lang.Double
cannot be cast to java.lang.Long


… at the line marked in the source.

Full trace is very shallow, just this one line:

[java] Exception in thread "main" java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Long
[java] at FooMain.main(FooMain.java:18)


Why is this happening and why wasn't the problem detected at compile time ?

I've tested this with gson 2.8.0.

I think this may have to do with the generics type information being erased at runtime, so maybe one can't construct a
TypeToken
at run-time based on a supplied generic type.

Upon further experimentation it turns out that the exception I get is identical as if I were doing:

private static Object fromJSON(String json) {
final Type TYPE = new TypeToken<Object>() {}.getType();
Object rv = (new Gson()).fromJson(json, TYPE);
return rv;
}

public static void main(String args[]) throws Exception {
long l = (long) fromJSON("42");
}


… the above produces the exact same trace at runtime which is consistent with a type erasure explanation. So I guess we'll just have to wait till Java 9.

Answer

The short answer is "type erasure".

The longer answer is that the generic version of the code - the new TypeToken<T>() call - does not record a different type value per call. The value of T is used only at compile time, is not available at run time, and there's only one run time version of the code which has to handle every value that could be passed to it.

The result of this is that Gson ends up trying to parse a value of type "T", and has no way of knowing that T is actually Long. So it guesses based on the contents, the contents are a number, and it goes with the numeric type with the broadest range of possible values - Double.

Comments