Skip to content

add orjson - #6116

Open
benedikt-bartscher wants to merge 36 commits into
reflex-dev:mainfrom
benedikt-bartscher:try-orjson
Open

add orjson#6116
benedikt-bartscher wants to merge 36 commits into
reflex-dev:mainfrom
benedikt-bartscher:try-orjson

Conversation

@benedikt-bartscher

Copy link
Copy Markdown
Contributor

No description provided.

@codspeed-hq

codspeed-hq Bot commented Feb 6, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 26 untouched benchmarks
🆕 2 new benchmarks
⏩ 8 skipped benchmarks1

Performance Changes

Mode Benchmark BASE HEAD Efficiency
🆕 Simulation test_process_event_wire[large_delta] N/A 32 ms N/A
🆕 Simulation test_process_event_wire[small_delta] N/A 3.6 ms N/A

Comparing benedikt-bartscher:try-orjson (5acd310) with main (cce9772)

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review February 7, 2026 13:38
@greptile-apps

greptile-apps Bot commented Feb 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds optional orjson support for Reflex JSON handling. The main changes are:

  • Socket and upload payload serialization now use orjson helpers when available.
  • Non-finite float values are encoded with sentinels for frontend round-tripping.
  • Generated JSON and JavaScript config output uses the new JSON helpers.
  • The reflex[orjson] extra and related tests were added.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
reflex/app.py Updates socket JSON hooks and event payload decoding to use the new orjson helper functions.
packages/reflex-base/src/reflex_base/utils/format.py Adds optional orjson serialization helpers and socket-specific handling for non-finite float values.
packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Adds frontend parsing and reviving logic for Reflex sentinel values in JSON payloads.
packages/reflex-components-core/src/reflex_components_core/core/_upload.py Switches upload event decoding and streamed state updates to the new JSON helper path.

Reviews (13): Last reviewed commit: "refactor: single TypeError fallback in o..." | Re-trigger Greptile

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread reflex/app.py Outdated
@adhami3310

Copy link
Copy Markdown
Member

the issue with orjson is that we lose NaN handling which is relevant for numpy, which is why we were using JSON5 as well

@benedikt-bartscher

benedikt-bartscher commented Feb 12, 2026

Copy link
Copy Markdown
Contributor Author

Yeah, that's why i did not update it in all places

benedikt-bartscher and others added 3 commits February 24, 2026 21:41
…llback

Happy path uses native JSON.parse; the catch rewrites Python"s bare
Infinity/-Infinity/NaN tokens outside string literals before retrying,
so only payloads that actually contain specials pay the extra cost.
NaN has no JSON literal and becomes null (matches JSON.stringify).
…o null

Swap bare NaN for a sentinel string before JSON.parse and revive it back
to a real NaN so Python-side float(nan) round-trips to the frontend.
@benedikt-bartscher

Copy link
Copy Markdown
Contributor Author

related #6339

@benedikt-bartscher
benedikt-bartscher requested a review from a team as a code owner April 25, 2026 22:13
Apply orjson_dumps/orjson_loads helpers to the new reflex_base
package locations after upstream's restructuring.
@benedikt-bartscher
benedikt-bartscher marked this pull request as draft April 26, 2026 16:50
@masenf

masenf commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

now that we've eliminated JSON5 in the frontend code, I think this PR is the next step for optimizing the serialization path.

@benedikt-bartscher
benedikt-bartscher marked this pull request as ready for review July 9, 2026 21:17
@benedikt-bartscher benedikt-bartscher changed the title try orjson add orjson Jul 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 18ae582a7f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/reflex-base/src/reflex_base/.templates/web/utils/state.js Outdated
Comment thread tests/units/utils/test_format_orjson.py Outdated
FarhanAliRaza added a commit to FarhanAliRaza/reflex that referenced this pull request Jul 31, 2026
Serialization previously walked every payload with
_replace_non_finite_floats before every socket emit to protect NaN/Infinity
floats (orjson silently emits null for them) and to escape
sentinel-colliding strings. The walk dominated serialization cost (~95% on
a 290KB payload) and ran even in the stdlib fallback, making default
installs slower than before reflex-dev#6116.

Both backends now dump once and decide from the output bytes whether the
walk is needed at all:

- orjson: None, NaN and Infinity all serialize to exactly the token null,
  so output without null provably lost no non-finite floats; output without
  the sentinel prefix has no colliding strings. Only when either substring
  appears is the payload walked and re-dumped. The default= callback is
  split into a non-walking pass-1 variant and a walking pass-2 variant so
  StateUpdate-wrapped payloads keep the fast path.
- stdlib (no orjson): bare NaN/Infinity tokens are restored by the
  frontend's bare-token rewriter, so only a sentinel-prefix hit forces the
  walk. Restores default-install performance to parity with pre-reflex-dev#6116.

Frontend mirror of the same idea: passing a reviver to JSON.parse disables
the engine's native fast parser (measured 4-12x slower in Chromium), so
parseNonFiniteAwareJSON only applies the reviver when the payload actually
contains the sentinel prefix. The upload NDJSON parser gains the bare-token
rewriter retry it was missing.

Also: hoist the orjson import to module level (was per-call on the compile
hot path), make orjson_dumps fall back to stdlib for indent widths other
than 2 instead of silently coercing, and preserve kwargs in its TypeError
fallback.

Serialize-only medians vs stdlib baseline (1000x10 table delta): 1.91ms ->
431us with orjson, parity without. Browser decode of the same delta: 2.05ms
-> 506us.
benedikt-bartscher and others added 5 commits July 31, 2026 20:31
test_process_event_wire measures the full receive -> process -> serialize
round trip: raw event JSON decoded via orjson_loads (as socket.io's decoder
does), events processed through BaseStateEventProcessor with a real
StateManagerMemory, and each delta serialized through orjson_dumps_socket
the way App._setup_state wires socket.io's encoder. Parametrized with a
small (5-row) and large (500-row) table delta so JSON parse/dump cost is
part of the measured path, which no existing benchmark covered.
Serialization previously walked every payload with
_replace_non_finite_floats before every socket emit to protect NaN/Infinity
floats (orjson silently emits null for them) and to escape
sentinel-colliding strings. The walk dominated serialization cost (~95% on
a 290KB payload) and ran even in the stdlib fallback, making default
installs slower than before reflex-dev#6116.

Both backends now dump once and decide from the output bytes whether the
walk is needed at all:

- orjson: None, NaN and Infinity all serialize to exactly the token null,
  so output without null provably lost no non-finite floats; output without
  the sentinel prefix has no colliding strings. Only when either substring
  appears is the payload walked and re-dumped. The default= callback is
  split into a non-walking pass-1 variant and a walking pass-2 variant so
  StateUpdate-wrapped payloads keep the fast path.
- stdlib (no orjson): bare NaN/Infinity tokens are restored by the
  frontend's bare-token rewriter, so only a sentinel-prefix hit forces the
  walk. Restores default-install performance to parity with pre-reflex-dev#6116.

Frontend mirror of the same idea: passing a reviver to JSON.parse disables
the engine's native fast parser (measured 4-12x slower in Chromium), so
parseNonFiniteAwareJSON only applies the reviver when the payload actually
contains the sentinel prefix. The upload NDJSON parser gains the bare-token
rewriter retry it was missing.

Also: hoist the orjson import to module level (was per-call on the compile
hot path), make orjson_dumps fall back to stdlib for indent widths other
than 2 instead of silently coercing, and preserve kwargs in its TypeError
fallback.

Serialize-only medians vs stdlib baseline (1000x10 table delta): 1.91ms ->
431us with orjson, parity without. Browser decode of the same delta: 2.05ms
-> 506us.
The stdlib fallbacks (no orjson, or orjson TypeError e.g. >64-bit ints)
walked the original packet, but _replace_non_finite_floats cannot traverse
custom objects like the StateUpdate every real socket payload wraps.
Strings produced by serializers.serialize therefore shipped unescaped, and
the frontend reviver silently corrupted user data that collided with a
sentinel: "__reflex_nan__" became NaN, "__reflex_esc__x" became "x".

Route both fallbacks through _json_dumps_socket_fallback, whose re-dump
passes a walking default= callback so serializer output is escaped the
same way the orjson path's _default_walked already does. The fast path
(single dump + prefix scan) is unchanged.

Regression tests use the real wire shape ["event", StateUpdate(delta)]
and fail against the previous implementation on all three paths.
Both orjson.dumps passes fell back to the same stdlib path on TypeError;
one try block covering the whole orjson section says that once. Behavior
unchanged: any payload orjson cannot represent (e.g. int > 64-bit) takes
_json_dumps_socket_fallback regardless of which pass it surfaced in.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

16 issues found across 35 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-base/news/6116.performance.md">

<violation number="1" location="packages/reflex-base/news/6116.performance.md:1">
P3: This overstates behavior of the general JSON helpers: `json_dumps` always uses stdlib, and `orjson_dumps` converts `NaN`/`Infinity` to `null` rather than round-tripping them. Scoping the sentinel claim to `orjson_dumps_socket` avoids promising data preservation to callers of `orjson_dumps`.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/upload.js">

<violation number="1" location="packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/upload.js:3">
P3: Frontend module loading now has a direct circular dependency between `state.js` and `helpers/upload.js`, making initialization order fragile. Moving the non-finite JSON codec into a dependency-free utility (ideally exposing the complete parse-with-fallback operation) would keep both consumers acyclic.</violation>
</file>

<file name="reflex/utils/frontend_skeleton.py">

<violation number="1" location="reflex/utils/frontend_skeleton.py:421">
P2: Persisted `package.json` numeric fields above orjson's 64-bit range are silently rounded and rewritten, violating this flow's pass-through behavior and making output depend on whether optional `orjson` is installed. Retaining stdlib parsing here, or otherwise preserving out-of-range numbers exactly, would avoid corrupting user-owned fields.</violation>
</file>

<file name="packages/reflex-site-shared/src/reflex_site_shared/lib/meta/meta.py">

<violation number="1" location="packages/reflex-site-shared/src/reflex_site_shared/lib/meta/meta.py:3">
P1: Importing this meta module can now fail with `ImportError` for supported `reflex-site-shared` installations because `orjson_dumps` is new but the package does not require a `reflex-base`/`reflex` release that exports it. Please add an appropriate runtime minimum dependency (or retain a compatibility fallback) before using the symbol.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py:94">
P2: Tailwind theme values or plugin arguments containing `NaN`/`Infinity` now become `null`, silently changing the generated JavaScript configuration. Since this output is JavaScript rather than strict JSON, consider retaining stdlib `json.dumps` here or using a serializer that preserves these valid JS numeric tokens.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/vars/number.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/vars/number.py:962">
P2: With orjson installed, non-finite or float-overflowing `Decimal` literals now serialize as `null`, silently changing a number into a null value instead of rejecting its invalid numeric representation. Checking the converted float for finiteness before `orjson_dumps` would keep `LiteralNumberVar.json()` consistent with the float path.</violation>
</file>

<file name="reflex/utils/path_ops.py">

<violation number="1" location="reflex/utils/path_ops.py:248">
P2: Existing files containing `NaN` or `Infinity` can no longer be updated because the previous `json.dump` path emitted those tokens while `orjson_loads` rejects them. A stdlib-compatible fallback for these legacy files would preserve `update_json_file` behavior.</violation>

<violation number="2" location="reflex/utils/path_ops.py:254">
P2: Updates containing non-finite floats now silently persist them as `null`, losing the distinction from a real `None`; the previous serializer preserved them as `NaN`/`Infinity`. This path should retain the old semantics or explicitly reject such values instead of silently corrupting them.</violation>
</file>

<file name="reflex/utils/exec.py">

<violation number="1" location="reflex/utils/exec.py:84">
P2: Valid package.json files containing an integer outside orjson's 64-bit range now crash frontend startup/change monitoring, although the previous `json.load` accepted them. Consider retaining stdlib parsing here or adding a stdlib fallback when `orjson_loads` rejects input that `json.loads` can parse.</violation>
</file>

<file name="reflex/utils/telemetry.py">

<violation number="1" location="reflex/utils/telemetry.py:135">
P2: CPU-info caching can now fail on systems whose locale encoding cannot represent a non-ASCII CPU manufacturer/model, because `orjson_dumps` emits those characters literally while `write_text` uses the locale encoding. Writing explicit UTF-8 bytes would preserve the new serializer’s output and match the byte-based read path.</violation>
</file>

<file name="reflex/utils/prerequisites.py">

<violation number="1" location="reflex/utils/prerequisites.py:117">
P1: With the `orjson` extra installed, reading reflex.json loses precision for its 128-bit UUID-derived `project_hash`; this path can then persist the rounded float, and telemetry cannot pass it to `uuid.UUID(int=...)`. These reflex.json reads should retain stdlib `json.loads` (or use a loader that preserves arbitrary-precision integers).</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/config.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/config.py:643">
P3: `Config.json()` never benefits from orjson because every `Config` contains the unsupported `_non_default_attributes` set, so `orjson_dumps` always retries the full serialization with `json_dumps`. Consider normalizing the config/custom fields for orjson or extending the helper with the registered serializer before switching this call site.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/.templates/web/utils/state.js">

<violation number="1" location="packages/reflex-base/src/reflex_base/.templates/web/utils/state.js:493">
P2: State values from a custom `App.sio` can be corrupted: legitimate `__reflex_nan__`, `__reflex_inf__`, or `__reflex_neg_inf__` strings are revived even though that server was never configured to escape collisions. Consider configuring provided `AsyncServer` instances with the sentinel-aware serializer, or marking sentinel-encoded packets before applying this reviver.</violation>
</file>

<file name="reflex/app.py">

<violation number="1" location="reflex/app.py:1669">
P2: Compiling a stateful route with a non-ASCII dynamic argument can now fail on systems whose default text encoding cannot represent it, because `orjson_dumps` emits Unicode directly into a locale-encoded file. Opening `stateful_pages_marker` with `encoding="utf-8"` would match the UTF-8 JSON output and its byte-based reader.</violation>
</file>

<file name="packages/reflex-base/src/reflex_base/utils/format.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/utils/format.py:713">
P3: `ensure_ascii=True` is silently ignored because this guard still selects orjson, which always emits UTF-8. Falling back to `json_dumps` when the option is truthy preserves the advertised keyword behavior.</violation>

<violation number="2" location="packages/reflex-base/src/reflex_base/utils/format.py:724">
P1: Reflex serializers are bypassed whenever orjson natively supports a value: `orjson_dumps` changes datetime formatting, and both fast paths skip custom Enum serializers. Route these native types through the serializer registry or retain `json_dumps` for them so generated values and socket deltas preserve existing semantics.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

"""Meta module."""

import json
from reflex_base.utils.format import orjson_dumps

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Importing this meta module can now fail with ImportError for supported reflex-site-shared installations because orjson_dumps is new but the package does not require a reflex-base/reflex release that exports it. Please add an appropriate runtime minimum dependency (or retain a compatibility fallback) before using the symbol.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-site-shared/src/reflex_site_shared/lib/meta/meta.py, line 3:

<comment>Importing this meta module can now fail with `ImportError` for supported `reflex-site-shared` installations because `orjson_dumps` is new but the package does not require a `reflex-base`/`reflex` release that exports it. Please add an appropriate runtime minimum dependency (or retain a compatibility fallback) before using the symbol.</comment>

<file context>
@@ -1,6 +1,6 @@
 """Meta module."""
 
-import json
+from reflex_base.utils.format import orjson_dumps
 
 import reflex as rx
</file context>
Fix with cubic

@@ -6,7 +6,6 @@
import importlib

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: With the orjson extra installed, reading reflex.json loses precision for its 128-bit UUID-derived project_hash; this path can then persist the rounded float, and telemetry cannot pass it to uuid.UUID(int=...). These reflex.json reads should retain stdlib json.loads (or use a loader that preserves arbitrary-precision integers).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/utils/prerequisites.py, line 117:

<comment>With the `orjson` extra installed, reading reflex.json loses precision for its 128-bit UUID-derived `project_hash`; this path can then persist the rounded float, and telemetry cannot pass it to `uuid.UUID(int=...)`. These reflex.json reads should retain stdlib `json.loads` (or use a loader that preserves arbitrary-precision integers).</comment>

<file context>
@@ -114,7 +114,7 @@ def get_or_set_last_reflex_version_check_datetime():
         return None
     # Open and read the file
-    data = json.loads(reflex_json_file.read_text())
+    data = orjson_loads(reflex_json_file.read_bytes())
     last_version_check_datetime = data.get("last_version_check_datetime")
     if not last_version_check_datetime:
</file context>
Fix with cubic

@@ -9,6 +9,11 @@
from functools import lru_cache

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Reflex serializers are bypassed whenever orjson natively supports a value: orjson_dumps changes datetime formatting, and both fast paths skip custom Enum serializers. Route these native types through the serializer registry or retain json_dumps for them so generated values and socket deltas preserve existing semantics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/utils/format.py, line 724:

<comment>Reflex serializers are bypassed whenever orjson natively supports a value: `orjson_dumps` changes datetime formatting, and both fast paths skip custom Enum serializers. Route these native types through the serializer registry or retain `json_dumps` for them so generated values and socket deltas preserve existing semantics.</comment>

<file context>
@@ -683,6 +688,210 @@ def json_dumps(obj: Any, **kwargs) -> str:
+        option |= orjson.OPT_SORT_KEYS
+
+    try:
+        return orjson.dumps(obj, option=option or None).decode()
+    except TypeError:
+        # json_dumps handles large integers and registered custom types.
</file context>
Fix with cubic

try:
parsed = json.loads(root_package_json_path.read_text())
except (json.JSONDecodeError, OSError) as e:
parsed = orjson_loads(root_package_json_path.read_bytes())

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Persisted package.json numeric fields above orjson's 64-bit range are silently rounded and rewritten, violating this flow's pass-through behavior and making output depend on whether optional orjson is installed. Retaining stdlib parsing here, or otherwise preserving out-of-range numbers exactly, would avoid corrupting user-owned fields.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/utils/frontend_skeleton.py, line 421:

<comment>Persisted `package.json` numeric fields above orjson's 64-bit range are silently rounded and rewritten, violating this flow's pass-through behavior and making output depend on whether optional `orjson` is installed. Retaining stdlib parsing here, or otherwise preserving out-of-range numbers exactly, would avoid corrupting user-owned fields.</comment>

<file context>
@@ -418,8 +418,8 @@ def _read_persisted_package_json() -> dict:
     try:
-        parsed = json.loads(root_package_json_path.read_text())
-    except (json.JSONDecodeError, OSError) as e:
+        parsed = orjson_loads(root_package_json_path.read_bytes())
+    except (ValueError, OSError) as e:
         console.warn(
</file context>
Fix with cubic

@@ -91,7 +91,7 @@ def tailwind_config_js_template(
Returns:

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Tailwind theme values or plugin arguments containing NaN/Infinity now become null, silently changing the generated JavaScript configuration. Since this output is JavaScript rather than strict JSON, consider retaining stdlib json.dumps here or using a serializer that preserves these valid JS numeric tokens.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/plugins/shared_tailwind.py, line 94:

<comment>Tailwind theme values or plugin arguments containing `NaN`/`Infinity` now become `null`, silently changing the generated JavaScript configuration. Since this output is JavaScript rather than strict JSON, consider retaining stdlib `json.dumps` here or using a serializer that preserves these valid JS numeric tokens.</comment>

<file context>
@@ -91,7 +91,7 @@ def tailwind_config_js_template(
         The Tailwind config template.
     """
-    import json
+    from reflex_base.utils.format import orjson_dumps
 
     # Extract parameters
</file context>
Fix with cubic

Comment thread reflex/app.py
stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
with stateful_pages_marker.open("w") as f:
json.dump(list(self._stateful_pages), f)
f.write(format.orjson_dumps(list(self._stateful_pages)))

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Compiling a stateful route with a non-ASCII dynamic argument can now fail on systems whose default text encoding cannot represent it, because orjson_dumps emits Unicode directly into a locale-encoded file. Opening stateful_pages_marker with encoding="utf-8" would match the UTF-8 JSON output and its byte-based reader.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At reflex/app.py, line 1669:

<comment>Compiling a stateful route with a non-ASCII dynamic argument can now fail on systems whose default text encoding cannot represent it, because `orjson_dumps` emits Unicode directly into a locale-encoded file. Opening `stateful_pages_marker` with `encoding="utf-8"` would match the UTF-8 JSON output and its byte-based reader.</comment>

<file context>
@@ -1667,7 +1666,7 @@ def _write_stateful_pages_marker(self):
             stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
             with stateful_pages_marker.open("w") as f:
-                json.dump(list(self._stateful_pages), f)
+                f.write(format.orjson_dumps(list(self._stateful_pages)))
 
     def add_all_routes_endpoint(self):
</file context>
Fix with cubic

@@ -0,0 +1 @@
The JSON helpers in `reflex_base.utils.format` use orjson when installed, with sentinel-based round-tripping of `NaN`/`Infinity` floats to the frontend. Without orjson, socket serialization keeps the stdlib fast path: payloads are dumped once and only re-processed when a sentinel-colliding string is detected in the output.

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This overstates behavior of the general JSON helpers: json_dumps always uses stdlib, and orjson_dumps converts NaN/Infinity to null rather than round-tripping them. Scoping the sentinel claim to orjson_dumps_socket avoids promising data preservation to callers of orjson_dumps.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/news/6116.performance.md, line 1:

<comment>This overstates behavior of the general JSON helpers: `json_dumps` always uses stdlib, and `orjson_dumps` converts `NaN`/`Infinity` to `null` rather than round-tripping them. Scoping the sentinel claim to `orjson_dumps_socket` avoids promising data preservation to callers of `orjson_dumps`.</comment>

<file context>
@@ -0,0 +1 @@
+The JSON helpers in `reflex_base.utils.format` use orjson when installed, with sentinel-based round-tripping of `NaN`/`Infinity` floats to the frontend. Without orjson, socket serialization keeps the stdlib fast path: payloads are dumped once and only re-processed when a sentinel-colliding string is detected in the output.
</file context>
Fix with cubic

import JSON5 from "json5";
import env from "$/env.json";
import {
parseNonFiniteAwareJSON,

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Frontend module loading now has a direct circular dependency between state.js and helpers/upload.js, making initialization order fragile. Moving the non-finite JSON codec into a dependency-free utility (ideally exposing the complete parse-with-fallback operation) would keep both consumers acyclic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/.templates/web/utils/helpers/upload.js, line 3:

<comment>Frontend module loading now has a direct circular dependency between `state.js` and `helpers/upload.js`, making initialization order fragile. Moving the non-finite JSON codec into a dependency-free utility (ideally exposing the complete parse-with-fallback operation) would keep both consumers acyclic.</comment>

<file context>
@@ -1,5 +1,9 @@
-import JSON5 from "json5";
 import env from "$/env.json";
+import {
+  parseNonFiniteAwareJSON,
+  reviveNonFiniteFloats,
+  rewriteBareNonFiniteFloats,
</file context>
Fix with cubic

from reflex_base.utils.serializers import serialize

return json.dumps(self, default=serialize)
return orjson_dumps(self)

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Config.json() never benefits from orjson because every Config contains the unsupported _non_default_attributes set, so orjson_dumps always retries the full serialization with json_dumps. Consider normalizing the config/custom fields for orjson or extending the helper with the registered serializer before switching this call site.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/config.py, line 643:

<comment>`Config.json()` never benefits from orjson because every `Config` contains the unsupported `_non_default_attributes` set, so `orjson_dumps` always retries the full serialization with `json_dumps`. Consider normalizing the config/custom fields for orjson or extending the helper with the registered serializer before switching this call site.</comment>

<file context>
@@ -638,11 +638,9 @@ def json(self) -> str:
-        from reflex_base.utils.serializers import serialize
-
-        return json.dumps(self, default=serialize)
+        return orjson_dumps(self)
 
     @staticmethod
</file context>
Fix with cubic

orjson is None
# orjson only supports two-space indentation and always emits UTF-8.
or indent not in (None, 2)
or kwargs.keys() - {"indent", "sort_keys", "ensure_ascii"}

@cubic-dev-ai cubic-dev-ai Bot Jul 31, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: ensure_ascii=True is silently ignored because this guard still selects orjson, which always emits UTF-8. Falling back to json_dumps when the option is truthy preserves the advertised keyword behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/reflex-base/src/reflex_base/utils/format.py, line 713:

<comment>`ensure_ascii=True` is silently ignored because this guard still selects orjson, which always emits UTF-8. Falling back to `json_dumps` when the option is truthy preserves the advertised keyword behavior.</comment>

<file context>
@@ -683,6 +688,210 @@ def json_dumps(obj: Any, **kwargs) -> str:
+        orjson is None
+        # orjson only supports two-space indentation and always emits UTF-8.
+        or indent not in (None, 2)
+        or kwargs.keys() - {"indent", "sort_keys", "ensure_ascii"}
+    ):
+        return json_dumps(obj, **kwargs)
</file context>
Suggested change
or kwargs.keys() - {"indent", "sort_keys", "ensure_ascii"}
or kwargs.get("ensure_ascii", False)
or kwargs.keys() - {"indent", "sort_keys", "ensure_ascii"}
Fix with cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants