← Back to archive

Repo of the Day

sveltejs/devalue

Published: Feb 24, 2023

Open repository ↗

Gets the job done when JSON.stringify can't. Like superjson basically

Summary

Devalue is a JavaScript serialization library from the Svelte team that handles values JSON.stringify cannot, including cycles, repeated references, Map/Set, BigInt, Date, RegExp, typed arrays, and Temporal values. It targets performance, compact output, and XSS mitigation, making it well suited for embedding server state into HTML pages or transferring rich data between JavaScript contexts.

What it is useful for

  • Safe state hydration. Embedding JSON.stringify(state) inside a <script> tag can break out of the script context if state contains </script>. uneval and stringify escape those strings, neutralizing the attack.
  • Graph-shaped data. Cycles (obj.self = obj) and shared references are preserved instead of lost or duplicated.
  • Modern and typed values. Map, Set, Date, RegExp, URL, URLSearchParams, BigInt, ArrayBuffer, and Temporal round-trip correctly.
  • Promises. stringifyAsync awaits promises and serializes their resolved values using the same format as stringify.
  • Custom domain types. Reducers (for stringify) and revivers (for parse) let you define how your own classes are encoded and reconstructed.
  • Cross-realm or sandboxed runtimes. The operations interface accepts custom introspection and construction, useful for serializing values held in a node:vm context or a WASM host.

How engineers can use it

The README does not document install commands, so check the repository for current setup. The published module exposes four entry points:

import * as devalue from 'devalue';

// uneval — emit JS source that recreates the value
devalue.uneval({ message: 'hello' }); // '{message:"hello"}'

// stringify / parse — JSON-style payload
const str = devalue.stringify({ message: 'hello' });
devalue.parse(str);

// stringifyAsync — awaits promises before serializing
await devalue.stringifyAsync({ slow: fetch('/api').then(r => r.json()) });

// unflatten — revive a devalue payload embedded in a larger JSON
devalue.unflatten(JSON.parse(json).data);

For custom classes, pass a reducers object to stringify and a revivers object to parse:

class Vector { constructor(x, y) { this.x = x; this.y = y; } }
devalue.stringify(new Vector(30, 40), {
  Vector: (v) => v instanceof Vector && [v.x, v.y]
});
devalue.parse(str, { Vector: ([x, y]) => new Vector(x, y) });

Documented limitations to plan around:

  • Functions and unhandled non-POJOs throw; errors carry a path property pointing at the offending location.
  • Typed arrays and DataView serialize the entire backing ArrayBuffer, including bytes outside a subarray. Copy intended bytes first if the full buffer is sensitive.
  • Output is not guaranteed stable across versions — don't persist payloads for long-term storage.
  • The README warns against using uneval to send data from client to server, and recommends indirect eval ((0, eval)(...)) or new Function(code) when deserializing so evaluated code cannot reach the surrounding scope.