Transform custom types into JSON-compatible data.
Have you ever tried to do something like this:
JSON.stringify({ createdAt: new Date() });
This works but the date is converted into a string so when you JSON.parse
the
result you don't get a Date
back.
ZenJSON allow you to transform you data into a new one that can safely be
stringify
ed. Of course you can also do the reverse operation to get back your
original data.
Here is a gist:
import { restore, sanitize } from "https://deno.land/x/zenjson/mod.ts"; const data = { createdAt: new Date(), nested: { array: [Infinity, undefined, NaN], }, }; // sanitize your data before calling JSON.stringify const str = JSON.stringify(sanitize(data)); // restore afer calling JSON.parse to get back the original data const parsed = restore(JSON.parse(str)); assert(parsed.createdAt instanceof Date); assert(parsed.nested.array[0] === Infinity);
# npm npm install zenjson # yarb yarn add zenjson
You can also use this package in Deno / Recent Browser using ESM import like this:
import { restore, sanitize } from "https://deno.land/x/zenjson/mod.ts"; // don't forget to fix the version, for example https://deno.land/x/zenjson@v1.0.0/mod.ts // open https://deno.land/x/zenjson/mod.ts to find the latest one
By default ZenJSON supports the following types:
Date
Infinity
, -Infinity
and NaN
undefined
When you call sanitize
, ZenJSON will traverse your data and match it against a
list of custom types. When a value matches a type, it will replace the value
into a tuple of two elements (the first element is the name of the type, the
second is the sanitized value).
For example if you call sanitize(NaN)
it will produce the following result:
['number', 'NaN']
.
If you then call the restore
function with this tuple:
restore(['number', 'NaN'])
it will return NaN
.
Note: Both sanitize
and restore
create a shallow copy, even if no change
is made.
You can provide your own custom types and even replace the default ones using
the createSanitize
and createRestore
functions.
These functions take one argument: the list of supported types. The defaults
supported types are exposed as defaultTypes
.
sanitize
function correspond to createSanitize(defaultTypes)
restore
function correspond to createRestore(defaultTypes)
If you use one of the create
function you probably want to add your own custom
type:
import { createSanitize, defaultTypes, } from "https://deno.land/x/zenjson/mod.ts"; const sanitize = createSanitize([ // copy the default types ...defaultTypes, // add your own type (keep reading to find out how to create this) myCustomType, ]);
Note: If you use TypeScript you can use the ICustomType
export to defined
you types.
A custom type is an object with the following properties:
name
: This identify your type. It must be unique. The defaults types uses
the following names: undefined
, number
, array
, date
.check
: A function that receive a value and return true
if it has the
correct type.sanitize
: A function that receive the value and must return a
JSON-compatible version of it (for example (date) => date.toISOString()
).restore
: A function that receive the sanitized value and must return the
restored value (for example (dateStr) => parseISO(dateStr)
).Here is an example of a custom type that handles BigInt:
import { createRestore, createSanitize, defaultTypes, } from "https://deno.land/x/zenjson/mod.ts"; const bigintType = { name: "bigint", check: (val) => typeof val === "bigint", sanitize: (val) => val.toString(), restore: (val) => BigInt(val), }; const sanitize = createSanitize([...defaultTypes, bigintType]); const restore = createRestore([...defaultTypes, bigintType]); const data = { num: 123456789123456789123456n }; const sanitized = sanitize(data); // { num: ['bigint', '123456789123456789123456'] } const restored = restore(sanitized); // { num: 123456789123456789123456n }
Note: The check
, sanitize
and restore
also receive a second parameter:
ctx
. This is an object that contains some usefull data and functions.
Take a look a the
types.ts
file
to see how the defaults types are implemented.
By the way, each one of the default types are exported and can be used indivdually. They are four types:
dateType
(name: date
) handles Date
objectsundefinedType
(name: undefined
) handles undefined
valuesspecialNumberType
(name: number
) handles NaN
, Infinity
and -Infinity
arrayType
(name: array
) handles special cases (see below)arrayType
custom typeYou might be wonderring what happens if your data looks like one of the sanitized tuple, for example you might have something like this:
const data = { keys: ["date", "time"], };
This object might cause an error because the restore
function will interpret
the ['date', 'time']
as a sanitized value and will try to transform it into a
date.
To solve this ZenJSON include the arrayType
in the list of default types. This
type will tranform any array that looks like a sanitized tuple into a tuple
['array', __THE_ARRAY__]
.
In the example above the result of sanitize(data)
is:
const result = { keys: ["array", ["date", "time"]], };
Which is correctly restore
d into the original object.
Important: When you define your own types you should always include the
arrayType
to avoid the problem desribed above.
ZenJSON handles nested array and object but it will stop as soon as a custom
type matches. If you want to sanitize data inside f your custom type, you can
use the ctx.sanitize
and ctx.restore
.
Here is an example of a custom type for
Set
:
const setType = { name: "set", check: (val) => val && val instanceof Set, sanitize: (val, ctx) => { // extract list of values const setValues = Array.from(set.values()); // use ctx.sanitize to sanitize items inside the set return setValues.map((item) => ctx.sanitize(item)); }, restore: (val, ctx) => { const restoredValues = val.map((item) => ctx.restore(item)); return new Set(restoredValues); }, };
ctx.state
For more advanced use case you can use the ctx.state
to store data outside of
the sanitized / restored data. This object is a ITypedMap
(a WeakMap
that
use a special key to enforce type safety).
When you call sanitize
or restore
you can pass a second argument that will
be used as the initial state of the ctx.state
.
const sanitize = createSanitize([ ...defaultTypes, someSpecialTypeThatUsesCtxState, ]); const state = createTypedMap(); const sanitized = sanitize(data, state); // do something with the state state.get(someKey);
Take a look at this test file for an example.
Add Package
deno add jsr:@dldc/zenjson
Import symbol
import * as zenjson from "@dldc/zenjson";
---- OR ----
Import directly with a jsr specifier
import * as zenjson from "jsr:@dldc/zenjson";
Add Package
npx jsr add @dldc/zenjson
Import symbol
import * as zenjson from "@dldc/zenjson";
Add Package
yarn dlx jsr add @dldc/zenjson
Import symbol
import * as zenjson from "@dldc/zenjson";
Add Package
pnpm dlx jsr add @dldc/zenjson
Import symbol
import * as zenjson from "@dldc/zenjson";
Add Package
bunx jsr add @dldc/zenjson
Import symbol
import * as zenjson from "@dldc/zenjson";