Devalue is a secure serialization library that extends beyond JSON.stringify's capabilities by handling cyclical references, repeated references, undefined values, Infinity, NaN, -0, regular expressions, dates, Map and Set objects, BigInt, and custom types through replacers, reducers and revivers.
npm install devalueimport { uneval, stringify, parse, unflatten } from "devalue";For CommonJS:
const { uneval, stringify, parse, unflatten } = require("devalue");import { uneval, stringify, parse } from "devalue";
// Basic object
let obj = { message: 'hello', count: 42 };
// uneval - generates JavaScript code
console.log(uneval(obj)); // '{message:"hello",count:42}'
// stringify/parse - JSON-based approach
let serialized = stringify(obj); // '[{"message":1,"count":2},"hello",42]'
let restored = parse(serialized); // { message: 'hello', count: 42 }
// Handling cyclical references
obj.self = obj;
console.log(uneval(obj)); // '(function(a){a.message="hello";a.count=42;a.self=a;return a}({}))'
serialized = stringify(obj); // '[{"message":1,"count":2,"self":0},"hello",42]'
restored = parse(serialized); // { message: 'hello', count: 42, self: [Circular] }Devalue provides three complementary approaches to serialization:
uneval() creates executable JavaScript for maximum compactnessstringify()/parse() provides safe, evaluable formatunflatten() handles pre-parsed JSON structuresGenerates JavaScript code to recreate a value - the most compact output format that doesn't require parsing libraries.
/**
* Turn a value into JavaScript code that creates an equivalent value
* @param value - The value to serialize
* @param replacer - Optional custom replacer function for custom types
* @returns JavaScript code string
* @throws DevalueError for functions or non-serializable objects
*/
function uneval(value: any, replacer?: (value: any) => string | void): string;Usage Examples:
import { uneval } from "devalue";
// Simple values
uneval(42); // "42"
uneval("hello"); // '"hello"'
uneval([1, 2, 3]); // "[1,2,3]"
// Complex objects with cycles
const circular = { name: "test" };
circular.ref = circular;
uneval(circular); // '(function(a){a.name="test";a.ref=a;return a}({}))'
// Custom types with replacer
class Vector {
constructor(x, y) { this.x = x; this.y = y; }
}
uneval(new Vector(10, 20), (value) => {
if (value instanceof Vector) {
return `new Vector(${value.x},${value.y})`;
}
}); // "new Vector(10,20)"Converts values to JSON format that can be safely parsed, handling more types than JSON.stringify.
/**
* Turn a value into JSON string that can be parsed with devalue.parse
* @param value - The value to serialize
* @param reducers - Optional custom type reducers object
* @returns JSON string representation
* @throws DevalueError for functions or non-serializable objects
*/
function stringify(value: any, reducers?: Record<string, (value: any) => any>): string;Usage Examples:
import { stringify } from "devalue";
// Basic serialization
stringify({ name: "Alice", age: 30 }); // '[{"name":1,"age":2},"Alice",30]'
// Special values
stringify([undefined, NaN, Infinity, -0]); // '[-1,-3,-4,-6]'
// Dates and RegExp
stringify(new Date("2023-01-01")); // '[["Date","2023-01-01T00:00:00.000Z"]]'
stringify(/hello/gi); // '[["RegExp","hello","gi"]]'
// Custom types with reducers
class User {
constructor(name, email) { this.name = name; this.email = email; }
}
stringify(new User("Bob", "bob@test.com"), {
User: (user) => user instanceof User && [user.name, user.email]
}); // '[["User",1],[2,3],"Bob","bob@test.com"]'Revives values serialized with devalue.stringify back to their original form.
/**
* Revive a value serialized with devalue.stringify
* @param serialized - JSON string from stringify
* @param revivers - Optional custom type revivers object
* @returns The deserialized value
* @throws Error for invalid input
*/
function parse(serialized: string, revivers?: Record<string, (value: any) => any>): any;Usage Examples:
import { parse } from "devalue";
// Basic parsing
const data = parse('[{"name":1,"age":2},"Alice",30]');
// { name: "Alice", age: 30 }
// With custom revivers
const userData = parse('[["User",1],[2,3],"Bob","bob@test.com"]', {
User: ([name, email]) => new User(name, email)
});Revives pre-parsed JSON structures, useful when devalue data is part of larger JSON.
/**
* Revive a value flattened with devalue.stringify
* @param parsed - Pre-parsed JSON structure (number or array)
* @param revivers - Optional custom type revivers object
* @returns The deserialized value
* @throws Error for invalid input
*/
function unflatten(parsed: number | any[], revivers?: Record<string, (value: any) => any>): any;Usage Examples:
import { unflatten, stringify } from "devalue";
// Embed devalue data in larger JSON
const complexData = { user: "Alice", preferences: { theme: "dark" } };
const json = `{
"type": "userdata",
"payload": ${stringify(complexData)}
}`;
// Parse and extract just the devalue portion
const parsed = JSON.parse(json);
const userData = unflatten(parsed.payload);
// { user: "Alice", preferences: { theme: "dark" } }All functions throw DevalueError for unsupported values, with path information for debugging.
class DevalueError extends Error {
name: "DevalueError";
path: string; // Location of problematic value
message: string;
}Usage Examples:
import { uneval } from "devalue";
try {
const map = new Map();
map.set('key', function invalid() {});
uneval({
object: {
array: [map]
}
});
} catch (error) {
console.log(error.name); // "DevalueError"
console.log(error.path); // '.object.array[0].get("key")'
console.log(error.message); // "Cannot stringify a function"
}number (including NaN, Infinity, -Infinity, -0)stringbooleannullundefinedbigintDate objectsRegExp objectsSet and Map objectsnew Number(), new String(), new Boolean())Object.create(null))// Custom type handlers
type Replacer = (value: any) => string | void;
type Reducer = (value: any) => any;
type Reviver = (value: any) => any;
// Reducer and reviver objects
type Reducers = Record<string, Reducer>;
type Revivers = Record<string, Reviver>;
// Error class
class DevalueError extends Error {
name: "DevalueError";
path: string;
message: string;
constructor(message: string, keys: string[]);
}