Skip to content

feat: add reviver option for decode - #294

Open
mvanhorn wants to merge 1 commit into
toon-format:mainfrom
mvanhorn:feat/reviver-decode
Open

mvanhorn wants to merge 1 commit into
toon-format:mainfrom
mvanhorn:feat/reviver-decode

Conversation

@mvanhorn

Copy link
Copy Markdown

Summary

Adds a reviver option to DecodeOptions, mirroring the existing replacer for EncodeOptions. The reviver transforms values during decode, matching JSON.parse reviver semantics (bottom-up traversal: leaves first, then parents).

Why this matters

JSON.stringify has replacer, JSON.parse has reviver. TOON's encode already has replacer. The decode side had no equivalent, forcing users to walk the decoded tree manually for any post-decode transformation.

PR #161 (quoteStrings) was closed in favor of the general-purpose replacer, confirming the preference for general mechanisms over specific flags. reviver follows the same philosophy for the decode side.

Usage

import { decode, encode } from '@toon-format/toon'

const toon = encode({ name: 'Alice', password: 'secret', role: 'admin' })

const result = decode(toon, {
  reviver: (key, value) => {
    if (key === 'password') return undefined
    if (key === 'name' && typeof value === 'string')
      return value.toUpperCase()
    return value
  }
})
// { name: 'ALICE', role: 'admin' }

Changes

  • packages/toon/src/types.ts: added DecodeReviver type and reviver option to DecodeOptions
  • packages/toon/src/decode/reviver.ts: new module implementing bottom-up value traversal (mirrors encode/replacer.ts)
  • packages/toon/src/index.ts: wired reviver into decodeFromLines(), exported DecodeReviver type
  • packages/toon/test/reviver.test.ts: 22 tests covering filtering, transformation, root handling, bottom-up traversal, path tracking, edge cases, and JSON.parse parity

Demo

reviver demo

Testing

All 488 tests pass (22 new + 466 existing). Lint, build, and type checks clean.

This contribution was developed with AI assistance (Claude Code).

@johannschopplich

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

The bottom-up traversal matches JSON.parse's reviver semantics, and test/reviver.test.ts mirroring test/replacer.test.ts is the right shape.

Three things before merge:

  1. Dedupe the type guards. decode/reviver.ts defines local isJsonObject / isJsonArray. These already live in encode/normalize.ts, and replacer.ts imports them. Please import from there, or move them to a shared/ module if a shared home reads cleaner.

  2. Reuse normalizeValue. The new normalizeRevivedValue reimplements a subset of encode/normalize.ts:normalizeValue and has a buggy fallback. String(value) for unknown types means functions/symbols/undefined become strings, where they should be null. The existing normalizeValue already handles Date, BigInt, Map, Set, toJSON, NaN/Infinity, and -0.

  3. README section. Add a doc block under packages/toon/README.md mirroring the existing replacer entry – same shape, swap the verbs.

Once these land, this is ready to merge.

@johannschopplich

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Still want this – reviver symmetry with replacer is a natural fit. It stalled after the approve-with-changes round; are you up for finishing the requested changes (plus a check on the bundle-size delta)? Otherwise we'll take it over and land it with credit.

@johannschopplich johannschopplich added enhancement New feature or request ready-for-human Needs human implementation or merge labels Jul 15, 2026
@mvanhorn

Copy link
Copy Markdown
Author

Thanks @johannschopplich — yes, I'm up for finishing it. It's been a bit, so to target the right gaps: which specific approve-with-changes items stalled it? I'll knock those out and report the bundle-size delta with the reviver added.

@johannschopplich

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

All three from the review round are still open – the branch has no commits since:

  1. Dedupe the type guardsdecode/reviver.ts defines local isJsonObject / isJsonArray; import them from encode/normalize.ts like replacer.ts does, or move them to a shared module.
  2. Reuse normalizeValuenormalizeRevivedValue reimplements a subset of it, and the String(value) fallback turns functions/symbols/undefined into strings where they should be null.
  3. Docs – this one changed since the review: the core README has since been slimmed down, so skip the README block. Add a Reviver section to docs/reference/api.md mirroring the Replacer Function section, plus a reviver mention in DecodeOptions.

And the bundle-size delta you already offered. With those, ready to merge.

Mirrors the existing `replacer` option for encode. The reviver function
is called bottom-up (leaves first) during decode, matching JSON.parse
reviver semantics. Supports value transformation, property filtering
via `undefined` return, and path tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mvanhorn
mvanhorn force-pushed the feat/reviver-decode branch from ed104ed to b3e4f61 Compare July 26, 2026 20:03
@mvanhorn

Copy link
Copy Markdown
Author

All three are in, plus the bundle numbers.

  1. Type guards -- decode/reviver.ts now imports isJsonObject / isJsonArray from encode/normalize.ts the same way replacer.ts does; the local copies are gone.
  2. normalizeValue -- normalizeRevivedValue is deleted. The three call sites use normalizeValue directly, so the String(value) fallback is gone and functions/symbols/undefined normalize to null alongside the Date/BigInt/Map/Set/toJSON/NaN/-0 handling you already had.
  3. Docs -- skipped the README per your note. Added a Reviver Function section to docs/reference/api.md mirroring Replacer Function, plus a reviver row in the DecodeOptions table.

Bundle delta for @toon-format/toon: +2,850 B raw (+4.34%), +570 B gzipped (+3.80%).

One thing worth flagging from the rebase: this branch was cut when expandPaths was still on main, and it isn't anymore. The rebase drops that, along with the one reviver test that exercised it. The reviver itself is unaffected.

pnpm test is 785 passing across the three packages, test:types and lint clean.

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

Labels

enhancement New feature or request ready-for-human Needs human implementation or merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants