Package Exports
- seroval
Readme
seroval
Stringify JS values
Install
npm install --save serovalyarn add serovalpnpm add serovalUsage
import { serialize } from 'seroval';
const object = {
number: [Math.random(), -0, NaN, Infinity, -Infinity],
string: ['hello world', '<script>Hello World</script>'],
boolean: [true, false],
null: null,
undefined: undefined,
bigint: 9007199254740991n,
array: [,,,], // holes
regexp: /[a-z0-9]+/i,
date: new Date(),
map: new Map([['hello', 'world']]),
set: new Set(['hello', 'world']),
};
// self cyclic references
// recursive objects
object.self = object;
// recursive arrays
object.array.push(object.array);
// recursive maps
object.map.set('self', object.map);
// recursive sets
object.set.add(object.set);
// mutual cyclic references
object.array.push(object.map);
object.map.set('mutual', object.set);
object.set.add(object.array);
const result = serialize(object);
console.log(result);Output (as a string):
((h,j,k,m)=>(m={number:[0.9039984824241858,-0,NaN,1/0,-1/0],string:["hello world","\x3Cscript>Hello World\x3C/script>"],boolean:[!0,!1],null:null,undefined:void 0,bigint:9007199254740991n,array:h=[,,,,j=new Map([["hello","world"],["mutual",k=new Set(["hello","world"])]])],regexp:/[a-z0-9]+/i,date:new Date("2023-03-16T11:57:24.849Z"),map:j,set:k},h[3]=h,j.set("self",j),k.add(k).add(h),m.self=m,m))()
// Formatted for readability
((h, j, k, m) => (m = {
number: [0.9039984824241858, -0, NaN, 1 / 0, -1 / 0],
string: ["hello world", "\x3Cscript>Hello World\x3C/script>"],
boolean: [!0, !1],
null: null,
undefined: void 0,
bigint: 9007199254740991n,
array: h = [, , , , j = new Map([
["hello", "world"],
["mutual", k = new Set(["hello", "world"])]
])],
regexp: /[a-z0-9]+/i,
date: new Date("2023-03-16T11:57:24.849Z"),
map: j,
set: k
}, h[3] = h, j.set("self", j), k.add(k).add(h), m.self = m, m))()Mutual cyclic example
import { serialize } from 'seroval';
const a = new Map([['name', 'a']]);
const b = new Map([['name', 'b']]);
const c = new Map([['name', 'c']]);
const d = new Map([['name', 'd']]);
c.set('left', a);
d.set('left', a);
c.set('right', b);
d.set('right', b);
a.set('children', [c, d]);
b.set('children', [c, d]);
const result = serialize({ a, b, c, d });
console.log(result);Output (as a string):
((h,j,k,m,o,q)=>(q={a:h=new Map([["name","a"],["children",[j=new Map([["name","c"],["right",o=new Map([["name","b"],["children",k=[,m=new Map([["name","d"]])]]])]]),m]]]),b:o,c:j,d:m},j.set("left",h),k[0]=j,m.set("left",h).set("right",o),q))()
// Formatted
((h, j, k, m, o, q) => (q = {
a: h = new Map([
["name", "a"],
["children", [j = new Map([
["name", "c"],
["right", o = new Map([
["name", "b"],
["children", k = [, m = new Map([
["name", "d"]
])]]
])]
]), m]]
]),
b: o,
c: j,
d: m
}, j.set("left", h), k[0] = j, m.set("left", h).set("right", o), q))()Deserialization
import { serialize, deserialize } from 'seroval';
const value = undefined;
console.log(deserialize(serialize(value)) === value);JSON
serialize and deserialize is great for server-to-client communication, but what about the other way? serialize may cause an RCE if used as a payload for requests. seroval includes toJSON and fromJSON as an alternative form of serialization.
First example above outputs the following json
import { toJSON } from 'seroval';
// ...
const result = toJSON(object);
console.log(result);{"t":{"t":8,"i":0,"d":{"k":["number","string","boolean","null","undefined","bigint","array","regexp","date","map","set","self"],"v":[{"t":7,"i":1,"a":[{"t":0,"s":0.7047255726239685},{"t":0,"s":"-0"},{"t":0,"s":null},{"t":0,"s":"1/0"},{"t":0,"s":"-1/0"}]},{"t":7,"i":2,"a":[{"t":0,"s":"\"hello world\""},{"t":0,"s":"\"\\x3Cscript>Hello World\\x3C/script>\""}]},{"t":7,"i":3,"a":[{"t":0,"s":"!0"},{"t":0,"s":"!1"}]},{"t":0,"s":null},{"t":0,"s":"void 0"},{"t":1,"s":"9007199254740991n"},{"t":7,"i":4,"a":[null,null,null,{"t":2,"i":4},{"t":6,"i":5,"d":{"k":[{"t":0,"s":"\"hello\""},{"t":0,"s":"\"self\""},{"t":0,"s":"\"mutual\""}],"v":[{"t":0,"s":"\"world\""},{"t":2,"i":5},{"t":5,"i":6,"a":[{"t":0,"s":"\"hello\""},{"t":0,"s":"\"world\""},{"t":2,"i":6},{"t":2,"i":4}]}],"s":3}}]},{"t":4,"i":7,"s":"/[a-z0-9]+/i"},{"t":3,"i":8,"s":"2023-03-16T12:01:29.836Z"},{"t":2,"i":5},{"t":2,"i":6},{"t":2,"i":0}],"s":12}},"r":0,"i":true,"f":8191,"m":[4,5,6,0]}Then you can feed it to fromJSON:
import { fromJSON } from 'seroval';
const revived = fromJSON(result);Alternatively, if you want to compile the JSON output to JS (like deserialize), you can use compileJSON
import { compileJSON, deserialize } from 'seroval';
const code = compileJSON(result);
const revived = deserialize(code);Promise serialization
seroval allows Promise serialization through serializeAsync and toJSONAsync.
import { serializeAsync } from 'seroval';
const value = Promise.resolve(100);
const result = await serializeAsync(value); // "Promise.resolve(100)"
console.log(await deserialize(result)); // 100Note
serovalcan only serialize the resolved value and so the output will always be usingPromise.resolve. If the Promise fulfills with rejection, the rejected value is thrown before serialization happens.
Supports
The following values are the only values accepted by seroval:
- Exact values
NaNInfinity-Infinity-0
- Primitives
numberstringbooleannullundefinedbigint
Array+ holesObjectRegExpDateMapSet
TypedArrayInt8ArrayInt16ArrayInt32ArrayUint8ArrayUint16ArrayUint32ArrayUint8ClampedArrayFloat32ArrayFloat64ArrayBigInt64ArrayBigUint64Array
ErrorAggregateErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError
Promise(withserializeAsync)Iterable- Cyclic references (both self and mutual)
Compat
serialize, serializeAsync, toJSON and toJSONAsync can accept a { disabledFeatures: number } option. The disabledFeatures defines how various code output of serialize, serializeAsync and compileJSON.
import { serialize, Feature } from 'seroval';
const y = Object.create(null);
y.self = y;
y.example = 'Hello World';
function serializeWithTarget(value, disabledFeatures) {
const result = serialize(value, {
disabledFeatures,
});
console.log(result);
}
serializeWithTarget(y, Feature.ArrowFunction | Feature.ObjectAssign);
serializeWithTarget(y, 0);(function(h){return (h=Object.create(null),h.self=h,h.example="Hello World",h)})()
(h=>(h=Object.assign(Object.create(null),{example:"Hello World"}),h.self=h,h))()disabledFeatures uses bit flags for faster checking, so if you need to disable multiple features, you can use the logical OR symbol (|).
Here's an ES2017 flag:
import { serialize, Feature } from 'seroval';
const ES2017FLAG =
Feature.AggregateError // ES2021
| Feature.BigInt // ES2020
| Feature.BigIntTypedArray // ES2020;
serialize(myValue, {
disabledFeatures: ES2017FLAG,
})By default, all feature flags are enabled. The following are the feature flags and their behavior when disabled:
AggregateError- Compiles down to
Errorinstead.
- Compiles down to
ArrayPrototypeValues- Used for iterables, uses
Symbol.iteratorinstead (ifSymbolIteratoris not set).
- Used for iterables, uses
ArrowFunction- Uses function expressions for top-level and for deferred Promise values
- method shorthands (if
MethodShortandis not set) or function expressions for iterables.
BigInt- Throws when attempted to use, includes
BigIntTypedArray
- Throws when attempted to use, includes
ErrorPrototypeStack- Skipped when detected.
Map- Throws when attempted to use.
MethodShorthand- Uses function expressions instead.
ObjectAssign- Uses manual object assignments instead.
Promise- Throws when attempted to use in
serializeAsyncandtoJSONAsync.
- Throws when attempted to use in
Set- Throws when attempted to use.
SymbolIterator- Throws when attempted to use.
TypedArray- Throws when attempted to use.
BigIntTypedArray- Throws when attempted to use
- Also throws if
BigIntis disabled.
Sponsors
License
MIT © lxsmnsyc