isotropic-value-to-source
v0.10.0
Published
Serialize a value to a source code string
Maintainers
Readme
isotropic-value-to-source
A utility that converts JavaScript values into formatted, syntactically correct source code strings.
Why Use This?
- Code Generation: Generate properly formatted JavaScript code programmatically
- Beyond JSON.stringify: Correctly handles many JavaScript types including ArrayBuffer, BigInt, DataView, Map, RegExp, Set, Symbol, Temporal, typed arrays, URL, URLSearchParams, and more
- Linter-Friendly: Produces code that passes Isotropic linting rules
- Pretty Formatting: Consistent indentation, line breaks, and property sorting
- Configurable Output: Customize formatting, property order, quote styles, and more
- Circular References: Safely handles circular structures
- Error Serialization: Converts any error (built-in, subclass, isotropic-error, or custom) into a plain object capturing its message, name, stack, and nested causes
- Unserializable Values: Marks values that cannot be represented as source (such as
PromiseandWeakMap) with a configurable token - Symbol Keys & Null Prototypes: Optionally serializes symbol-keyed properties and preserves null-prototype objects
- Method Preservation: Option to include functions in the serialized output
Installation
npm install isotropic-value-to-sourceUsage
import _valueToSource from 'isotropic-value-to-source';
{
// Basic usage
const code = _valueToSource({
name: 'John',
age: 30,
hobbies: [
'reading',
'cycling'
],
active: true
});
console.log(code);
// Output:
// {
// active: true,
// age: 30,
// hobbies: [
// 'reading',
// 'cycling'
// ],
// name: 'John'
// }
// Custom configuration
const customCode = _valueToSource(data, {
doubleQuote: true, // Use double quotes instead of single quotes
includeUndefinedProperties: true, // Include undefined properties in output
indentString: ' ' // Use 2 spaces for indentation
});
}API
valueToSource(value, options)
Converts a JavaScript value to a source code string.
Parameters
value(Any): The value to convert to a source code stringoptions(Object, optional): Configuration options object:circularReferenceToken(String): Token to use for circular references. Default:'CIRCULAR_REFERENCE'doubleQuote(Boolean): Whether to use double quotes instead of single quotes. Default:falseincludeFunctions(Boolean): Whether to include functions in the output. Default:falseincludeSymbolKeys(Boolean): Whether to include symbol-keyed properties in the output. Only enumerable symbol-keyed properties are included. Default:falseincludeUndefinedProperties(Boolean): Whether to include properties with undefined values. Default:falseindentLevel(Number): Initial indentation level. Default:0indentString(String): String to use for indentation. Default:' '(4 spaces)lineEnding(String): String to use for line endings. Default:'\n'propertySort(Object): Options for property sorting:caseSensitive(Boolean): Whether to sort properties case-sensitively. Default:falsedirection(String): Sort direction, either'asc'or'desc'. Default:'asc'ignoreSpecialCharacters(Boolean): Whether to ignore special characters when sorting. Default:trueprefixPositions(Object): Map of prefixes to their sort positions ('first'or'last'). Default:{ _: 'last' }
unserializableToken(String): Token to use in the output in place of a value that cannot be represented as source code (see Unserializable Values). Default:'UNSERIALIZABLE'
Returns
- (String): A JavaScript source code string representing the input value
Examples
Basic Types
import _valueToSource from 'isotropic-value-to-source';
// Numbers
_valueToSource(42); // "42"
_valueToSource(Infinity); // "Infinity"
_valueToSource(NaN); // "NaN"
_valueToSource(-0); // "-0" (preserves negative zero)
// Strings
_valueToSource('hello'); // "'hello'"
_valueToSource("quote's"); // "'quote\\'s'"
// Booleans
_valueToSource(true); // "true"
_valueToSource(false); // "false"
// Undefined and null
_valueToSource(undefined); // "void null"
_valueToSource(null); // "null"
// BigInt
_valueToSource(42n); // "42n"
// Symbols
_valueToSource(Symbol()); // "Symbol()"
_valueToSource(Symbol('description')); // "Symbol('description')"
_valueToSource(Symbol.for('key')); // "Symbol.for('key')"Complex Types
import _valueToSource from 'isotropic-value-to-source';
// Arrays
_valueToSource([1, 2, 3]);
// [
// 1,
// 2,
// 3
// ]
// Nested arrays
_valueToSource([[1], [2, 3]]);
// [
// [
// 1
// ],
// [
// 2,
// 3
// ]
// ]
// Objects
_valueToSource({ a: 1, b: 2 });
// {
// a: 1,
// b: 2
// }
// Nested objects
_valueToSource({ a: { b: { c: 3 } } });
// {
// a: {
// b: {
// c: 3
// }
// }
// }
// Dates are converted to a Temporal.Instant (Date is no longer a supported output type)
_valueToSource(new Date('2023-01-01'));
// Temporal.Instant.from('2023-01-01T00:00:00.000Z')
// Regular expressions
_valueToSource(/^test$/gi);
// /^test$/gi
// Sets
_valueToSource(new Set([1, 2, 3]));
// new Set([
// 1,
// 2,
// 3
// ])
// Maps
_valueToSource(new Map([['key1', 'value1'], ['key2', 'value2']]));
// new Map([
// [
// 'key1',
// 'value1'
// ],
// [
// 'key2',
// 'value2'
// ]
// ])
// Temporal (all Temporal types are supported)
_valueToSource(Temporal.Instant.from('2023-01-01T00:00:00Z'));
// Temporal.Instant.from('2023-01-01T00:00:00Z')
_valueToSource(Temporal.ZonedDateTime.from('2023-01-01T12:30:00-05:00[America/New_York]'));
// Temporal.ZonedDateTime.from('2023-01-01T12:30:00-05:00[America/New_York]')
_valueToSource(Temporal.PlainDate.from('2023-01-01'));
// Temporal.PlainDate.from('2023-01-01')
_valueToSource(Temporal.PlainTime.from('12:30:45'));
// Temporal.PlainTime.from('12:30:45')
_valueToSource(Temporal.PlainDateTime.from('2023-01-01T12:30:45'));
// Temporal.PlainDateTime.from('2023-01-01T12:30:45')
_valueToSource(Temporal.Duration.from('P1Y2M3DT4H5M6S'));
// Temporal.Duration.from('P1Y2M3DT4H5M6S')
// (Temporal.PlainYearMonth and Temporal.PlainMonthDay are supported too)
// Typed arrays (every typed array variant is supported)
_valueToSource(new Uint8Array([1, 2, 3]));
// new Uint8Array([
// 1,
// 2,
// 3
// ])
_valueToSource(new BigInt64Array([1n, -2n]));
// new BigInt64Array([
// 1n,
// -2n
// ])
// ArrayBuffer (reconstructed via a Uint8Array view)
_valueToSource(new Uint8Array([10, 20, 30]).buffer);
// new Uint8Array([
// 10,
// 20,
// 30
// ]).buffer
// DataView
_valueToSource(new DataView(new Uint8Array([1, 2, 3, 4]).buffer));
// new DataView(new Uint8Array([
// 1,
// 2,
// 3,
// 4
// ]).buffer)
// URL
_valueToSource(new URL('https://example.com/path'));
// new URL('https://example.com/path')
// URLSearchParams
_valueToSource(new URLSearchParams('a=1&b=2&a=3'));
// new URLSearchParams('a=1&b=2&a=3')Working with Functions
import _valueToSource from 'isotropic-value-to-source';
{
// Including function properties in objects
const object = {
farewell: function () {
return `Goodbye ${this.name}`;
},
greet () {
return `Hello ${this.name}`;
},
name: 'John'
};
_valueToSource(object, {
includeFunctions: true
});
// {
// farewell: function () {
// return `Goodbye ${this.name}`;
// },
// greet () {
// return `Hello ${this.name}`;
// },
// name: 'John'
// }
}
{
// Including functions in arrays
const array = [
() => 'arrow function',
function () {
return 'function expression';
}
];
_valueToSource(array, {
includeFunctions: true
});
// [
// () => 'arrow function',
// function () {
// return 'function expression';
// }
// ]
}When a property value is a method (shorthand, async, generator, or one whose name requires quoting or is computed), it is emitted as a method definition; other function values are emitted as function expressions or arrow functions. A few caveats apply when includeFunctions is true:
- A function's body is reproduced from its
toString()representation, so the original source's indentation and line endings are preserved rather than reformatted to match the surrounding output. - Property getters and setters are invoked during traversal (their returned values are serialized), not preserved as accessors.
- A method that has been assigned to a property whose name differs from the method's own name may not preserve the property name.
Boxed Primitives
Boxed primitive wrapper objects are unwrapped to their primitive values.
import _valueToSource from 'isotropic-value-to-source';
_valueToSource(new Number(5)); // "5"
_valueToSource(new String('hi')); // "'hi'"
_valueToSource(new Boolean(true)); // "true"Symbol Keys
By default, symbol-keyed properties are omitted. Set includeSymbolKeys to true to include enumerable symbol-keyed properties, which are emitted as computed keys after the string-keyed properties. Non-enumerable symbol-keyed properties are always omitted.
import _valueToSource from 'isotropic-value-to-source';
{
const object = {
name: 'config'
};
object[Symbol('tag')] = 'x';
object[Symbol.for('registered')] = 'y';
_valueToSource(object, {
includeSymbolKeys: true
});
// {
// name: 'config',
// [Symbol('tag')]: 'x',
// [Symbol.for('registered')]: 'y'
// }
}Null-Prototype Objects
Objects with a null prototype (for example, those created with Object.create(null)) are preserved as such in the output.
import _valueToSource from 'isotropic-value-to-source';
_valueToSource(Object.create(null));
// Object.create(null)
{
const object = Object.assign(Object.create(null), {
a: 1,
b: 2
});
_valueToSource(object);
// Object.assign(Object.create(null), {
// a: 1,
// b: 2
// })
}An own property named __proto__ is always emitted as a computed key (['__proto__']) so that it round-trips as a genuine own property instead of reassigning the prototype.
Errors
Any error instance (a built-in Error, a built-in subclass such as RangeError, an AggregateError, isotropic-error, or a custom error class) is serialized as a plain object capturing its properties. This includes the message, the stack trace, any own enumerable properties, and nested causes or aggregated errors (which are themselves serialized recursively). A name property is added when nothing else in the output already identifies the error type, so a RangeError remains recognizable. Cycles between errors are handled with the circularReferenceToken, just like any other circular structure.
The output is an ordinary object literal rather than a reconstructed error, so it neither imports nor depends on any particular error implementation.
import _valueToSource from 'isotropic-value-to-source';
_valueToSource(new RangeError('index out of bounds'));
// {
// message: 'index out of bounds',
// name: 'RangeError',
// stack: 'RangeError: index out of bounds\n at ...'
// }
// Nested causes are serialized recursively
_valueToSource(new Error('request failed', {
cause: new Error('connection refused')
}));
// {
// cause: {
// message: 'connection refused',
// name: 'Error',
// stack: 'Error: connection refused\n at ...'
// },
// message: 'request failed',
// name: 'Error',
// stack: 'Error: request failed\n at ...'
// }
// Aggregated errors (including the errors-within-details pattern) are serialized too
_valueToSource({
details: {
errors: [
new Error('first'),
new Error('second')
]
},
message: 'multiple failures'
});
// {
// details: {
// errors: [
// {
// message: 'first',
// name: 'Error',
// stack: '...'
// },
// {
// message: 'second',
// name: 'Error',
// stack: '...'
// }
// ]
// },
// message: 'multiple failures'
// }Unserializable Values
Some values cannot be represented as source code that reconstructs an equivalent value. These are replaced in the output with the unserializableToken (default 'UNSERIALIZABLE'), much like circular references. The values treated as unserializable are Promise, WeakMap, WeakSet, WeakRef, and FinalizationRegistry.
import _valueToSource from 'isotropic-value-to-source';
_valueToSource({
cache: new WeakMap(),
ok: true,
pending: Promise.resolve()
});
// {
// cache: UNSERIALIZABLE,
// ok: true,
// pending: UNSERIALIZABLE
// }
// Custom token
_valueToSource(new WeakMap(), {
unserializableToken: '/* unserializable */'
});
// /* unserializable */Handling Circular References
import _valueToSource from 'isotropic-value-to-source';
{
// Creating a circular structure
const object1 = {
name: 'Object 1'
},
object2 = {
name: 'Object 2',
reference: object1
};
object1.reference = object2; // Creates a circular reference
// Default handling
_valueToSource(object1);
// {
// name: 'Object 1',
// reference: {
// name: 'Object 2',
// reference: CIRCULAR_REFERENCE
// }
// }
// Custom circular reference token
_valueToSource(object1, {
circularReferenceToken: '/* circular */'
});
// {
// name: 'Object 1',
// reference: {
// name: 'Object 2',
// reference: /* circular */
// }
// }
}Customizing Output Format
import _valueToSource from 'isotropic-value-to-source';
{
const data = {
title: 'Example',
items: [
1,
2,
3
],
details: {
created: new Date('2023-01-01'),
active: true
}
};
// Change quote style and indentation
_valueToSource(data, {
doubleQuote: true,
indentString: ' ',
lineEnding: '\r\n'
});
// {
// details: {
// active: true,
// created: Temporal.Instant.from("2023-01-01T00:00:00.000Z")
// },
// items: [
// 1,
// 2,
// 3
// ],
// title: "Example"
// }
// Custom property sorting
_valueToSource(data, {
propertySort: {
caseSensitive: true, // Sort case-sensitively
direction: 'desc', // Sort in descending order
ignoreSpecialCharacters: false, // Don't ignore special characters
prefixPositions: {
title: 'first' // Put 'title' property first
}
}
});
}Code Generation
import _fs from 'node:fs/promises';
import _valueToSource from 'isotropic-value-to-source';
const _generateConfigModule = async ({
config,
filepath
}) => {
console.log(`Writing configuration module to ${filepath}`);
await _fs.writeFile(filepath, [
'// Auto-generated configuration file',
`// Generated on ${new Date().toISOString()}`,
'',
`export default ${_valueToSource(config)};`,
''
].join('\n'));
console.log(`Configuration module written to ${filepath}`);
};
{
// Usage
_generateConfigModule({
config: {
apiEndpoint: 'https://api.example.com',
features: {
analytics: {
enabled: true,
trackErrors: true
},
logging: true
},
retryAttempts: 3,
timeout: 30000,
},
filepath: './src/config.js'
});
}Differences from JSON.stringify
While JSON.stringify is great for serializing data for data interchange, valueToSource is designed for code generation:
| Feature | JSON.stringify | valueToSource |
| ------------------------------------------- | ---------------- | --------------------------- |
| Output format | JSON | JavaScript source code |
| Handles functions | ❌ | ✅ (with option) |
| Handles RegExp | ❌ | ✅ |
| Handles Map, Set | ❌ | ✅ |
| Handles Error | ❌ (becomes {}) | ✅ (as a plain object) |
| Handles Temporal types | ❌ | ✅ |
| Handles Date | ✅ (as a string) | ✅ (as a Temporal.Instant) |
| Handles typed arrays, ArrayBuffer, DataView | ❌ | ✅ |
| Handles URL, URLSearchParams | ❌ | ✅ |
| Handles Symbol | ❌ | ✅ |
| Handles symbol-keyed properties | ❌ | ✅ (with option) |
| Handles BigInt | ❌ | ✅ |
| Unwraps boxed primitives | ✅ | ✅ |
| Preserves null-prototype objects | ❌ | ✅ |
| Preserves negative zero | ❌ (becomes 0) | ✅ |
| Handles undefined | ❌ | ✅ (with option) |
| Circular references | ❌ (throws error) | ✅ (with token) |
| Unserializable values | ❌ (omits or throws) | ✅ (with token) |
| Pretty-printing | Limited | Advanced formatting options |
| Property sorting | ❌ | ✅ (with multiple options) |
| Quote style options | ❌ | ✅ |
Contributing
Please refer to CONTRIBUTING.md for contribution guidelines.
Issues
If you encounter any issues, please file them at https://github.com/ibi-group/isotropic-value-to-source/issues
