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 ifstatecontains</script>.unevalandstringifyescape 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, andTemporalround-trip correctly. - Promises.
stringifyAsyncawaits promises and serializes their resolved values using the same format asstringify. - Custom domain types. Reducers (for
stringify) and revivers (forparse) let you define how your own classes are encoded and reconstructed. - Cross-realm or sandboxed runtimes. The
operationsinterface accepts custom introspection and construction, useful for serializing values held in anode:vmcontext 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
pathproperty pointing at the offending location. - Typed arrays and
DataViewserialize the entire backingArrayBuffer, including bytes outside asubarray. 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
unevalto send data from client to server, and recommends indirecteval((0, eval)(...)) ornew Function(code)when deserializing so evaluated code cannot reach the surrounding scope.