Last 12 weeks · 6 commits
2 of 6 standards met
has two code paths: an inline value format, and a reference format used when a value appears more than once or inside a cycle (the value is hoisted into a function parameter so the references can share identity). The inline format handles the eight types, but the reference format never got a matching case, so a repeated Temporal value falls through to the plain-object branch and is emitted as . It is silently lost: round-trips the same input correctly (), so the two serializers disagreed on a repeated Temporal value. This mirrors the existing inline Temporal case into the reference switch and adds a repetition fixture. Reverting only the change fails the new case while the // checks stay green, which isolates the bug to .
What does not deduplicate objects used as keys. When the same object is a key in more than one (or is a key and also appears elsewhere in the graph), inlines a fresh copy at each occurrence, so the round-tripped value loses object identity: / already handle this correctly — only is affected. Closes #54. Why In the pass, the case only recursed into values, not keys: Because keys never entered , a key that appears more than once was never recognized as a shared reference and never assigned a hoisted variable — so each occurrence was serialized inline as a fresh literal. ( already walks its members, and 's flatten pass already walks both and , which is why those paths were unaffected.) Fix Walk the key as well as the value. The existing step already emits the hoisted name for any value that ended up in , including keys reached through the entry arrays, so no other change is needed: After the fix: Tests Added two fixtures (each exercised by the existing uneval / stringify / parse / unflatten / stringifyAsync round-trip suites, with callbacks asserting key identity): Map key (repetition) — minimal case: an object shared as a key and a sibling array element. Map keys (interlinked) — the issue #54 graph above. Both new fixtures fail on and pass with this change; the / variants pass either way (confirming the bug was -only). Full suite: 692 passing.
What A typed array / / that is referenced more than once and lives inside a cyclic container is lost on round-trip — references to it become the placeholder: ( is fine — the references are in the , after the reassignment. The bug needs a container whose fills run before the reassignment, i.e. a cyclic/named container.) Why A repeated typed array / DataView / ArrayBuffer is hoisted as a placeholder () and rebuilt with a reassignment (). That reassignment was pushed onto the same ordered list as the property fills (). The list is ordered by reference count, so when a referencing container is emitted first, runs while is still . Fix Emit those reassignments first, in a separate list. They only depend on IIFE arguments — a typed array's buffer is always passed as an argument and never reassigned — so they never depend on each other or on the containers. Running them before the property fills makes every reference resolve to the real value. I verified with a round-trip fuzzer ( over 20k random values mixing cycles, repeated references, typed arrays, DataViews and sparse arrays): this was the last remaining round-trip failure in that space. Tests Added a test that round-trips a typed array referenced twice inside a cycle (red before, green after). Full passes (659).
Repository: sveltejs/devalue. Description: Gets the job done when JSON.stringify can't Stars: 2757, Forks: 89. Primary language: JavaScript. Languages: JavaScript (100%). License: MIT. Homepage: https://svelte.dev/repl/138d70def7a748ce9eda736ef1c71239 Latest release: v5.8.1 (1mo ago). Open PRs: 14, open issues: 23. Last activity: 1mo ago. Community health: 50%. Top contributors: Rich-Harris, github-actions[bot], elliott-with-the-longest-name-on-github, mrkishi, gtm-nayan, danielroe, ivanhofer, LorisSigrist, GauBen, TooTallNate and others.