diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index a06f290747..5fb67d1c32 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -1195,7 +1195,7 @@ public T fromJson(JsonElement json, TypeToken typeOfT) throws JsonSyntaxE private static void assertFullConsumption(Object obj, JsonReader reader) { try { - if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) { + if (reader.peek() != JsonToken.END_DOCUMENT) { throw new JsonSyntaxException("JSON document was not fully consumed."); } } catch (MalformedJsonException e) { diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java index d56da87a48..b2fafdd8d6 100644 --- a/gson/src/main/java/com/google/gson/JsonParser.java +++ b/gson/src/main/java/com/google/gson/JsonParser.java @@ -108,7 +108,7 @@ public static JsonElement parseReader(Reader reader) throws JsonIOException, Jso try { JsonReader jsonReader = new JsonReader(reader); JsonElement element = parseReader(jsonReader); - if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) { + if (jsonReader.peek() != JsonToken.END_DOCUMENT) { throw new JsonSyntaxException("Did not consume the entire document."); } return element; diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java index 759310ea20..9f6c0b4d05 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java +++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java @@ -258,7 +258,29 @@ public long nextLong() throws IOException { throw new IllegalStateException( "Expected " + JsonToken.NUMBER + " but was " + token + locationString()); } - long result = ((JsonPrimitive) peekStack()).getAsLong(); + Object o = peekStack(); + long result; + if (o instanceof JsonPrimitive) { + JsonPrimitive primitive = (JsonPrimitive) o; + if (primitive.isNumber()) { + Number number = primitive.getAsNumber(); + if (number instanceof Long || number instanceof Integer) { + result = number.longValue(); + } else { + // For other number types (e.g., Double, BigDecimal), check for loss of precision + double doubleValue = number.doubleValue(); + result = (long) doubleValue; + if (result != doubleValue) { + throw new NumberFormatException( + "Expected a long but was " + number + locationString()); + } + } + } else { + result = Long.parseLong(primitive.getAsString()); + } + } else { + throw new IllegalStateException("Unexpected token: " + o); + } popStack(); if (stackSize > 0) { pathIndices[stackSize - 1]++; @@ -273,7 +295,29 @@ public int nextInt() throws IOException { throw new IllegalStateException( "Expected " + JsonToken.NUMBER + " but was " + token + locationString()); } - int result = ((JsonPrimitive) peekStack()).getAsInt(); + Object o = peekStack(); + int result; + if (o instanceof JsonPrimitive) { + JsonPrimitive primitive = (JsonPrimitive) o; + if (primitive.isNumber()) { + Number number = primitive.getAsNumber(); + if (number instanceof Integer) { + result = number.intValue(); + } else { + // For other number types (e.g., Long, Double, BigDecimal), check for loss of precision + double doubleValue = number.doubleValue(); + result = (int) doubleValue; + if (result != doubleValue) { + throw new NumberFormatException( + "Expected an int but was " + number + locationString()); + } + } + } else { + result = Integer.parseInt(primitive.getAsString()); + } + } else { + throw new IllegalStateException("Unexpected token: " + o); + } popStack(); if (stackSize > 0) { pathIndices[stackSize - 1]++; diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java index 42d4649683..f973eb8669 100644 --- a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java +++ b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeReaderTest.java @@ -199,4 +199,27 @@ public void testOverrides() { "getNestingLimit()"); MoreAsserts.assertOverridesMethods(JsonReader.class, JsonTreeReader.class, ignoredMethods); } + + // Tests for issue #2817: Inconsistency JsonReader vs JsonTreeReader about double precision loss + @Test + public void testNextIntThrowsForDouble() throws IOException { + JsonObject json = new JsonObject(); + json.addProperty("value", 42.123); + JsonTreeReader reader = new JsonTreeReader(json); + reader.beginObject(); + reader.nextName(); + NumberFormatException e = assertThrows(NumberFormatException.class, reader::nextInt); + assertThat(e).hasMessageThat().startsWith("Expected an int but was"); + } + + @Test + public void testNextLongThrowsForDouble() throws IOException { + JsonObject json = new JsonObject(); + json.addProperty("value", 42.123); + JsonTreeReader reader = new JsonTreeReader(json); + reader.beginObject(); + reader.nextName(); + NumberFormatException e = assertThrows(NumberFormatException.class, reader::nextLong); + assertThat(e).hasMessageThat().startsWith("Expected a long but was"); + } }