gson: Gson doesn't deserialise Long numbers correctly
Gson shouldn’t cast a number to a Double if a number does not have decimal digits. It’s clearly wrong.
public class GsonVsJackson {
public static void main(String[] args) throws Exception {
testGson();
System.out.println("========================");
testJackson();
}
public static void testGson() {
System.out.println("Testing Gson 2.8");
Gson gson = new Gson();
HashMap<String, Object> newTest = new HashMap<>();
newTest.put("first", 6906764140092371368L);
String jsonString = gson.toJson(newTest);
System.out.println(jsonString); // output ok: {"first":6906764140092371368}
Map<String, Object> mapFromJson = gson.fromJson(jsonString, Map.class);
Number numberFromJson = (Number) mapFromJson.get("first");
System.out.println(numberFromJson.getClass() + " = " + numberFromJson); // java.lang.Double val 6.9067641400923709E18
long longVal = numberFromJson.longValue();
System.out.println(longVal); // output rounded: 6906764140092370944
}
public static void testJackson() throws Exception {
System.out.println("Testing Jackson");
ObjectMapper jackson = new ObjectMapper();
HashMap<String, Object> newTest = new HashMap<>();
newTest.put("first", 6906764140092371368L);
String jsonString = jackson.writeValueAsString(newTest);
System.out.println(jsonString); // output ok: {"first":6906764140092371368}
Map<String, Object> mapFromJson = jackson.readValue(jsonString, Map.class);
Number numberFromJson = (Number) mapFromJson.get("first");
System.out.println(numberFromJson.getClass() + " = " + numberFromJson); // java.math.BigInteger = 6906764140092371368
long longVal = numberFromJson.longValue();
System.out.println(longVal); // output OK: 6906764140092371368
}
}
Kind Regards, Daniele
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 33
- Comments: 38 (15 by maintainers)
Links to this issue
Commits related to this issue
- Fix widget service calls with numbers (#640) (#650) * Widget service calls will fail, if a number for any field is used. Because the GSON parser will parse every integer to a double. This is especi... — committed to home-assistant/android by chriss158 4 years ago
- Switched from Gson to Jackson for AbstractParser Gson always deserializes numbers as double, which causes problems for setting parsed integer properties in a JSON object (open issue: google/gson#1084... — committed to AML14/EvoMaster by AML14 4 years ago
- Parse JSON numbers to Number type instead of Double. Currently all numbers are parsed as Double thus integers are parsed as "int.0", which is **wrong** according to JSON specification (see www.json.o... — committed to palexdev/gson by zenglian 6 years ago
- Fix widget service calls with numbers (#640) (#650) * Widget service calls will fail, if a number for any field is used. Because the GSON parser will parse every integer to a double. This is especi... — committed to rauljurado620/android-apps by rauljurado620 4 years ago
- Fix widget service calls with numbers (#640) (#650) * Widget service calls will fail, if a number for any field is used. Because the GSON parser will parse every integer to a double. This is especi... — committed to zarakarlsson628/android-gradle-common by zarakarlsson628 4 years ago
- Replace GSON with Jackson Databind due to https://github.com/google/gson/issues/1084 + Fix Lists and Sets behaving the same in JSON — committed to litetex/NewPipe by litetex a year ago
It's not a bug, JSON does not distinguish between integers or floats, and does not care the size of numbers
.Gson is created for java not for javascript. Java does care the data type and size. JSON is not used for javascript only. Hotdog is not dog, there is no need to stick to its literal meaning. Number adapter won’t work for Object type, e.g., ArrayList<Object>.
This is an old issue existed for many years. It’s easy for to fix it. I hope it will be fixed in 2018. Other json libs do not have this bug (I think it is).
It’s not a bug, and gson does it fully legit: JSON does not distinguish between integers or floats, and does not care the size of numbers. Numerics in JSON are just numbers, and
java.lang.Double
is the best and largest primitive-counterpart candidate to hold a JSON number.If you need a long value at a call-site, then just call its
longValue()
method. If, for any particular reason, you need a behavior you are talking about, then you have to implement a custom type adapter factory. Say, something like this (not sure if it’s implemented right, though):However, it can only work if your code can tell Gson to deserialize numbers. This won’t work:
But this will:
I agree with the comment above and naturally we, as people, consider 1 as integer, not as rational or real.
The same should apply for the code as well. When there is a possibility to parse 1 it should be done to the most commonly used type, namely Integer. Of course, the value can be greater then Integer.MAX_VALUE, so in this case, it could be parsed to Long. And again if it is greater then Long.MAX_Value, the result will be Double.
At least fromJson() and toJson() applied consequently on one String, should result in the same string. Now, this is not the case.
Let’s give an example: Code:
Result:
If we are after user requirements my only one was to have
json_in
andjson_out
injson_in -> Object -> json_out
conversion identical while current implementation completely breaks it.Fixed by #1290.
Thank you @lyubomyr-shaydariv , I already solved the issue by replacing Gson with Jackson. I think that the correctness of an algorithm should has an higher priority over the performance.
I wrote this post hopping to help the Gson team to improve the quality of their library.
Have a good weekend!
Kind regards, Daniele
I don’t think this is bug, but the problem is that the NumberAdapter is not replaceable as the default adapters are hardcoded in
UnmodifiableList
. Ideally you should be able to remove current NumberAdapter and use your one. In my case, I needed this specifically:To achieve so, I use reflection to replace the adapter internally - it is super-ugly and bad, but it works:
What happens if you specify types instead of using raw
ArrayList
instances? Ie. replace this:with this:
This is not a debate contest, so do not try to find my logic hole. Instead, try to find out what is the user requirements. When the input is {“a”:1}, guess user most likely wants integer or float.
I’m not saying either that is not legit or it’s a bug, i’m saying that this can be done better without loosing precision, like Jackson does 😃