| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
Sorry, something went wrong.
📲 Install BuildsAndroid
|
Sorry, something went wrong.
Performance metrics 🚀
Baseline results on branch: mainStartup times
App size
Previous results on branch: no/java-536-json-number-parsingStartup times
App size
|
Sorry, something went wrong.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0998a8a. Configure here.
Sorry, something went wrong.
There was a problem hiding this comment.
pre-approving, but I'd double-check the bot comment
Sorry, something went wrong.
JsonObjectDeserializer typed numbers by calling nextInt() and catching the NumberFormatException it throws for every non-integer value, then falling back to nextDouble(). For payloads full of floating-point values (timestamps, measurements) this threw and filled a stack trace on nearly every number, dominating the cost of deserializing arbitrary objects. Parse the value as a double once and narrow it back to an int only when it is integral and fits, which avoids the throws. Return types are unchanged (Integer for values that fit an int, Double otherwise), so callers that read integers out of the generic object tree are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
📜 Description
JsonObjectDeserializer.nextNumber() (used when deserializing arbitrary/unknown JSON into a generic Map/List tree) decided whether a JSON number was an int or a double by calling reader.nextInt() and catching the NumberFormatException it throws for every non-integer value, then falling back to reader.nextDouble(). Because NumberFormatException fills in a stack trace, every floating-point value (timestamps, measurements, …) paid the cost of a thrown exception.
This replaces the exception-driven typing by reading the literal as a double once and narrowing it back to an int only when the value is integral and fits:
Return types are intentionally unchanged — Integer for values that are integral and fit an int, Double otherwise (fractional or out of int range). This matters: callers read integers back out of the generic tree and cast them (e.g. ReplayRecording casts rrweb source/type values to Integer/int), and defined fields such as MeasurementValue.value and context values like thread.id would otherwise change on the wire (4 → 4.0). Only the internal path to those same results changes; no exceptions are thrown.
Note that reader.nextDouble() is used rather than Double.parseDouble(reader.nextString()): it reuses the reader's already-buffered numeric token instead of materializing a String and re-parsing it, and it keeps the reader's non-finite guard, so a literal that overflows to infinity (1e400) is still rejected as malformed JSON instead of being stored as Infinity. (NaN/Infinity literals never reach this method — the reader tokenizes them as STRING, not NUMBER, even in lenient mode.)
For context: Gson and Moshi both return Double for all numbers when deserializing arbitrary JSON. We deliberately do not match that here, because the generic tree feeds code that depends on the int/double distinction.
One deliberate narrowing versus the old code: the previous reader.nextLong() fallback is gone, so nextNumber() no longer returns Long. Integers beyond int range were already returned as Double by the old path (nextInt() failed, nextDouble() then succeeded), so this only removes a branch that was unreachable for well-formed numbers; values above 2^53 still lose precision exactly as before.
💡 Motivation and Context
Part of JAVA-536 (optimize vendored libraries for startup performance). While benchmarking whether the vendored gson JSON streaming code should be replaced with moshi, this exception-based number typing showed up as a self-inflicted cost. The moshi comparison itself concluded a swap is not worthwhile (the vendored gson engine is as fast or faster, and moshi would add Okio + churn ~80 files); full data is in JAVA-536.
📈 Benchmark
Parsing one sentry_event.json into a generic object tree, median ms per 1000 iterations (desktop JVM, 5 warmup rounds, median of 10):
Removing the exceptions roughly halves the number-typing work in isolation. End-to-end the win is more modest (~5%), because JsonObjectDeserializer's token-stack allocations dominate that path; payloads with many floating-point values (e.g. profiling measurements) benefit more.
Numbers were measured with a local micro-benchmark (not committed, to keep this PR to the fix + tests); the before/after was obtained by stashing the change and re-running. Methodology: preload the payload into memory, parse it nextObjectOrNull ×1000 per timed run, 2–5 warmup rounds discarded, report the median. The benchmark measured the Double.parseDouble(nextString()) variant; the committed nextDouble() version avoids an additional String allocation and re-parse per number, so it should be at least as fast. Full moshi-vs-gson comparison data is in JAVA-536.
💚 How did you test it?
📝 Checklist