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.28952097444015235,-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-22T02:53:41.129Z"),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.28952097444015235, -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-22T02:53:41.129Z"),
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(JSON.stringify(result));{"t":{"t":16,"i":0,"d":{"k":["number","string","boolean","null","undefined","bigint","array","regexp","date","map","set","self"],"v":[{"t":15,"i":1,"l":5,"a":[{"t":0,"s":0.4350045546286634},{"t":5},{"t":8},{"t":6},{"t":7}]},{"t":15,"i":2,"l":2,"a":[{"t":1,"s":"hello world"},{"t":1,"s":"\\x3Cscript>Hello World\\x3C/script>"}]},{"t":15,"i":3,"l":2,"a":[{"t":2,"s":true},{"t":2,"s":false}]},{"t":3},{"t":4},{"t":9,"s":"9007199254740991"},{"t":15,"i":4,"l":5,"a":[null,null,null,{"t":10,"i":4},{"t":14,"i":5,"d":{"k":[{"t":1,"s":"hello"},{"t":1,"s":"self"},{"t":1,"s":"mutual"}],"v":[{"t":1,"s":"world"},{"t":10,"i":5},{"t":13,"i":6,"l":4,"a":[{"t":1,"s":"hello"},{"t":1,"s":"world"},{"t":10,"i":6},{"t":10,"i":4}]}],"s":3}}]},{"t":12,"i":7,"c":"[a-z0-9]+","m":"i"},{"t":11,"i":8,"s":"2023-03-22T02:55:33.504Z"},{"t":10,"i":5},{"t":10,"i":6},{"t":10,"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.
Serializable references
There are values that has no way to be serializable at all, i.e. functions, but usually in an isomorphic code, functions can exist on both client and server-side. What if we can serialize these functions in such a way we can refer to their counterparts?
seroval has createReference that you can use to map user-defined strings to their references.
import { createReference } from 'seroval';
const thisIsAnIsomorphicFunction = createReference(
// This is (ideally) a unique identifier
// that is used to map the serialized value
// to its actual reference (and vice versa)
'my-function',
() => {
// Ideally this function should exist on both
// server and client, but we want to add the ability
// to serialize and deserialize this reference on
// both sides
}
);
// we can now serialize this
const serialized = toJSON(thisIsAnIsomorphicFunction); // or any of the serializer
thisIsAnIsomorphicFunction === fromJSON(serialized); // trueNote It can only accept objects, functions and symbols and it doesn't actually serialize their values but only the string you used to identify the reference
Supports
The following values are the only values accepted by seroval:
- Exact values
NaNInfinity-Infinity-0
- Primitives
numberstringbooleannullundefinedbigint
Array+ holesObjectRegExpDateMapSetObject.create(null)
TypedArrayInt8ArrayInt16ArrayInt32ArrayUint8ArrayUint16ArrayUint32ArrayUint8ClampedArrayFloat32ArrayFloat64ArrayBigInt64ArrayBigUint64Array
ErrorAggregateErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError
Promise(withserializeAsync)Iterable- Well-known symbols
URLURLSearchParams- Cyclic references (both self and mutual)
- Isomorphic references (a reference that exist on both the serializer and deserializer side)
Compat
serialize, serializeAsync, toJSON and toJSONAsync can accept a { disabledFeatures: number } option. The disabledFeatures defines how the output code would look like when serialized by 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
Iterable, usesSymbol.iteratorinstead.
- Used for
ArrowFunction- Uses function expressions for top-level and for deferred
Promisevalues - method shorthands (if
MethodShortandis not set) or function expressions forIterable.
- Uses function expressions for top-level and for deferred
BigInt- Throws when attempted to use, includes
BigIntTypedArray - Disables use of
BigInt,BigInt64ArrayandBigUint64Array
- Throws when attempted to use, includes
ErrorPrototypeStack- Skipped when detected.
- Affects both
ErrorandAggregateError
Map- Throws when attempted to use.
- Disables serialization of
Map
MethodShorthand- Uses function expressions instead.
- Only affects
Iterable
ObjectAssign- Uses manual object assignments instead.
- Affects
Iterable,Error,AggregateErrorandObject.create(null)
Promise- Throws when attempted to use in
serializeAsyncandtoJSONAsync. - Disables serialization of
Promise
- Throws when attempted to use in
Set- Throws when attempted to use.
- Disables serialization of
Set
Symbol- Throws when attempted to use.
- This disables serialization of well-known symbols and
Iterable.
TypedArray- Throws when attempted to use.
- Disables serialization of
TypedArray
BigIntTypedArray- Throws when attempted to use
- Also throws if
BigIntis disabled. - Disables serialization of
BigInt64ArrayandBigUint64Array
WebAPI- Throws and disables the following usage:
Sponsors
License
MIT © lxsmnsyc