From 34378c3ea2ebc6fd19de33e8cd22651c4d66317e Mon Sep 17 00:00:00 2001 From: daguimu Date: Thu, 26 Mar 2026 09:40:37 +0800 Subject: [PATCH] fix: Preserve integral Number type in toJsonTree for byte/short/int The BYTE, SHORT, and INTEGER type adapters' write methods called value(byteValue()), value(shortValue()), and value(intValue()) respectively. Since JsonWriter only has value(long), Java auto-widened the primitive to long, causing JsonTreeWriter.value(long) to store a Long in the JsonPrimitive instead of the original Byte/Short/Integer. Fix by casting the narrowed primitive to Number, which triggers autoboxing to the correct type and resolves to value(Number) instead of value(long). Fixes #2680 --- .../gson/internal/bind/TypeAdapters.java | 6 ++--- .../google/gson/functional/PrimitiveTest.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 852ae56c5b..04acfb3022 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -214,7 +214,7 @@ public void write(JsonWriter out, Number value) throws IOException { if (value == null) { out.nullValue(); } else { - out.value(value.byteValue()); + out.value((Number) value.byteValue()); } } }; @@ -249,7 +249,7 @@ public void write(JsonWriter out, Number value) throws IOException { if (value == null) { out.nullValue(); } else { - out.value(value.shortValue()); + out.value((Number) value.shortValue()); } } }; @@ -277,7 +277,7 @@ public void write(JsonWriter out, Number value) throws IOException { if (value == null) { out.nullValue(); } else { - out.value(value.intValue()); + out.value((Number) value.intValue()); } } }; diff --git a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java index 81cd894c0f..8e9b82f4c9 100644 --- a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java +++ b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java @@ -161,6 +161,33 @@ public void testIntSerialization() { assertThat(gson.toJson(1.5, Integer.class)).isEqualTo("1"); } + /** Regression test for https://github.com/google/gson/issues/2680 */ + @Test + public void testToJsonTreePreservesIntegralType() { + // toJsonTree should preserve the Number type, not widen byte/short/int to Long + assertThat(gson.toJsonTree((byte) 1, byte.class).getAsNumber().getClass()) + .isEqualTo(Byte.class); + assertThat(gson.toJsonTree((byte) 1, Byte.class).getAsNumber().getClass()) + .isEqualTo(Byte.class); + + assertThat(gson.toJsonTree((short) 1, short.class).getAsNumber().getClass()) + .isEqualTo(Short.class); + assertThat(gson.toJsonTree((short) 1, Short.class).getAsNumber().getClass()) + .isEqualTo(Short.class); + + assertThat(gson.toJsonTree(1, int.class).getAsNumber().getClass()).isEqualTo(Integer.class); + assertThat(gson.toJsonTree(1, Integer.class).getAsNumber().getClass()).isEqualTo(Integer.class); + + // Long should remain Long + assertThat(gson.toJsonTree(1L, long.class).getAsNumber().getClass()).isEqualTo(Long.class); + assertThat(gson.toJsonTree(1L, Long.class).getAsNumber().getClass()).isEqualTo(Long.class); + + // Narrowing conversion should produce the target type + assertThat(gson.toJsonTree(1.5, Integer.class).getAsNumber().getClass()) + .isEqualTo(Integer.class); + assertThat(gson.toJsonTree(1L, Byte.class).getAsNumber().getClass()).isEqualTo(Byte.class); + } + @Test public void testLongSerialization() { assertThat(gson.toJson(1L, long.class)).isEqualTo("1");