Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
### Performance

- Reduce the number of SDK threads: `LifecycleWatcher` now schedules the session-end task on the shared timer executor instead of creating a dedicated `java.util.Timer` thread ([#5819](https://github.com/getsentry/sentry-java/pull/5819))
- Speed up deserialization of arbitrary JSON objects by typing numbers without throwing exceptions ([#5783](https://github.com/getsentry/sentry-java/pull/5783))

## 8.50.1

Expand Down
19 changes: 9 additions & 10 deletions sentry/src/main/java/io/sentry/JsonObjectDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,17 +172,16 @@ private boolean handlePrimitive(NextValue callback) throws IOException {
}

private Object nextNumber(JsonObjectReader reader) throws IOException {
try {
return reader.nextInt();
} catch (Exception exception) {
// Need to try/fail as there are no int/double/long tokens.
// JSON has no int/double token distinction. Probing with reader.nextInt() and catching the
// NumberFormatException it throws for every non-integer (e.g. timestamps) dominated the cost of
// deserializing arbitrary objects. Read once as a double and narrow to an int only when the
// value is integral and fits, which reproduces the previous return types without any throws.
final double value = reader.nextDouble();
final int intValue = (int) value;
if (intValue == value) {
return intValue;
}
try {
return reader.nextDouble();
} catch (Exception exception) {
// Need to try/fail as there are no int/double/long tokens.
}
return reader.nextLong();
return value;
Comment thread
cursor[bot] marked this conversation as resolved.
}

private @Nullable Token getCurrentToken() {
Expand Down
50 changes: 50 additions & 0 deletions sentry/src/test/java/io/sentry/JsonObjectDeserializerTest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.sentry

import java.io.IOException
import java.io.StringReader
import java.lang.Exception
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.fail
import org.junit.Test
Expand Down Expand Up @@ -42,6 +44,54 @@ class JsonObjectDeserializerTest {
assertEquals(1.1, actual)
}

// A value that is integral and fits an int is typed as Integer (regardless of how it was
// written); anything else is a Double. This matches the behavior prior to removing the
// exception-based number typing.

@Test
fun `deserialize negative int`() {
assertEquals(-5, deserialize("-5"))
}

@Test
fun `deserialize negative double`() {
assertEquals(-3.14, deserialize("-3.14"))
}

@Test
fun `deserialize integral exponent notation as int`() {
assertEquals(100, deserialize("1e2"))
assertEquals(100, deserialize("1E2"))
}

@Test
fun `deserialize fractional exponent notation as double`() {
assertEquals(0.0025, deserialize("2.5e-3"))
}

@Test
fun `deserialize whole-valued decimal as int`() {
assertEquals(1, deserialize("1.0"))
}

@Test
fun `deserialize integer larger than int range as double`() {
assertEquals(1.0e10, deserialize("10000000000"))
assertEquals(2147483648.0, deserialize("2147483648"))
}

@Test
fun `deserialize max int as int`() {
assertEquals(Int.MAX_VALUE, deserialize("2147483647"))
}

@Test
fun `deserialize rejects literal overflowing to infinity`() {
// Strict JSON forbids non-finite numbers, so an out-of-range literal must fail rather than be
// stored as Infinity.
assertFailsWith<IOException> { deserialize("1e400") }
}

@Test
fun `deserialize array`() {
val json = "[\"a\",\"b\"]"
Expand Down
Loading