Skip to content

Commit

Permalink
Fix numeric conversion to Long when serializing numbers as JsonElemen…
Browse files Browse the repository at this point in the history
…t tree
  • Loading branch information
Marcono1234 committed Feb 22, 2025
1 parent 87d30c0 commit 1724ced
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 6 deletions.
2 changes: 1 addition & 1 deletion gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ public void write(JsonWriter out, Number value) throws IOException {
}
float floatValue = value.floatValue();
checkValidFloatingPoint(floatValue);
// For backward compatibility don't call `JsonWriter.value(float)` because that method has
// For backward compatibility don't call `JsonWriter#value(float)` because that method has
// been newly added and not all custom JsonWriter implementations might override it yet
Number floatNumber = value instanceof Float ? value : floatValue;
out.value(floatNumber);
Expand Down
24 changes: 19 additions & 5 deletions gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,9 @@ public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.byteValue());
// Always wrap as Number; don't use `JsonWriter#value(long)`, which converts number
Number byteNumber = value instanceof Byte ? value : value.byteValue();
out.value(byteNumber);
}
}
};
Expand Down Expand Up @@ -245,7 +247,9 @@ public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.shortValue());
// Always wrap as Number; don't use `JsonWriter#value(long)`, which converts number
Number shortNumber = value instanceof Short ? value : value.shortValue();
out.value(shortNumber);
}
}
};
Expand Down Expand Up @@ -273,7 +277,9 @@ public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
} else {
out.value(value.intValue());
// Always wrap as Number; don't use `JsonWriter#value(long)`, which converts number
Number intNumber = value instanceof Integer ? value : value.intValue();
out.value(intNumber);
}
}
};
Expand Down Expand Up @@ -368,7 +374,10 @@ public Number read(JsonReader in) throws IOException {
public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
} else if (value instanceof Long) {
out.value(value);
} else {
// Only perform unwrapping if numeric conversion is necessary
out.value(value.longValue());
}
}
Expand All @@ -389,11 +398,13 @@ public Number read(JsonReader in) throws IOException {
public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
} else if (value instanceof Float) {
out.value(value);
} else {
// For backward compatibility don't call `JsonWriter.value(float)` because that method
// For backward compatibility don't call `JsonWriter#value(float)` because that method
// has been newly added and not all custom JsonWriter implementations might override
// it yet
Number floatNumber = value instanceof Float ? value : value.floatValue();
Number floatNumber = value.floatValue();
out.value(floatNumber);
}
}
Expand All @@ -414,7 +425,10 @@ public Number read(JsonReader in) throws IOException {
public void write(JsonWriter out, Number value) throws IOException {
if (value == null) {
out.nullValue();
} else if (value instanceof Double) {
out.value(value);
} else {
// Only perform unwrapping if numeric conversion is necessary
out.value(value.doubleValue());
}
}
Expand Down
80 changes: 80 additions & 0 deletions gson/src/test/java/com/google/gson/functional/JsonTreeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@
import static com.google.common.truth.Truth.assertThat;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.common.TestTypes.BagOfPrimitives;
import com.google.gson.internal.LazilyParsedNumber;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Before;
import org.junit.Test;

Expand Down Expand Up @@ -88,6 +96,68 @@ public void testJsonTreeNull() {
assertThat(jsonElement.has("stringValue")).isFalse();
}

@Test
public void testToJsonTreeNumbers() {
List<Number> numbers =
List.of(
(byte) 1,
(short) 2,
3,
4L,
5f,
6d,
BigInteger.valueOf(7),
new BigDecimal(8),
new LazilyParsedNumber("9"));

Gson gsonSpecialFloats = new GsonBuilder().serializeSpecialFloatingPointValues().create();

for (Number number : numbers) {
JsonElement json = gson.toJsonTree(number);
assertIsNumber(json, number);

json = gsonSpecialFloats.toJsonTree(number);
assertIsNumber(json, number);
}
}

/**
* Tests {@link Gson#toJsonTree(Object)} with {@link AtomicInteger} and {@link AtomicLong}, which
* should be serialized as non-atomic (and non-mutable) numbers.
*/
@Test
public void testToJsonTreeAtomicNumbers() {
JsonElement json = gson.toJsonTree(new AtomicInteger(1));
// Current implementation converts int to long, because there is only `JsonWriter#value(long)`
assertIsNumber(json, 1L);

json = gson.toJsonTree(new AtomicLong(1));
assertIsNumber(json, 1L);
}

/** Tests numeric conversion when using {@link Gson#toJsonTree(Object, Type)}. */
@Test
public void testToJsonTreeNumberConversion() {
JsonElement json = gson.toJsonTree(1.5f, int.class);
assertIsNumber(json, 1);

json = gson.toJsonTree(500, byte.class);
assertIsNumber(json, (byte) -12);

json = gson.toJsonTree(1, float.class);
assertIsNumber(json, 1f);

json = gson.toJsonTree(1, double.class);
assertIsNumber(json, 1d);

Gson gsonSpecialFloats = new GsonBuilder().serializeSpecialFloatingPointValues().create();
json = gsonSpecialFloats.toJsonTree(1, float.class);
assertIsNumber(json, 1f);

json = gsonSpecialFloats.toJsonTree(1, double.class);
assertIsNumber(json, 1d);
}

private static void assertContains(JsonObject json, JsonPrimitive child) {
for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
JsonElement node = entry.getValue();
Expand All @@ -100,6 +170,16 @@ private static void assertContains(JsonObject json, JsonPrimitive child) {
throw new AssertionError("Does not contain " + child);
}

private static void assertIsNumber(JsonElement json, Number expectedNumber) {
assertThat(json).isInstanceOf(JsonPrimitive.class);
JsonPrimitive jsonPrimitive = (JsonPrimitive) json;
assertThat(jsonPrimitive.isNumber()).isTrue();
Number number = jsonPrimitive.getAsNumber();
assertThat(number).isEqualTo(expectedNumber);
// Explicitly check class because Truth's `isEqualTo` is lenient for numeric values
assertThat(number.getClass()).isEqualTo(expectedNumber.getClass());
}

private static class SubTypeOfBagOfPrimitives extends BagOfPrimitives {
@SuppressWarnings("unused")
float f = 1.2F;
Expand Down

0 comments on commit 1724ced

Please sign in to comment.