JSON-Z – JSON for Everyone
JSON-Z is a superset of JSON that aims to alleviate some limitations of JSON by expanding its syntax to include some productions from ECMAScript 5.1, ECMAScript 6.0, and later.
The goal of JSON-Z is to increase flexibility of parsing while, by default, maintaining maximum compatibility with standard JSON for stringification. JSON-Z output, like JSON, is also valid JavaScript (with two optional exceptions).
This JavaScript library is the official reference implementation for JSON-Z parsing and serialization libraries.
See the interactive JSON-Z demo at json-z.org.
By the way, the original JSON might be pronounced like the name "Jason", but JSON-Z is pronounced jay-SANH-zee, kind of like "Jumanji".
At least that's what I'm going with.
Summary of Features
The following features, which are not supported in standard JSON, have been added to JSON-Z:
Objects
- Object keys may be unquoted ECMAScript 5.1 IdentifierName s.
- Unquoted object keys may include character escapes.
- Character escapes with two hex digits (
\xXX
) are supported for parsing, as well as the standard four-digit\uXXXX
form. - Object keys may be single quoted or backtick quoted (using backticks does not, however, invoke string interpolation).
- Object key/value pairs may have a single trailing comma.
Arrays
- Array elements may have a single trailing comma.
- Arrays may be sparse, e.g.
[1, , 3, 4]
. - If arrays have string keys with associated values (not recommended!), e.g.
[1, 2, 3, #frequency: "Kenneth"]
, such key/value pairs can be parsed and optionally stringified. This also applies to numeric keys which are negative or non-integer. (The#
is not part of the key, it simply precedes any explicitly keyed value in an array.) Key/value pairs such as these are normally hidden, and do not affect thelength
property of an array.
Strings
- Strings may be single quoted or backtick quoted (using backticks does not, however, invoke string interpolation).
- Strings may span multiple lines by escaping new line characters.
- Character escapes with two hex digits (
\xXX
) are supported for parsing, as well as the standard four-digit\uXXXX
form.
Numbers
- Numbers may be hexadecimal, octal, or binary.
- Numbers may have a leading or trailing decimal point, and may contain underscores used as separators.
- Numbers may be IEEE 754 positive infinity (
Infinity
), negative infinity (-Infinity
), andNaN
. - Numbers may begin with an explicit plus sign.
- Negative zero (
-0
) is parsed and stringified as distinct from positive 0. - Numbers may be
BigInt
values by appending a lowercasen
to the end of an integer value, e.g.-23888n
, or9_223_372_036_854_775_807n
. - When running a version of JavaScript that does not support native
BigInt
primitives, a third-partyBigInt
-like library can be used. BigInt
values can be in decimal, hexadecimal, octal, or binary form. Exponential notation can also be used (e.g.4.2E12n
) so long as the value including its exponent specifies an integer value.- Numbers may be arbitrary precision decimal values by appending a lowercase
m
, e.g.3.1415926535897932384626433832795028841971693993751m
.NaN_m
,Infinity_m
, and-Infinity_m
can also be used. (Using a third-party extended-precision library is necessary to take full advantage of this feature.) - Numbers may be fixed precision decimal values by appending a lowercase
d
, e.g.2.718281828459045235360287471352662d
.NaN_d
,Infinity_d
, and-Infinity_d
can also be used. (Using a third-party extended-precision library is necessary to take full advantage of this feature.)
Note: Decimal constants in JavaScript, and the related support for extended precision decimal math, are currently only Stage 0 and Stage1 proposals. JSON-Z support for these constants should be considered experimental. If eventually implemented, it is most likely only one form (arbitrary precision, fixed precision, but not both) of extended precision decimal math will survive, and only one of the two suffixes (m
ord
) will be used. It is purely a convention of JSON-Z to usem
for arbitrary precision, andd
for fixed precision.
Comments
- Single and multi-line comments are allowed.
White Space
- Additional white space characters are allowed.
Undefined
- Handles
undefined
values.
Replacer functions
- As part of handling
undefined
values, when a replacer function returnsundefined
, that will itself become the encoded value of the value which has been replaced. - Replacer functions can return the special value
JSONZ.DELETE
to indicate that a key/value pair in an object be deleted, or that a slot in an array be left empty. - A global replacer function can be specified.
- For the benefit of anonymous (arrow) functions, which do not have their own
this
, replacer functions are passed the holder of a key/value pair as a third argument to the function.
Reviver functions
- A global reviver function can be specified.
- For the benefit of anonymous (arrow) functions, which do not have their own
this
, reviver functions are passed the holder of a key/value pair as a third argument to the function.
Extended types
In standard JSON, all values are either strings, numbers, booleans, or null
s, or values are objects or arrays composed of the latter as well as other objects and arrays. JSON-Z optionally allows special handling for other data types, so that values such Date
or Set
objects can be specifically represented as such, parsed and stringified distinctly without having to rely on reviver and replacer functions.
- Built-in support for
Date
,Map
,Set
,RegExp
, andUint8Array
(using base64 representation).Uint8ClampedArray
is also covered, treated asUint8Array
. - There is also built-in support for
BigInt
and "Big Decimal" values as extended types, an alternative to using plain numbers withn
,m
, ord
suffixes. - User-defined extended type handlers, which can both add new data types, or override the handling of built-in extended data types.
Short Example
{
// comments
unquoted: 'and you can quote me on that',
singleQuotes: 'I can use "double quotes" here',
backtickQuotes: `I can use "double quotes" and 'single quotes' here`,
lineBreaks: "Look, Mom! \
No \\n's!",
million: 1_000_000, // Underscore separators in numbers allowed
hexadecimal: 0xdecaf,
// Leading 0 indicates octal if no non-octal digits (8, 9) follow
octal: [0o7, 074],
binary: 0b100101,
leadingDecimalPoint: .8675309, andTrailing: 8675309.,
negativeZero: -0,
positiveSign: +1,
notDefined: undefined,
bigInt: -9223372036854775808n,
bigDecimal: 3.1415926535897932384626433832795028841971693993751m,
fixedBigDecimal: 2.718281828459045235360287471352662d,
trailingComma: 'in objects', andIn: ['arrays',],
sparseArray: [1, 2, , , 5],
// Function-like extended types. This is revived as a JavaScript `Date` object
date: _Date('2019-07-28T08:49:58.202Z'),
// Type container extended types. This is optionally revived as a JavaScript `Date` object
date2: {"_$_": "Date", "_$_value": "2019-07-28T08:49:58.202Z"},
// A relatively compact way to send and receive binary data
buffer: _Uint8Array('T25lLiBUd28uIEZpdmUuLi4gSSBtZWFuIHRocmVlIQ=='),
"backwardsCompatible": "with JSON",
}
Specification
For a detailed explanation of the JSON-Z format, please read (TODO: create specs).
Installation
Node.js
npm install json-z
const JSONZ = require('json-z')
Or, as TypeScript:
;
Browsers
<script src="https://unpkg.com/json-z/dist/index.min.js"></script>
This will create a global JSONZ
variable.
API
The JSON-Z API is compatible with the JSON API. Type definitions to support TypeScript are included.
JSONZ.parse()
Parses a JSON-Z string, constructing the JavaScript value or object described by the string. An optional reviver function can be provided to perform a transformation on the resulting object before it is returned.
Note: One important change from JSON5 is that the JSONZ.parse()
function is re-entrant, so it is safe to call JSONZ.parse()
from within reviver functions and extended type handlers.
Syntax
JSONZ.parse(text[, reviver][, options])
This works very much like JSON.parse
, with the addition of the options
parameter, and that the reviver
function is passed a third argument, holder
, in addition to key
and value
, which lets the reviver
know the array or object that contains the value being examined.
Parameters
text
: The string to parse as JSON-Z.reviver
: If a function, this prescribes how the value originally produced by parsing is transformed, before being returned.options
: An object with the following properties:reviveTypedContainers
: Iftrue
(the default isfalse
), objects which take the form of an extended type container, e.g.{"_$_": "Date", "_$_value": "2019-07-28T08:49:58.202Z"}
, can be revived as specific object classes, such asDate
.reviver
: An alternate means of providing a reviver function.
Return value
The object corresponding to the given JSON-Z text.
JSONZ.stringify()
Converts a JavaScript value to a JSON-Z string, optionally replacing values if a replacer function is specified, or optionally including only the specified properties if a replacer array is specified.
This works very much like JSON.stringify
, with the addition of the options
parameter, and that the replacer
function is passed a third argument, holder
, in addition to key
and value
, which lets the replacer
know the array or object that contains the value being examined.
Syntax
JSONZ.stringify(value[, replacer[, space]])
JSONZ.stringify(value[, options])
Parameters
-
value
: The value to convert to a JSON-Z string. -
replacer
: A function that alters the behavior of the stringification process, or an array of String and Number objects that serve as a whitelist for selecting/filtering the properties of the value object to be included in the JSON-Z string. If this value is null or not provided, all properties of the object are included in the resulting JSON-Z string.When using the standard
JSON.stringify()
, a replacer function is called with two arguments:key
andvalue
. JSON-Z adds a third argument,holder
. This value is already available to standardfunction
s asthis
, butthis
won't be bound toholder
when using an anonymous (arrow) function as a replacer, so the third argument (which can be ignored if not needed) provides alternative access to theholder
value. -
space
: A string or number that is used to insert white space into the output JSON-Z string for readability purposes. If this is a number, it indicates the number of space characters to use as white space; this number is capped at 10. Values less than 1 indicate that no space should be used. If this is a string, the string (or the first 10 characters of the string, if it's longer than that) is used as white space. A single space adds white space without adding indentation. If this parameter is not provided (or is null), no white space is added. If indenting white space is used, trailing commas can optionally appear in objects and arrays. -
options
: This can either be anOptionSet
value (see below), or an object with the following properties:extendedPrimitives
: Iftrue
(the default isfalse
) this enables direct stringification ofInfinity
,-Infinity
, andNaN
. Otherwise, these values becomenull
.extendedTypes
: IfJSONZ.ExtendedTypeMode.AS_FUNCTIONS
orJSONZ.ExtendedTypeMode.AS_OBJECTS
(the default isJSONZ.ExtendedTypeMode.OFF
), this enables special representation of additional data types, such as_Date("2019-07-28T08:49:58.202Z")
, which can be parsed directly as a JavaScriptDate
object, or{"_$_": "Date", "_$_value": "2019-07-28T08:49:58.202Z"}
, which can be automatically rendered as aDate
object by a built-in replacer.primitiveBigDecimal
: 🧪 Iftrue
(the default isfalse
) this enables direct stringification of arbitrary-precision big decimals using the 'm
' suffix. Otherwise, big decimals must be provided as quoted strings or extended types. (Note: The 'm
' suffix can't be parsed as current valid JavaScript, but it is potentially a future valid standard notation.)primitiveBigInt
: Iftrue
(the default isfalse
) this enables direct stringification of big integers using the 'n
' suffix. Otherwise, big integers are provided as quoted strings or extended types.primitiveFixedBigDecimal
: 🧪 Iftrue
(the default isfalse
) this enables direct stringification of fixed-precision big decimals using the 'd
' suffix. Otherwise, big decimals must be provided as quoted strings or extended types. (Note: The 'd
' suffix can't be parsed as current valid JavaScript, but it is potentially a future valid standard notation.)quote
: A string representing the quote character to use when serializing strings (single quote'
or double quote"
), or one of the following values:JSONZ.Quote.DOUBLE
: Always quote with double quotes (this is the default).JSONZ.Quote.SINGLE
: Always quote with single quotes.JSONZ.Quote.PREFER_DOUBLE
: Quote with double quotes, but switch to single quotes or backticks to reduce the number of characters which have to be backslash escaped.JSONZ.Quote.PREFER_SINGLE
: Quote with single quotes, but switch to single quotes or backticks to reduce the number of characters which have to be backslash escaped.
quoteAllKeys
: By default (atrue
value), object keys are quoted, just as in standard JSON. If set tofalse
quotes are omitted unless syntactically necessary.replacer
: Same as thereplacer
parameter.revealHiddenArrayProperties
: Consider this an experimental option. While normally arrays should only have data stored using non-negative integer indices, data can be stored in arrays using string keys and other types of numeric keys. This option will reveal and stringify such additional key/value pairs if present, but this is at the expense of making the JSON-Z output something that must be parsed back using JSON-Z, and is no longer directly usable as valid JavaScript.space
: Same as thespace
parameter. The default is no spacing.sparseArrays
: Iftrue
(the default isfalse
) empty slots in arrays are represented with consecutive commas, e.g.[1,,3]
. This can't be parsed as valid standard JSON, so by default such an array will be stringified as[1,null,3]
.trailingComma
: Iftrue
(the default isfalse
), the final item in an indented object or array has a terminating comma.typePrefix
: Normally a single underscore (_
), this is a prefix used for extended type notation. It can be any string of valid identifier characters staring and ending in an underscore. It is used to help create unique function names when extended type restoration is done using functions named in the global namespace.
Return value
A JSON-Z string representing the value.
Using obj.toJSON() and obj.toJSONZ()
For use with the standard JSON.stringify()
, any object being stringified can have an optional toJSON()
method. This way an object can explicitly tell JSON.stringify()
how its value should be represented.
JSON-Z can also use an object's toJSON()
method, but other factors might take priority as follows:
- If an object has a
toJSONZ()
method, this takes the highest priority. The value returned bytoJSONZ()
can be further modified by any replacer function in effect. Note that whentoJSONZ()
is called, two arguments are passed to this function:key
(an array index or object property name) andholder
(the parent array or parent object (if any) of the object). - If an object can be converted by an extended type handler, that has the next priority. When
ExtendedTypeMode.AS_FUNCTIONS
is in effect, a conversion handled by an extended type handler is final. Replacer functions can, however, further act upon extended type conversions whenExtendedTypeMode.AS_OBJECTS
is in effect. toJSON()
is the next possible value conversion, but only iftoJSONZ()
has not already taken priority.- Any active replacer function is then applied.
- Finally, special handling for
BigInt
and "big decimal" numbers takes place.
JSONZ.hasBigInt()
Returns true if JSON-Z is currently providing full big integer support.
JSONZ.hasNativeBigInt()
Returns true if the currently running version of JavaScript natively supports big integers via BigInt
and constants such as 100n
.
JSONZ.hasBigDecimal()
Returns true if JSON-Z is currently providing full arbitrary-precision big decimal support.
JSONZ.hasFixedBigDecimal()
Returns true if JSON-Z is currently providing full fixed-precision big decimal support.
JSONZ.setBigInt()
Sets a function or class for handling big integer values, or turns special handling of native JavaScript BigInt
on or off.
Syntax
JSONZ.setBigInt(bigIntClass)
JSONZ.setBigInt(active)
Parameters
bigIntClass
: A function or class responsible for handling big integer values.bigIntClass(valueAsString, radix)
, e.g.bigIntClass('123', 10)
orbigIntClass('D87E', 16)
, either with or without a precedingnew
, must return a big integer object that satisfies the testbigIntValue instanceof bigIntClass
.active
: Iftrue
, nativeBigInt
support (if available) is activated. Iffalse
,BigInt
support is deactivated.
Sample usage
npm install json-z
npm install big-integer
const JSONZ = require('json-z);
const bigInt = require('big-integer');
JSONZ.setBigInt(bigInt);
JSONZ.setBigDecimal()
Sets a function or class for handling arbitrary-precision decimal floating-point values.
Syntax
JSONZ.setBigDecimal(bigDecimalClass)
Parameters
bigDecimalClass
: A function or class responsible for handling big decimal values.bigDecimalClass(valueAsString | NaN | Infinity | -Infinity)
, e.g.bigDecimalClass('14.7')
orbigDecimalClass(NaN)
, either with or without a precedingnew
, must return a big decimal object that satisfies the testbigDecimalValue instanceof bigDecimalClass
.
JSONZ.setFixedBigDecimal()
Sets a function or class for handling fixed-precision decimal floating-point values.
Syntax
JSONZ.setFixedBigDecimal(fixedBigDecimalClass)
Parameters
fixedBigDecimalClass
: A function or class responsible for handling big decimal values.fixedBigDecimalClass(valueAsString | NaN | Infinity | -Infinity)
, e.g.fixedBigDecimalClass('14.7')
orfixedBigDecimalClass(NaN)
, either with or without a precedingnew
, must return a big decimal object that satisfies the testfixedBigDecimalValue instanceof fixedBigDecimalClass
.
Sample usage
npm install json-z
npm install decimal.js
const JSONZ = require('json-z');
const Decimal = require('decimal.js');
JSONZ.setBigDecimal(Decimal);
// Approximate representation of decimal128
JSONZ.setFixedBigDecimal(Decimal.clone().set({precision: 34, minE: -6143, maxE: 6144}));
JSONZ.setOptions(options[, additionalOptions])
Sets global options which will be used for all calls to JSONZ.stringify()
. The specific options passed to JSONZ.stringify()
itself override the global options on a per-option basis.
Parameters
options
: This can be an object just as described forJSONZ.stringify()
, or it can be one of the followingOptionSet
constants:JSONZ.OptionSet.MAX_COMPATIBILITY
: These are the options which make the output of JSON-Z fully JSON-compliant.JSONZ.OptionSet.RELAXED
: These options produce output which is fully-valid (albeit cutting-edge) JavaScript, removing unnecessary quotes, favoring single quotes, permitting values likeundefined
andNaN
and sparse arrays.JSONZ.OptionSet.THE_WORKS
: This set of options pulls out (nearly) all the stops, creating output which generally will have to be parsed back using JSON-Z, including function-style extended types and big decimal numbers.revealHiddenArrayProperties
remains false, however, and must be expressly activated.
additionalOptions
: Ifoptions
is anOptionSet
value,additionalOptions
can be used to make further options modifications.
JSONZ.resetOptions()
This restores the default global stringification options for JSON-Z. It is equivalent to JSONZ.setOptions(JSONZ.OptionSet.MAX_COMPATIBILITY)
.
JSONZ.setParseOptions(options)
Sets global options which will be used for all calls to JSONZ.parse()
. The specific options passed to JSONZ.parse()
itself override the global options on a per-option basis.
Parameters
options
: An object with the following properties:reviveTypedContainers
: Same as described forJSONZ.parse()
.reviver
: A global reviver function.
JSONZ.resetParseOptions()
Resets the global parsing options, i.e. no automatic type container revival, no global revival function.
JSONZ.addTypeHandler(handler)
This adds a global extended type handler. These handlers allow JSON-Z to parse and stringify special data types beyond the arrays, simple objects, and primitives supported by standard JSON. Here, as an example, is the built-in handler for Date
objects:
const dateHandler = name: 'Date' obj instanceof Date date ? NaN : date;
When adding multiple type handlers, the most recently added handlers have priority over previous type handlers, which is important if it's possible for the test
function to recognize objects or values also recognized by other handlers.
The extendedTypes
option for JSONZ.stringify()
lets you choose between two formats for extended types:
JSONZ.ExtendedTypeMode.AS_FUNCTIONS
format:
_Date('2019-07-28T08:49:58.202Z')
The disadvantage of this format is that it can't be parsed as standard JSON. The advantage is that it is valid JavaScript, and it works better as JSON-P.
As long as _Date
is a global function (see JSONZ.globalizeTypeHandlers
), the date object can be revived. To help with possible global namespace conflicts, the option typePrefix
can be changed to something like '_jsonz_'
, which will result in output like this:
_jsonz_Date("2019-07-28T08:49:58.202Z")
JSONZ.ExtendedTypeMode.AS_OBJECTS
format:
{"_$_": "Date", "_$_value": "2019-07-28T08:49:58.202Z"}
This has the advantage of being valid standard JSON, and even without using JSON-Z on the receiving end, the right reviver function can convert this to a Date
. The disadvantage is that it's harder to use this format with JSON-P, as there's no natural place to intercept the data and convert it.
JSONZ.ExtendedTypeMode.OFF
disables both of the above options.
JSONZ.globalizeTypeHandlers([prefix])
This function registers your type handlers (and the built-in type handlers) as global functions, which facilitates the process of handling JSON-Z output as JSON-P. The optional prefix
argument (which needs to be either a single underscore (the default), or a valid JavaScript identifier that both begins and ends in an underscore) lets you control how these functions use the global namespace. If you change the default prefix, that same prefix needs to be used as an option by the call to JSONZ.stringify()
which creates the output that you're consuming.
Any previously globalized type handlers are first removed.
JSONZ.removeGlobalizedTypeHandlers()
This function removes all previously globalized type handlers.
JSONZ.removeTypeHandler(typeName)
This function removes the type handler for the given typeName
.
JSONZ.resetStandardTypeHandlers()
This removes all user-added type handlers, and restores all built-in type handlers.
JSONZ.restoreStandardTypeHandlers()
This restores all built-in type handlers, leaving any user-added type handlers.
require()
JSON-Z files
Node.js When using Node.js, you can require()
JSON-Z files by adding the following
statement.
Then you can load a JSON-Z file with a Node.js require()
statement. For
example:
const config =
CLI
Since JSON is more widely used than JSON-Z, this package includes a CLI for converting JSON-Z to JSON and for validating the syntax of JSON-Z documents.
Installation
npm install --global json-z
Usage
json-z [options] <file>
If <file>
is not provided, then STDIN is used.
Options:
-s
,--space
: The number of spaces to indent ort
for tabs-o
,--out-file [file]
: Output to the specified file, otherwise STDOUT-v
,--validate
: Validate JSON-Z but do not output JSON-V
,--version
: Output the version number-h
,--help
: Output usage information
Development
git clone https://github.com/kshetline/json-zcd json-znpm install
When contributing code, please write relevant tests and run npm test
and npm run lint
before submitting pull requests. Please use an editor that supports
EditorConfig.
Issues
To report bugs or request features regarding the JavaScript implementation of JSON-Z, please submit an issue to this repository.
License
MIT. See LICENSE.md for details.
Credits
Assem Kishore founded the JSON5 project, upon which JSON-Z is based.
Michael Bolin independently arrived at and published some of the same ideas that went into JSON5, with awesome explanations and detail. Recommended reading: Suggested Improvements to JSON
Douglas Crockford of course designed and built JSON, but his state machine diagrams on the JSON website, gave the JSON5 team motivation and confidence that building a new parser to implement these ideas was within reach. The original implementation of JSON5 was also modeled directly off of Doug’s open-source json_parse.js parser. We’re grateful for that clean and well-documented code.
Max Nanasy has been an early and prolific supporter of JSON5, contributing multiple patches and ideas.
Andrew Eisenberg contributed the original
JSON5 stringify
method.
Jordan Tucker aligned JSON5 more closely with ES5, wrote the official JSON5 specification, completely rewrote the JSON5 codebase from the ground up, and is actively maintaining the JSON5 project.
Kerry Shetline branched off from JSON5, at version 2.1.0 of that project, to create JSON-Z.