Skip to content

fix: decode tuple/frozenset payloads with non-finite floats#9365

Merged
mscolnick merged 2 commits into
mainfrom
ms/fix-structures
Apr 24, 2026
Merged

fix: decode tuple/frozenset payloads with non-finite floats#9365
mscolnick merged 2 commits into
mainfrom
ms/fix-structures

Conversation

@mscolnick

Copy link
Copy Markdown
Contributor

Python's json.dumps emits bare NaN/Infinity/-Infinity for non-finite floats inside embedded tuple/frozenset payloads (e.g. text/plain+tuple:[NaN]). The outer JSON still parses because those tokens live inside a JSON string, but formatTuplePayload and formatFrozensetPayload used strict JSON.parse and threw.

Swap both to jsonParseWithSpecialChar (same parser we already use at the outer level) and render non-finite elements as float('nan') / float('inf') / -float('inf'), matching the scalar-key form.

Python's `json.dumps` emits bare `NaN`/`Infinity`/`-Infinity` for
non-finite floats inside embedded tuple/frozenset payloads (e.g.
`text/plain+tuple:[NaN]`). The outer JSON still parses because those
tokens live inside a JSON string, but `formatTuplePayload` and
`formatFrozensetPayload` used strict `JSON.parse` and threw.

Swap both to `jsonParseWithSpecialChar` (same parser we already use
at the outer level) and render non-finite elements as `float('nan')`
/ `float('inf')` / `-float('inf')`, matching the scalar-key form.
Copilot AI review requested due to automatic review settings April 24, 2026 17:02
@vercel

vercel Bot commented Apr 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marimo-docs Ready Ready Preview, Comment Apr 24, 2026 5:16pm

Request Review

Copilot AI 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.

Pull request overview

Fixes frontend decoding of text/plain+tuple: and text/plain+frozenset: embedded JSON payloads that may contain bare NaN/Infinity tokens emitted by Python’s json.dumps, aligning behavior with the existing special-float parsing used elsewhere.

Changes:

  • Update tuple/frozenset (and set) payload decoding to use jsonParseWithSpecialChar, and render non-finite floats as float('nan') / float('inf') / -float('inf') in copy output.
  • Add frontend regression coverage for tuple/frozenset payloads containing bare NaN/Infinity.
  • Add backend tests exercising tuple/frozenset encodings involving non-finite floats (outer JSON round-trip).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
tests/_output/formatters/test_structures.py Adds regression tests for tuple/frozenset structures involving non-finite floats.
frontend/src/components/editor/output/tests/json-output.test.ts Adds a regression test ensuring copy output handles embedded bare NaN/Infinity tokens.
frontend/src/components/editor/output/JsonOutput.tsx Switches tuple/frozenset/set payload parsing to jsonParseWithSpecialChar and formats non-finite numbers into Python float(...) literals.

Comment thread tests/_output/formatters/test_structures.py Outdated
Comment thread frontend/src/components/editor/output/JsonOutput.tsx
Comment thread frontend/src/components/editor/output/JsonOutput.tsx
Comment thread tests/_output/formatters/test_structures.py Outdated
@Light2Dark Light2Dark added the bug Something isn't working label Apr 24, 2026

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 3 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="frontend/src/components/editor/output/JsonOutput.tsx">

<violation number="1" location="frontend/src/components/editor/output/JsonOutput.tsx:414">
P2: `jsonParseWithSpecialChar` silently returns `{}` on parse failure instead of throwing, but this function assumes the result is always an array and immediately accesses `.length` / `.map`. For a malformed payload, `{}.map` will throw a `TypeError` at render time. Add an `Array.isArray(items)` guard (falling back to the raw payload string), matching the defensive pattern already used by `formatSetPayload`.</violation>
</file>
Architecture diagram
sequenceDiagram
    participant Kernel as Python Kernel
    participant UI as JsonOutput Component
    participant Parser as jsonParseWithSpecialChar
    participant Clipboard as User/Clipboard

    Note over Kernel, UI: Data Serialization (marimo/_output/formatters)

    Kernel->>Kernel: Format collection (tuple/frozenset)
    Note right of Kernel: Python json.dumps emits<br/>bare NaN/Infinity inside strings
    Kernel-->>UI: JSON response with "text/plain+tuple:[NaN, Infinity]"

    Note over UI, Parser: Frontend Decoding Flow

    UI->>UI: Detect KEY_ENCODED_PREFIX
    
    rect rgb(240, 240, 240)
        Note right of UI: NEW: Handling non-finite floats in collections
        UI->>Parser: CHANGED: jsonParseWithSpecialChar(payload)
        Note right of Parser: Parses non-standard tokens<br/>(NaN, Infinity, -Infinity)
        Parser-->>UI: Array of JS numbers/values
        
        UI->>UI: NEW: formatCollectionItems()
        alt is non-finite number
            UI->>UI: Map to "float('nan')" or "float('inf')"
        else standard value
            UI->>UI: JSON.stringify(x)
        end
    end

    UI->>UI: Join items with Python syntax (e.g. (item,))
    
    UI-->>Clipboard: Rendered string / Copy to clipboard
    Note right of Clipboard: Result: "(float('nan'), float('inf'))"
Loading

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread frontend/src/components/editor/output/JsonOutput.tsx
- formatTuplePayload / formatFrozensetPayload / formatSetPayload now
  Array.isArray-guard the jsonParseWithSpecialChar result. It returns
  {} on parse failure (doesn't throw), so without the guard a malformed
  payload would crash rendering/copy on .length/.map. Fall back to the
  raw payload (matching formatSetPayload's existing pattern), and add
  a frontend test covering the malformed path.

- The "outer JSON is strict" tests now decode with
  parse_constant=reject to actually enforce strictness. Python's
  json.loads accepts bare NaN/Infinity by default — that's too lenient
  to test JS JSON.parse compatibility, which is the actual contract.
if (Number.isNaN(x)) {
return "float('nan')";
}
return x > 0 ? "float('inf')" : "-float('inf')";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do you know if this affects pd.NaT, np.nan

@mscolnick mscolnick requested a review from manzt April 24, 2026 17:26
@mscolnick mscolnick merged commit 425d626 into main Apr 24, 2026
42 checks passed
@mscolnick mscolnick deleted the ms/fix-structures branch April 24, 2026 17:26
@codecov

codecov Bot commented Apr 24, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 25.12MB (100.0%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name Size Change
marimo-esm 25.12MB 25.12MB (100%) ⬆️⚠️

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants