Arguably the most comprehensive cross-platform library for stringifying any JavaScript value into a human-readable format, handling nearly every scenario you might encounter.
- Stringify any JavaScript value in a format similar to
util.inspect
in Node.js. Special cases like Map, Set, array empty slots, wrapper objects for primitives, ArrayBuffer, DataView, WeakMap, WeakSet, and more are handled properly. - Support for maximum recursion depth in nested objects.
- Circular references displayed with a reference pointer (e.g.,
<ref *1> { foo: { bar: [Circular *1] } }
) instead of just[Circular]
. - Auto indentation and line breaking for long outputs, just like
console.log
/util.inspect
in Node.js and Deno. - ANSI color support.
- Highly customizable options for indentation, quote style, hidden properties, colors, and more.
- Custom serializers for your own types, with a user-friendly API that works on a tree-like structure instead of raw strings, handling maximum recursion depth, indentation, and circular references automatically — without any extra effort.
Simply import the show
function and use it to stringify any JavaScript value:
import { show } from "showify";
const value = {
foo: "bar",
"Hello\nworld": [-0, 2n, NaN],
[Symbol("qux")]: { quux: "corge" },
map: new Map([
["foo", "bar"],
[{ bar: 42 }, "qux"],
]),
};
value.circular = value;
console.log(show(value, { indent: 2, trailingComma: "auto", colors: true }));
// <ref *1> {
// foo: "bar",
// "Hello\nworld": [-0, 2n, NaN],
// map: Map(2) { "foo" => "bar", { bar: 42 } => "qux" },
// circular: [Circular *1],
// [Symbol(qux)]: { quux: "corge" },
// }
The show()
function accepts an optional second argument for options. Some common options are listed here:
depth
: Maximum recursion depth, defaults toInfinity
.indent
: Number of spaces to indent, defaults to0
. To enable indentation and auto line breaking, setindent
to a positive integer, e.g.,2
.breakLength
: Maximum line length before breaking, defaults to80
. This option is ignored ifindent
is0
.sorted
: Whether to sort the keys of objects (includingMap
s andSet
s) in the resulting string, defaults tofalse
.quoteStyle
: Preferred quote style for strings, should be"single"
,"double"
,"backtick"
, or an array of them to try in order, defaults to["double", "single", "backtick"]
.trailComma
: Whether to add a trailing comma to the last element of an array or object, should be"none"
,"always"
or"auto"
(add trailing comma only when the last item is on a separate line), defaults to"none"
.colors
: Enable ANSI colors, defaults tofalse
.
showify supports many other options. For a complete list of options, see the available options section below.
Note that showify uses slightly different default options compared to util.inspect
in Node.js. If you want to achieve the exactly same default output as util.inspect
in Node.js, see the related FAQ.
To install showify via npm (or any other package manager you prefer):
npm install showify
If you prefer a lightweight version with a smaller bundle size, you may also consider @showify/lite, which removes ANSI color support and several other features that are not needed for most use cases.
Here’s a comparison of the features of showify, util.inspect
in Node.js, and other libraries:
- ✅ 1st-class, built-in, and ready to use with no additional configuration or code.
- 🟡 Supported, but has limitations or is not as complete as showify.
- 🔶 Supported and documented, but not out-of-the-box or requires extra user-code to implement.
- ❌ Not officially supported or documented.
showHidden: boolean
from Node.js, showify also provides an "exclude-meta"
option, which behaves like true
but removes common meta properties like the length
property of arrays from the output.Promise
objects are displayed as Promise { <state unknown> }
, since it’s impossible to determine the state of a Promise
without awaiting it.WeakMap
and WeakSet
objects are displayed as ${className} { <items unknown> }
even when showHidden
is "always"
, since it’s impossible to retrieve the values of a WeakMap
or WeakSet
without knowing the keys.Aside from the features listed above, showify also supports many more special cases than other libraries, such as wrapper objects for primitives (e.g., new String("foo")
), async/generator functions, ES6 classes, and more.
- Module: Module detection may be misled by
Symbol.toStringTag
. If an object has a non-writable, non-enumerable, and non-configurableSymbol.toStringTag
property set to"Module"
, it will be considered a module. - Promise:
Promise
objects are displayed asPromise { <state unknown> }
, since it’s impossible to determine the state of aPromise
without awaiting it. - Proxy:
Proxy
objects are treated as regular objects, since it’ impossible to identify whether an object is aProxy
in vanilla JavaScript. - WeakMap/WeakSet:
WeakMap
andWeakSet
objects are displayed as${className} { <items unknown> }
even whenshowHidden
is"always"
, since it’s impossible to retrieve the values of aWeakMap
orWeakSet
without knowing the keys.
- Default options: showify uses slightly different default options compared to
util.inspect
in Node.js. For example, showify uses infinite depth, prefers double quotes, and does not break lines by default, whileutil.inspect
uses a depth of2
, prefers single quotes, and breaks lines by default. See the related FAQ if you want to achieve the exact same default output asutil.inspect
in Node.js. - Custom serialization:
util.inspect
supports custom serialization via the specialSymbol(nodejs.util.inspect.custom)
property, while showify does not. Instead, showify offers similar functionality with aserializers
option in theshow
function. - Break length: showify tries to break lines exactly at
breakLength
characters, whileutil.inspect
may break lines at slightly different positions due to a different algorithm for calculating the break position. - Array break: When breaking arrays, showify always places one element per line, while
util.inspect
might place multiple elements on a single line, e.g.,[\n 1, 2, 3, 4, 5,\n 6, 7, 8, 9, 10\n]
. - Circular reference pointer: showify always displays a reference pointer when the same object is referenced, while
util.inspect
does not always do so if the same object is referenced multiple times. Symbol.toStringTag
:util.inspect
shows[Symbol(Symbol.toStringTag)]: "..."
for some objects (like generators and generator functions) whenshowHidden
istrue
, even ifSymbol.toStringTag
is not an own property of the object. showify does not show this in such cases.
Common rules:
- Maximum recursion depth: Values are stringified up to the specified
depth
(defaults toInfinity
). When stringifying a value, if the current depth exceeds the specifieddepth
, showify checks if further recursion is necessary. If recursion is not needed, it simply stringifies the value as is. If further recursion is needed, showify displays[${className}]
and stops further recursion. A special case occurs when the value’s[[Prototype]]
isnull
, in which case the value is displayed as[Object: null prototype]
. - Break length: Lines are broken at
breakLength
(defaults to80
) characters if possible. When stringifying a value, showify first tries the inline format. If the inline format doesn't fit within thebreakLength
, it switches to the multiline format. This process is repeated recursively for each child value. - Circular references: Objects with circular references are displayed with a reference pointer. For example,
<ref *1> { foo: [ [Circular *1] ], bar: <ref *2> { inner: [Circular *2], obj: [Circular *1] } }
. - Extra keys: Extra keys of any special object (e.g., wrapper objects for primitives, errors, promises, functions, arrays) are displayed. For example,
[Function: foo] { bar: "baz" }
or[1, 2, 3, foo: "bar"]
. - Class name: An object’s
${className}
is defined as:- If the object is a prototype of a class (by checking if
value === value.constructor.prototype
),Object.getPrototypeOf(value).constructor.name
. - Otherwise,
value.constructor.name
. - If the name from the above two steps is empty,
"Object"
.
- If the object is a prototype of a class (by checking if
Symbol.toStringTag
: If an object has aSymbol.toStringTag
property that is not already shown and is not equal to its${className}
, it is displayed as the following:- For
Date
s andRegExp
s, it is displayed with brackets around after the class name, e.g.,Date [MyTag] 2025-02-13T11:42:41.196Z
orMyRegExp [MyTag] /(?:)/ { foo: 'bar' }
. - For
Error
s, it is displayed with brackets around after the error name, e.g.,Error [MyTag]: error message\n at ...
or[TypeError [MyTag]: error message]
. - For ES6 classes, it is displayed with brackets around after the class name, e.g.,
[class MyClass [MyTag]]
or[class MyClass [MyTag] extends MySuperClass]
. - For other objects, the tag is displayed with brackets around after its original prefix (if it does not have a prefix, the prefix is defined as
${className}
), e.g.,Object [MyTag] { foo: "bar" }
or[String: "foo"] [MyTag]
.
- For
Primitive values:
- Undefined:
undefined
is displayed asundefined
. Gray ifcolors
istrue
. - Null:
null
is displayed asnull
. Bold ifcolors
istrue
. - Boolean:
true
andfalse
are displayed astrue
andfalse
, respectively. Yellow ifcolors
istrue
. - Number: Numbers are displayed as their literal values, e.g.,
42
. Yellow ifcolors
istrue
. - BigInt: BigInts are displayed as their literal values with n suffix, e.g.,
42n
. Yellow ifcolors
istrue
. - String: Strings are displayed as their literal values with quotes based on the
quoteStyle
option, e.g.,"foo"
. Green ifcolors
istrue
. - Multi-line strings: Multi-line strings are broken into
"${s1}\n" + "${s2}\n" + ...
if its indentation plus the length of the string exceedsbreakLength
. Each part is colorized separately ifcolors
istrue
, and+
s are not colorized. - Long strings: If
maxStringLength
is set and the string is longer thanmaxStringLength
, it is displayed as"${s}"... ${n} more character${n === 1 ? "" : "s"}
, e.g.,"foo bar"... 3 more characters
. This also applies to multi-line strings, e.g.,"foo\n" + "bar"... 1 more character"
. - Symbol: Symbols are displayed as their literal values, e.g.,
Symbol(foo)
. Green ifcolors
istrue
. - Wrapper objects for primitives: Wrapped primitive values are displayed as
[${Type}: ${value}]
, e.g.,[String: "foo"]
. Their colors are the same as their primitive values ifcolors
istrue
. If a wrapped primitive’s${className}
is not equal to its${Type}
, it is displayed as[${Type} (${className}): ${value}]
, e.g.,[String (MyString): "foo"]
.
Special objects:
- Date:
Date
objects are displayed asdate.toISOString()
. If${className}
is not"Date"
, it is displayed as${className} ${date.toISOString()}
. - RegExp:
RegExp
objects are displayed asre.toString()
. If${className}
is not"RegExp"
, it is displayed as${className} ${re.toString()}
. - Error:
Error
objects are displayed aserror.stack
if available and valid, or[${error.stack}]
iferror.stack
is available but invalid, or[${className}: ${error.message}]
if message is available, or[${className}]
otherwise. - Promise:
Promise
objects are displayed asPromise { <state unknown> }
. - ArrayBuffer:
[Uint8Contents]
andbyteLength
are displayed forArrayBuffer
objects. For example,ArrayBuffer { [Uint8Contents]: <2a 00 00 00 00 00 00 00>, byteLength: 8 }
. - DataView:
byteLength
,byteOffset
andbuffer
are displayed forDataView
objects. For example,DataView { byteLength: 8, byteOffset: 0, buffer: ArrayBuffer { [Uint8Contents]: <2a 00 00 00 00 00 00 00>, byteLength: 8 } }
.
Classes and functions:
- ES6 Class: If a callable (i.e.,
typeof value === "function"
) object is an ES6 class, it is displayed as[class ${className}]
or[class (anonymous)]
if the class name is not available, or[class ${className} extends ${superClassName}]
if the class has a superclass and the superclass name is available. - Function: Other callables are identified as functions. A function is displayed as
[${Type}: ${functionName}]
iffunctionName
is available, or[${Type} (anonymous)]
otherwise.${Type}
isFunction
for regular functions,AsyncFunction
for async functions,GeneratorFunction
for generator functions, andAsyncGeneratorFunction
for async generator functions. For functions that${className}
is not equal to its${Type}
, they are displayed with a${className}
suffix, e.g.,[Function: anonymous] MyFn
fornew class MyFn extends Function {}()
, or[Function (anonymous)] Object
forFunction.prototype
, or[GeneratorFunction: anonymous] MyGenFn [GeneratorFunction]
fornew class MyGenFn extends function* () {}.constructor {}()
.
Collections:
- Array:
Array
objects are displayed as[e1, e2, ...]
. Empty slots are displayed as<${n} empty item${n === 1 ? "" : "s"}>
. For subclasses ofArray
or typed arrays, the${className}
is displayed with its size, e.g.,MyArray(4) [1, <2 empty items>, 2, { foo: "bar" }]
orUint8Array(3) [1, 2, 3]
. - Long Arrays: If
maxArrayLength
is set and the array has more elements thanmaxArrayLength
, it is displayed as[${e1}, ${e2}, ... ${n} more item${n === 1 ? "" : "s"}]
, e.g.,[1, 2, 3, 4, ... 2 more items]
. - Map:
Map
objects are displayed asMap(${size}) { key1 => value1, key2 => value2, ... }
. - Set:
Set
objects are displayed asSet(${size}) { value1, value2, ... }
. - WeakMap/WeakSet:
WeakMap
andWeakSet
objects are displayed as${className} { <items unknown> }
.
Regular objects:
- Module:
Module
objects are displayed as[Module]
if its[[Prototype]]
is not null, or[Module: null prototype]
otherwise. - Object: Other objects are displayed as
{ key: value1, "non identifier key": value2, [Symbol(id)]: value3, ... }
.${className}
is displayed if it’s not"Object"
and no prefix is already defined, e.g.,MyClass { key1: value1, key2: value2, ... }
. Objects with a null[[Prototype]]
are displayed as[Object: null prototype]
. - Object keys: String keys are displayed as
key
if they are valid identifiers, or"key"
otherwise. Symbol keys are displayed as[Symbol(key)]
. If a key is non-enumerable, it is displayed as[key]
(NOTE: non-enumerable keys are only displayed ifshowHidden
is not"none"
). - Getter/Setter: Getters and setters are displayed as
[Getter/Setter]
,[Getter]
, or[Setter]
. For example,{ foo: [Getter/Setter], bar: [Getter] }
. Ifgetters
is not"none"
, getters are further inspected, e.g.,{ foo: [Getter] { bar: "baz" } }
. Note that getters might throw an error, and in such cases, the error message is displayed if the thrown value is an object with amessage
property, e.g.,{ foo: [Getter: <Inspection threw (error message)>] }
.
Option | Type | Default | Description |
---|---|---|---|
depth |
number |
Infinity |
Maximum recursion depth of the object to be inspected, similar to util.inspect . |
indent |
number |
0 |
Number of spaces to indent the output. If indent is 0 , the output is not indented. |
breakLength |
number |
80 |
Maximum line length before breaking. This option is ignored if indent is 0 . |
showHidden |
"none" | "always" | "exclude-meta" | boolean |
"none" |
Whether to show hidden (non-enumerable) properties, should be "none" , "always" , or "exclude-meta" . If set to "exclude-meta" , it behaves like "always" but removes common meta properties like the length property of arrays. For compatibility with Node.js’s util.inspect , true is also accepted as "always" , and false is also accepted as "none" , but it is recommended to use the string values for clarity. |
getters |
"none" | "get" | "set" | "all" |
"none" |
Whether to inspect getters. If set to "get" , only getters without a corresponding setter are inspected. If set to "set" , only getters with a corresponding setter are inspected. If set to "all" , all getters are inspected. |
sorted |
boolean |
false |
Whether to sort the keys of objects (including Map s and Set s) in the resulting string. |
omittedKeys |
Set<string | symbol> |
new Set() |
A set of keys to omit from the output. Note that this option is not recursive and only omits the top-level keys. |
quoteStyle |
"single" | "double" | "backtick" |
["double", "single", "backtick"] |
Preferred quote style for strings. If an array is provided, showify tries each quote style in order. |
quoteKeys |
"auto" | "always" |
"auto" |
Whether to quote object keys. If set to "auto" , object keys are quoted only when necessary. |
numericSeparator |
"none" | string |
"none" |
The thousands separator for numbers (including BigInts), e.g., "," or "_" . If set to "none" , no separator is used. |
trailingComma |
"none" | "always" | "auto" |
"none" |
Whether to add a trailing comma to the last element of an array or object. If set to "auto" , a trailing comma is added only when the last item is on a separate line. |
arrayBracketSpacing |
boolean |
false |
Whether to add spaces inside the brackets of arrays. |
objectCurlySpacing |
boolean |
true |
Whether to add spaces inside the curly braces of objects. |
referencePointer |
boolean |
true |
Whether to display circular references with a reference pointer. If set to false , circular references are displayed as [Circular] . |
maxArrayLength |
number |
Infinity |
Maximum number of array elements to display. If an array has more elements than maxArrayLength , it is displayed as [${e1}, ${e2}, ... ${n} more item${n === 1 ? "" : "s"}] . |
maxStringLength |
number |
Infinity |
Maximum length of a string to display. If a string is longer than maxStringLength , it is displayed as "${s}"... ${n} more character${n === 1 ? "" : "s}" . |
colors |
boolean |
false |
Whether to enable ANSI colors. |
styles |
Object |
{} |
Custom styles for different types of values. Valid keys are string , symbol , number , bigint , boolean , null , undefined , date , regexp , and special . Valid colors are bold , dim , reset , black , blue , cyan , gray , green , magenta , red , white , and yellow . |
serializers |
Serializer[] |
[] |
Custom serializers for your own types. See the custom serializers section for more details. |
showify supports custom serializers through the serializers
option in the show()
function. A serializer must implement the following interface:
interface Serializer {
if: (value: object, options: SerializerOptions) => boolean;
then: (
value: object,
options: SerializerOptions,
expand: (value: unknown, options?: Partial<SerializerOptions>) => Node,
) => Node;
}
You may wonder what Node
is. A Node
is a tree-like structure that represents the value to be stringified:
type Node =
| { type: "circular"; ref: object }
| { type: "text"; value: string; ref?: object }
| { type: "variant"; inline: Node; wrap: Node; ref?: object }
| { type: "sequence"; values: Node[]; ref?: object }
| { type: "between"; values: Node[]; open?: Node; close?: Node; ref?: object };
Internally, showify interacts with Nodes rather than directly with strings:
╔═══════╗ ╔═════════════════════╗ ╔════════════════════════╗
║ value ║ → ║ Build tree of nodes ║ → ║ Render nodes to string ║
╚═══════╝ ╚═════════════════════╝ ╚════════════════════════╝
serializers
come into play during the second step, where the tree of nodes is built. The if
function of a serializer is called with the value to be serialized. If it returns true
, the then
function is invoked to build the tree of nodes. The expand
function is a helper that recursively serializes child values.
You can use the exported serializer
helper function and the Node
object to create your own serializers:
import { Node as SerializerNode, serializer, show } from "showify";
const { circular, text, variant, sequence, pair, between } = SerializerNode;
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
const pointSer = serializer({
if: (value) => value instanceof Point,
then: (value) => text(`Point(${value.x}, ${value.y})`),
});
class Box {
constructor(data) {
this.data = data;
}
}
const boxSer = serializer({
if: (value) => value instanceof Box,
then: (value, options, expand) =>
sequence([text("Box("), expand(value.data), text(")")]),
});
const obj = {
point: new Point(1, 2),
box1: new Box({ foo: "bar" }),
box2: new Box(new Point(3, 4)),
};
console.log(show(obj, { indent: 2, serializers: [pointSer, boxSer] }));
// {
// point: Point(1, 2),
// box1: Box({ foo: "bar" }),
// box2: Box(Point(3, 4))
// }
This example shows how to use custom serializers to display Point
and Box
objects in a custom format. The pointSer
serializer formats Point
objects as Point(x, y)
, and the boxSer
serializer formats Box
objects as Box(...)
.
We’ve seen how to use text
and sequence
to build simple nodes. There are other nodes we haven’t used yet. Here’s a brief introduction to all these nodes:
circular
: Represents a circular reference. Normally, you don’t need to create this node manually in a serializer. All nodes returned by a custom serializer automatically get aref
property, which helps detect circular references.text
: Represents a text node. Thevalue
property holds the text to be displayed.variant
: Represents a “variant” node. Some objects format differently depending on whether they are inline or multiline. For example, whentrailingComma
is"auto"
, a trailing comma is added only when the last item is on a separate line. This can be represented withvariant(inlineNode, multilineNode)
.sequence
: Represents a sequence of nodes. Thevalues
property is an array of nodes to be displayed in order. Unlikebetween
,sequence
does not break lines between nodes.pair(left, right)
is an alias ofsequence([left, right])
.between
: Represents a sequence of nodes. Thevalues
property is an array of nodes to be displayed in order. Theopen
andclose
properties are optional nodes to be displayed before and after thevalues
nodes. Don’t be confused by the name,between
can be used likesequence
withoutopen
andclose
. The key difference is thatbetween
breaks lines between nodes according to theindent
andbreakLength
options.
So far, you might wonder how sequence
and between
differ. Let’s use a Pair
class as an example to demonstrate the difference:
class Pair {
constructor(left, right) {
this.left = left;
this.right = right;
}
}
const badPairSer = serializer({
if: (value) => value instanceof Pair,
then: (value, options, expand) =>
sequence([
text("("),
expand(value.left),
text(", "),
expand(value.right),
text(")"),
]),
});
const goodPairSer = serializer({
if: (value) => value instanceof Pair,
then: (value, options, expand) =>
between([
pair(expand(value.left), text(", ")),
expand(value.right)
], text("("), text(")")),
});
At first glance, badPairSer
and goodPairSer
seem identical, and they produce the same output for short values:
const p = new Pair("left", "right");
console.log(show(p, { indent: 2, serializers: [badPairSer] }));
// ("left", "right")
console.log(show(p, { indent: 2, serializers: [goodPairSer] }));
// ("left", "right")
However, they behave differently when the Pair
object is displayed in multiline format:
const s1 = "This is a very long string that will break the line";
const s2 = "This is another very long string that will break the line";
const p = new Pair(s1, s2);
console.log(show(p, { indent: 2, serializers: [badPairSer] }));
// ("This is a very long string that will break the line", "This is another very long string that will break the line")
console.log(show(p, { indent: 2, serializers: [goodPairSer] }));
// (
// "This is a very long string that will break the line",
// "This is another very long string that will break the line"
// )
Although the inline format exceeds the breakLength
limit, badPairSer
does not break lines between its children, so the output stays on a single line. On the other hand, goodPairSer
uses between
, which breaks lines between its children, resulting in a multiline output.
Here’s a more interesting example where child nodes themselves can be multiline:
const obj1 = { foo: "bar", bar: "baz", baz: "quxx" };
const obj2 = { qux: "baz", baz: "bar", bar: "foo" };
const p = new Pair(obj1, obj2);
console.log(show(p, { indent: 2, serializers: [badPairSer] }));
// ({
// foo: "bar",
// bar: "baz",
// baz: "quux"
// }, {
// qux: "baz",
// baz: "bar",
// bar: "foo"
// })
console.log(show(p, { indent: 2, serializers: [goodPairSer] }));
// (
// { foo: "bar", bar: "baz", baz: "quux" },
// { qux: "baz", baz: "bar", bar: "foo" }
// )
While the outputs might be different from what you expect, the underlying principle remains the same. The badPairSer
serializer uses sequence
, which does not break lines between direct children. Therefore, it tries to break lines within the children themselves when possible. In contrast, goodPairSer
uses between
, which handles line breaks between children, so it doesn’t require further instruction for child elements to break lines.
The goodPairSer
is still not “good” enough — the separator is still ", "
(with a space after) when multiline, which is not what we want. We can use variant
to solve this problem:
const goodPairSer = serializer({
if: (value) => value instanceof Pair,
then: (value, options, expand) =>
variant(
// The inline format
sequence([
text("("),
expand(value.left),
text(", "), // A comma with a space after
expand(value.right),
text(")"),
]),
// The multiline format
between([
pair(expand(value.left), text(",")), // No space after the comma
expand(value.right)
], text("("), text(")")),
),
});
The variant
node enforces inline formatting for the first argument and multiline formatting for the second argument. Using sequence
for the inline format is just a personal preference; you can use between
instead, as they behave similarly in inline format.
The options
argument in then
function and the second optional argument in expand
are almost identical to the options passed to show
, which we'll refer to as SerializerOptions
below. The SerializerOptions
is a subset of the options passed to show
, with the following differences:
indent
,breakLength
, andreferencePointer
are not available inSerializerOptions
, as they only affect rendering the nodes to strings and do not interact with the process of building the node tree.level
is available inSerializerOptions
, indicating the current depth of the object being inspected.ancestors
is available inSerializerOptions
, which is an array of the ancestors of the current value. This is useful for detecting circular references.- An object named
c
is available inSerializerOptions
, containing functions to colorize strings using ANSI colors. These functions are similar to chalk, such asc.bold
,c.cyan
, and so on, as well asc.number
,c.special
, etc., which are aliases for corresponding styles in thestyles
option. Note that whencolors
isfalse
, all color functions are no-op functions that return the input string unchanged.
showify automatically handles level
increment in expand
, and internally triggers a signal to stop recursion when level
exceeds depth
, so you don't need to worry about it. Circular references are also automatically detected — the current value is added to the ancestors
array, and the ref
property of each node returned is set to its value to detect circular references. As shown earlier, since the tree of nodes is built in two stages, indentation and line breaking are handled by between
, so you don't need to worry about those either.
Here’s an example showing how to use the options
argument in then
and expand
:
const ser = serializer({
if: (value, { omittedKeys }) =>
"_tag" in value &&
typeof value._tag === "string" &&
// Detect if `_tag` is already omitted to avoid infinite recursion
!omittedKeys.has("_tag"),
then: (val, { ancestors, c, level }, expand) => {
const tag = val._tag;
return sequence([
text(c.blue(tag)),
text("("),
expand(val, {
// Reset `level` and force expansion the object with a depth of 1
level: 0,
depth: 1,
// Omit the `_tag` key when expanding the object
omittedKeys: new Set(["_tag"]),
// Avoid auto-adding current value to `ancestors` to avoid circulars
ancestors: [...ancestors],
}),
text(")"),
]);
},
});
const obj = { _tag: "Some", value: 42, nested: { foo: { bar: "baz" } } };
console.log(show(obj, { indent: 2, serializers: [ser], colors: true }));
// Some({ value: 42, nested: { foo: [Object] } })
Among the libraries that stringify JavaScript values, showify is the most complete and universal, working in any JavaScript runtime environment and producing output similar to util.inspect
in Node.js.
object-inspect and pretty-format are popular libraries for stringifying JavaScript values, especially the latter, which is used by Jest’s .matchSnapshot()
method. However, try printing several random objects with showify (e.g., console.log(show(await import("node:util"), { indent: 2, colors: true }))
), and you’ll quickly see that showify produces much cleaner and more readable output compared to these libraries.
As of now, showify is the only library that handles auto line breaking, circular references with reference pointers, hidden properties, proper getters/setters display, and much more. showify also offers a lightweight implementation under 3kB minified+gzipped, perfect for embedding in other libraries or frameworks.
Compared to util.inspect
in Node.js, showify supports most of the features util.inspect
offers, along with additional options not available in util.inspect
. However, there are some limitations. Since showify is designed to work universally across browsers and other JavaScript runtimes, it cannot support some Node.js-specific features like inspecting a Proxy or synchronously inspecting a Promise.
For a complete list of features, see the comparison table.
While showify behaves slightly differently from util.inspect
in rare cases, you can achieve similar default behavior by setting the following options:
show(value, {
depth: 2,
indent: 2,
quoteStyle: ["single", "double", "backtick"],
arrayBracketSpacing: true,
maxArrayLength: 100,
maxStringLength: 10000,
});
This project is licensed under the Mozilla Public License Version 2.0 (MPL 2.0).
For details, please refer to the LICENSE
file.
In addition to the open-source license, a commercial license is available for proprietary use. If you modify this library and do not wish to open-source your modifications, or if you wish to use the modified library as part of a closed-source or proprietary project, you must obtain a commercial license.
For details, see COMMERCIAL_LICENSE.md
.