or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

index.mddocs/

Devalue

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.

Package Information

  • Package Name: devalue
  • Package Type: npm
  • Language: JavaScript (ES modules)
  • Installation: npm install devalue

Core Imports

import { uneval, stringify, parse, unflatten } from "devalue";

For CommonJS:

const { uneval, stringify, parse, unflatten } = require("devalue");

Basic Usage

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] }

Architecture

Devalue provides three complementary approaches to serialization:

  • Code Generation: uneval() creates executable JavaScript for maximum compactness
  • JSON-based Serialization: stringify()/parse() provides safe, evaluable format
  • Partial Deserialization: unflatten() handles pre-parsed JSON structures
  • Security First: XSS protection through character escaping and function rejection
  • Custom Types: Extensible through replacers, reducers, and revivers

Capabilities

Code Generation (uneval)

Generates 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)"

JSON-based Serialization (stringify)

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"]'

JSON Deserialization (parse)

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)
});

Partial Deserialization (unflatten)

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" } }

Error Handling

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"
}

Supported Data Types

Primitive Types

  • number (including NaN, Infinity, -Infinity, -0)
  • string
  • boolean
  • null
  • undefined
  • bigint

Object Types

  • Plain objects (POJOs)
  • Arrays (including sparse arrays)
  • Date objects
  • RegExp objects
  • Set and Map objects
  • Boxed primitives (new Number(), new String(), new Boolean())
  • Objects with null prototype (Object.create(null))

Advanced Features

  • Cyclical references
  • Repeated references
  • Custom types via replacers/reducers/revivers

Security Features

  • XSS protection through character escaping
  • Function serialization prevention
  • Non-POJO rejection (unless handled by custom handlers)
  • Symbol property rejection

Type Definitions

// 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[]);
}