diff --git a/README.md b/README.md index ede9378..1736f07 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,12 @@ This extends the default JSON serialization adding the following: - Sparse array serialization - `undefined`/`NaN` serialization - Serialization of `Infinity`, `BigInt`'s, `Set`'s, `Map`'s -- Function serialization (off by default) +- Function serialization - Deep and partial-deep cleen object copy -Possible differences to JSON output: -- Repeating long strings and BigInts are referenced by default instead of - being reincluded in the output. - +Data not stored: +- Attributes on arrays, maps, sets, and functions, +- Function closures. ## Motivation @@ -40,6 +39,43 @@ var serialize = require('ig-serialize') ## Introduction +`serialize.js` provides two toolsets: + +1. A means to serialize and deserialize complex data structures into an + extended JSON format. + ```javascript + var obj = { + sparse_array: [,,,,1], + bad_number: NaN, + really_large_number: 99999999999999999999999n, + } + obj.recursive = obj + obj.re_reference = obj.sparse_array + + var str = serialize(obj) + + // ... + + var copy = deserialize(str) + ``` + This is useful when requiering serialization of data structures more + complex than pure JSON can handle. +2. A means to cleanly copy deep data structures with guaranteed isolation. + ```javascript + var obj = { + // ... + } + + var copy = deepCopy(obj) + ``` + +### Long strings and large `BigInt`'s + +Repeating strings and `BigInt`'s longer that `MIN_LENGTH_REF` are stored +by reference by default. + +See: ['MIN_LENGTH_REF'](#min_length_ref-options-min_length_ref) + ### Serializing functions @@ -58,6 +94,12 @@ Thus, care must be taken when serializing structures containing function. ## API +### `eJSON` + +An `JSON`-api-compatible object providing [`.stringify(..)`](#serialize-ejson-stringify) and [`.parse(..)`](#deserialize-ejson-parse) +static methods. + + ### `serialize(..)` / `eJSON.stringify(..)` Serialize a JavaScript value into a JSON/eJSON string. @@ -147,23 +189,38 @@ eJSON.parse(, {functions: }) ### `deepCopy(..)` +Deep-copy an object. + ``` deepCopy() -> ``` +The returned object is a fully sanitized through serialization clean copy. + ### `partialDeepCopy(..)` +Partially deep-copy and object, retaining only references to functions. + ``` partialDeepCopy() -> ``` +The returned object is a partially sanitized through serialization clean +copy with function references copied as-is from the input retaining and +"transferring" all associated function state like attributes and closures. + +Note that this is by definition a _controlled state leak_ from input +object to copy, so care must be taken to _control_ object-specific state +in function closures and attributes -- keeping function state independent +from object state is _in general_ a good practice. + ### `MIN_LENGTH_REF` / `.min_length_ref` -Defines the default minimum length of repeating string or bin-int to +Defines the default minimum length of repeating string or `BigInt`'s to include as a reference in the output. If set to `0`, referencing will be disabled. @@ -171,9 +228,6 @@ If set to `0`, referencing will be disabled. Default: 96 -### `DEBUG` - - ## Format @@ -194,22 +248,26 @@ structure traversed: - map -> pair of numbers -- the first indicates item order the second if 0 selects the key, if 1 selects the value. -Note that string path items are unambiguous and are always treated as -attributes. - An empty path indicates the root object. +Notes: +- String path items are unambiguous and are always treated as attributes. + This enables referencing of attributes of any object like arrays, maps, ...etc. +- Map/set paths are structured as if sets and maps are represented by + arrays structured as their respective constructors expect as input. + + ### Referencing If an object is encountered for a second time it will be serialized as a reference by path to the first occurrence. -Format: +Grammar: ```bnf ::= '' - | ' ']>' + | ' ']> ::= @@ -258,7 +316,7 @@ serialize([null, undefined, NaN]) // -> '[null,undefined,NaN]' ### Sparse arrays Sparse arrays are represented in the same way JavaScript handles them -syntactically. +syntactically -- with commas separating empty "positions". Example: ```javascript @@ -266,10 +324,9 @@ serialize([,]) // -> '[,]' serialize([,,]) // -> '[,,]' ``` -Note that trailing commas are ignored in the same way JavaScript ignores -them: +Trailing commas are handled in the same way JavaScript handles them: ```javascript -// trailing comma... +// trailing commas are ignored... serialize([1,]) // -> '[1]' // sparse element... @@ -298,6 +355,10 @@ serialize(-Infinity) // -> '-Infinity' ### Map / Set +Maps and sets are stored in the same format as their respective constructor +calls, dropping the `new` keyword: + +Grammar: ```bnf ::= 'Map([]) @@ -311,7 +372,6 @@ serialize(-Infinity) // -> '-Infinity' '[' ',' ']' ``` - ```bnf ::= 'Set([]) @@ -322,7 +382,7 @@ serialize(-Infinity) // -> '-Infinity' | ',' ``` - +Examples: ```javascript serialize(new Set([1,2,3])) // -> 'Set([1,2,3])' serialize(new Map([['a', 1], ['b', 2]])) // -> 'Map([["a",1],["b",2]])' @@ -331,26 +391,44 @@ serialize(new Map([['a', 1], ['b', 2]])) // -> 'Map([["a",1],["b",2]])' ### Functions +A function can be stored in one of two ways: +1. As code (default) +2. As a function index, if an array to store function references is given. + +Grammar: ```bnf ::= ' ',(' ')]> ::= - | + | - ::= - + ::= + + ::= ``` +`` is the length of the next block in chars, including the braces. + + ```javascript serialize(function(){}) // -> '' + +var functions = [] +serialize(function(){}, {functions}) // -> '' ``` +Note that deserializing functions is disabled by default as it can pose +a security risk if the input to `deserialize(..)` is not trusted. +(see: [`deserialize(..)`](#deserialize-ejson-parse)) + ## Running tests +`serialize.js` uses ['test.js'](/flynx/test.js) for testing. + Get the development dependencies: ```shell $ npm install -D @@ -363,10 +441,22 @@ $ npm test To run the tests directly: ```shell -$ node ./test.js +$ ./test.js +``` + +To run the tests with modifier chains of length 3: +```shell +$ ./test.js -m 3 ``` +## License + +[BSD 3-Clause License](./LICENSE) + +Copyright (c) 2014-2026, Alex A. Naanou, +All rights reserved. + diff --git a/serialize.js b/serialize.js index 8acb823..8d3a9ff 100644 --- a/serialize.js +++ b/serialize.js @@ -771,7 +771,7 @@ function(str, options){ var deepCopy = module.deepCopy = -function(obj, funcs){ +function(obj, funcs=true){ var options = {functions: funcs} return deserialize( serialize(obj, null, 0, options), @@ -781,10 +781,7 @@ function(obj, funcs){ var partialDeepCopy = module.partialDeepCopy = function(obj, funcs=[]){ - var options = {functions: funcs} - return deserialize( - serialize(obj, null, 0, options), - options) } + return deepCopy(obj, funcs) }