Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→codsen-format-diagnostic-valuenext

codsen-format-diagnostic-value1.1.1

Safely format untrusted JavaScript values for diagnostics

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • PURPOSE
  • API — FORMATDIAGNO…
  • COMPACT AND…
  • VALUES OUTSID…
  • ACCESSORS AN…
  • INSPECTION LI…
  • RE-EXPORT FRO…
  • Changelog

No dependencies whatsoever. This package declares no dependencies or devDependencies.

Permalink to InstallationInstallation

Permalink to Quick TakeQuick Take

Purpose

codsen-format-diagnostic-value turns any JavaScript value into bounded, human-readable text for validation errors and diagnostic logs. It represents values that JSON.stringify() cannot handle, avoids reading accessors, marks circular references, and contains failures while inspecting hostile objects.

The result is JSON-like, not JSON. Diagnostic tokens, symbol keys, and truncation can make it invalid JSON, so don’t parse the result or use it as a storage format.

API — formatDiagnosticValue()

The main function formatDiagnosticValue() is imported like this:

The function accepts these arguments:

ArgumentTypeRequiredDescription
value
Type: unknown
valueunknownyesThe value to format.
indentation
Type: 0 | 4
indentation0 | 4noUse 0, the default, for compact output. Use 4 for multiline output with four-space indentation.

It returns a string. Ordinary inputs aren’t mutated; proxy traps can cause their own side effects, as described in Inspection limits.

Compact and indented output

By default, nested values stay on one line:

import { formatDiagnosticValue } from "codsen-format-diagnostic-value";

console.log(
  formatDiagnosticValue({ one: 1, nested: { ok: true }, list: ["x", null] }),
);
// => {"one":1,"nested":{"ok":true},"list":["x",null]}

Pass 4 when the diagnostic follows a line break or needs to remain readable as it grows:

console.log(formatDiagnosticValue({ one: 1, list: [true, "x"] }, 4));
// => {
//        "one": 1,
//        "list": [
//            true,
//            "x"
//        ]
//    }

No other indentation width is part of the API.

Values outside JSON

The formatter preserves useful distinctions that JSON omits or rejects:

InputOutput
undefined
undefinedundefined
123n
123n123n
Symbol("marker")
Symbol("marker")Symbol("marker")
A function
A function[Function]
NaN
NaNNaN
Positive or negative infinity
Positive or negative infinityInfinity or -Infinity
Negative zero
Negative zero-0

Strings use double quotes. The formatter escapes quotes, backslashes, C0 control characters (U+0000–U+001F), the U+2028 line separator, the U+2029 paragraph separator, and UTF-16 surrogate code units. These escapes keep those code units visible in diagnostic text.

Objects contain their own enumerable string and symbol properties. Inherited and non-enumerable properties are omitted. Arrays contain indexed slots only; extra named properties are omitted, and a missing or non-enumerable indexed slot becomes [Empty].

Built-ins such as Date, Map, Set, RegExp, and Error don’t receive type-specific serializers. Each is represented through its own enumerable properties, which can produce {} when it has none.

Accessors and circular references

The formatter inspects property descriptors instead of reading property values. Getters and setters aren’t invoked:

let getterCalls = 0;
const input = {};

Object.defineProperty(input, "value", {
  enumerable: true,
  get() {
    getterCalls += 1;
    throw new Error("must not run");
  },
});

console.log(formatDiagnosticValue(input));
// => {"value":[Getter]}
console.log(getterCalls);
// => 0

Accessors appear as [Getter], [Setter], [Getter/Setter], or [Accessor]. The formatter also doesn’t call an object’s toJSON() method.

A reference to an active ancestor becomes [Circular]:

const input = { name: "root" };
input.self = input;

console.log(formatDiagnosticValue(input));
// => {"name":"root","self":[Circular]}

Repeated references that aren’t circular are expanded each time. For example, two sibling properties that point to the same object both include that object’s contents.

Inspection limits

The formatter caps its output and limits its own traversal after each JavaScript reflection operation returns:

LimitBehavior
2,000 UTF-16 code units
2,000 UTF-16 code unitsStops output with …. An escape sequence is never split.
Five object levels
Five object levelsReplaces a deeper object or array with [MaxDepth].
50 entries across the whole value
50 entries across the whole valueAdds [MaxEntries] when the shared object-property and array-slot budget is exhausted.

The entry limit is global for one call, not 50 entries per nested object. Reflected non-enumerable object keys also consume that budget, even though they don’t appear in the output. This bounds descriptor checks for proxies that report many hidden keys.

Reflection failures are represented at the affected object, property, or array slot. Any failure that reaches the outer formatter, including a primitive conversion failure, makes the whole call return [Uninspectable].

Reflect.ownKeys() collects the complete key list before the 50-entry budget applies. Reflection on a proxy can also invoke its ownKeys and getOwnPropertyDescriptor traps. Those traps can run code, cause side effects, or fail to return. The formatter contains thrown failures from them, but these limits aren’t CPU-time or allocation guarantees, and the formatter isn’t a sandbox.

Re-export from codsen-utils

codsen-utils also re-exports formatDiagnosticValue() for packages that already depend on that utility collection:

import { formatDiagnosticValue } from "codsen-utils";

Permalink to changelogChangelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab