ig-serialize 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -13
- package/package.json +1 -1
- package/serialize.js +64 -10
- package/test.js +4 -4
package/README.md
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
This extends the default JSON serialization adding the following:
|
|
4
4
|
- Recursive data structure serialization
|
|
5
|
+
- Sparse array serialization
|
|
5
6
|
- `undefined`/`NaN` serialization
|
|
6
|
-
- `Set
|
|
7
|
+
- Serialization of `Infinity`, `BigInt`'s, `Set`'s, `Map`'s
|
|
7
8
|
- Function serialization (off by default)
|
|
8
9
|
- Deep and partial-deep cleen object copy
|
|
9
10
|
|
|
10
11
|
Possible differences to JSON output:
|
|
11
|
-
- Repeating long strings and BigInts
|
|
12
|
-
reincluded in the output.
|
|
12
|
+
- Repeating long strings and BigInts are referenced by default instead of
|
|
13
|
+
being reincluded in the output.
|
|
14
|
+
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
## Motivation
|
|
@@ -23,6 +25,7 @@ and tooling design, basic parsing, among others.
|
|
|
23
25
|
|
|
24
26
|
## Installation
|
|
25
27
|
|
|
28
|
+
For basic use:
|
|
26
29
|
```shell
|
|
27
30
|
$ npm install ig-serilaize
|
|
28
31
|
```
|
|
@@ -30,6 +33,10 @@ $ npm install ig-serilaize
|
|
|
30
33
|
Or just download and drop [serialize.js](serialize.js) into your code.
|
|
31
34
|
|
|
32
35
|
|
|
36
|
+
```javascript
|
|
37
|
+
serialize = require('ig-serialize')
|
|
38
|
+
```
|
|
39
|
+
|
|
33
40
|
|
|
34
41
|
## Introduction
|
|
35
42
|
|
|
@@ -53,12 +60,106 @@ Thus, care must be taken when serializing structures containing function.
|
|
|
53
60
|
|
|
54
61
|
### `serialize(..)` / `eJSON.stringify(..)`
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
Serialize a JavaScript value into a JSON/eJSON string.
|
|
64
|
+
```
|
|
65
|
+
serialize(<value>)
|
|
66
|
+
eJSON.stringify(<value>)
|
|
67
|
+
-> <string>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
More control:
|
|
71
|
+
```
|
|
72
|
+
serialize(obj, options){
|
|
73
|
+
serialize(obj, indent, depth=0, options){
|
|
74
|
+
-> <string>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Options format:
|
|
78
|
+
```
|
|
79
|
+
{
|
|
80
|
+
// pretty-printing indent...
|
|
81
|
+
// (default: undefined)
|
|
82
|
+
indent: undefined,
|
|
83
|
+
|
|
84
|
+
// outout root indent...
|
|
85
|
+
// (default: 0)
|
|
86
|
+
depth: 0,
|
|
87
|
+
|
|
88
|
+
// minimal referenced string/bigint length...
|
|
89
|
+
// (default: MIN_LENGTH_REF)
|
|
90
|
+
min_length_ref: MIN_LENGTH_REF,
|
|
91
|
+
|
|
92
|
+
// functions list...
|
|
93
|
+
// (default: undefined)
|
|
94
|
+
functions: undefined,
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Supported options:
|
|
99
|
+
- `indent` controls formatting and nested value indent, if set to a number
|
|
100
|
+
that number of spaces will be used to indent nested values if given a
|
|
101
|
+
string that string is used for indenting, note that only whitespace is
|
|
102
|
+
supported currently.
|
|
103
|
+
Default: `undefined` (disabled)
|
|
104
|
+
- `depth` if given is a number of `indent`'s, used to set top level indent
|
|
105
|
+
depth of the returned string, this can be useful when pretty-printing
|
|
106
|
+
or nesting the output.
|
|
107
|
+
Default: `0`
|
|
108
|
+
- `min_length_ref` sets the minimal length of a string or big-int value
|
|
109
|
+
for referencing when encountered repeatedly.
|
|
110
|
+
If set to `0` or `Infinity` referencing of strings and big-ints will
|
|
111
|
+
be is disabled.
|
|
112
|
+
Default: 'MIN_LENGTH_REF'
|
|
113
|
+
- `functions` if passed an array, encounterd functions will be pushed to
|
|
114
|
+
it and stored in the output by index.
|
|
115
|
+
Default: `undefined`
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
### `deserialize(..)` / `eJSON.parse(..)`
|
|
119
|
+
|
|
120
|
+
Deserialize a JSON/eJSON into a value.
|
|
121
|
+
```
|
|
122
|
+
deserialize(<string>)
|
|
123
|
+
eJSON.parse(<string>)
|
|
124
|
+
-> <value>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Deserializing function is disabled by default as it can be a security
|
|
128
|
+
risk if the eJSON came from an untrusted source.
|
|
129
|
+
|
|
130
|
+
Enable function deserialization:
|
|
131
|
+
```
|
|
132
|
+
deserialize(<string>, true)
|
|
133
|
+
eJSON.parse(<string>, true)
|
|
134
|
+
deserialize(<string>, {functions: true})
|
|
135
|
+
eJSON.parse(<string>, {functions: true})
|
|
136
|
+
-> <value>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Passing a function list (generated by `serialize(<value>, {functions: <functions>})`)
|
|
140
|
+
for deserialization:
|
|
141
|
+
```
|
|
142
|
+
deserialize(<string>, {functions: <functions>})
|
|
143
|
+
eJSON.parse(<string>, {functions: <functions>})
|
|
144
|
+
-> <value>
|
|
145
|
+
```
|
|
146
|
+
|
|
57
147
|
|
|
58
148
|
### `deepCopy(..)`
|
|
59
149
|
|
|
150
|
+
```
|
|
151
|
+
deepCopy(<value>)
|
|
152
|
+
-> <value>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
|
|
60
156
|
### `partialDeepCopy(..)`
|
|
61
157
|
|
|
158
|
+
```
|
|
159
|
+
partialDeepCopy(<value>)
|
|
160
|
+
-> <value>
|
|
161
|
+
```
|
|
162
|
+
|
|
62
163
|
|
|
63
164
|
### `MIN_LENGTH_REF` / `<options>.min_length_ref`
|
|
64
165
|
|
|
@@ -79,23 +180,108 @@ Default: 96
|
|
|
79
180
|
The output of `.serialize(..)` is a strict superset of [standard JSON](https://www.json.org/json-en.html),
|
|
80
181
|
while the input format is a bit more relaxed than in several details.
|
|
81
182
|
|
|
82
|
-
Extensions to JSON:
|
|
83
|
-
- Recursion
|
|
84
|
-
- undefined / NaN
|
|
85
|
-
- BigInt
|
|
86
|
-
- Map / Set
|
|
87
|
-
- Function
|
|
88
183
|
|
|
89
184
|
### Structural paths
|
|
90
185
|
|
|
91
|
-
|
|
186
|
+
Paths are used for internal references in cases when objects are
|
|
187
|
+
encountered multiple times, e.g. in recursion.
|
|
188
|
+
|
|
189
|
+
A path is an array of keys, the meaning of each key depends on the data
|
|
190
|
+
structure traversed:
|
|
191
|
+
- array -> number
|
|
192
|
+
- object -> string
|
|
193
|
+
- set -> number -- item order in set
|
|
194
|
+
- map -> pair of numbers -- the first indicates item order the second
|
|
195
|
+
if 0 selects the key, if 1 selects the value.
|
|
196
|
+
|
|
197
|
+
Note that string path items are unambiguous and are always treated as
|
|
198
|
+
attributes.
|
|
199
|
+
|
|
200
|
+
An empty path indicates the root object.
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
### Referencing
|
|
204
|
+
|
|
205
|
+
If an object is encountered for a second time it will be serialized as
|
|
206
|
+
a reference by path to the first occurrence.
|
|
207
|
+
|
|
208
|
+
Format:
|
|
209
|
+
```bnf
|
|
210
|
+
<ref> ::=
|
|
211
|
+
'<REF' <path> '>'
|
|
212
|
+
|
|
213
|
+
<path> ::=
|
|
214
|
+
'[' <path-items> ']'
|
|
215
|
+
|
|
216
|
+
<path-items> ::=
|
|
217
|
+
<item>
|
|
218
|
+
| <item> ',' <path-items>
|
|
219
|
+
|
|
220
|
+
<item> ::=
|
|
221
|
+
<number>
|
|
222
|
+
| <string>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
```javascript
|
|
227
|
+
// a recursive array...
|
|
228
|
+
var o = []
|
|
229
|
+
o.o = o
|
|
230
|
+
|
|
231
|
+
// root object reference...
|
|
232
|
+
serialize(o) // -> '[<REF[]>]'
|
|
233
|
+
|
|
234
|
+
// array item...
|
|
235
|
+
serialize([o]) // -> '[[<REF[0]>]]'
|
|
236
|
+
|
|
237
|
+
// set item...
|
|
238
|
+
// NOTE: the path here is the same as in the above example -- since we
|
|
239
|
+
// use ordered topology for paths sets do not differ from arrays.
|
|
240
|
+
serialize(new Set([o])) // -> 'Set([[<REF[0]>]])'
|
|
241
|
+
|
|
242
|
+
// map key...
|
|
243
|
+
serialize(new Map([[o, 'value']])) // -> 'Map([[[<REF[0,0]>],"value"]])'
|
|
244
|
+
|
|
245
|
+
// map value...
|
|
246
|
+
serialize(new Map([['key', o]])) // -> 'Map([["key",[<REF[0,1]>]]])'
|
|
247
|
+
```
|
|
92
248
|
|
|
93
|
-
If an object is encountered
|
|
94
249
|
|
|
95
|
-
###
|
|
250
|
+
### Null types
|
|
251
|
+
|
|
252
|
+
In addition to `null`, serialize.js adds support for `undefined` and
|
|
253
|
+
`NaN` which are stored as-is.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
```javascript
|
|
257
|
+
serialize([null, undefined, NaN]) // -> '[null,undefined,NaN]'
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Sparse arrays
|
|
261
|
+
|
|
262
|
+
Sparse arrays are represented in the same way JavaScript handles them
|
|
263
|
+
syntactically.
|
|
264
|
+
|
|
265
|
+
Example:
|
|
266
|
+
```javascript
|
|
267
|
+
serialize([,]) // -> '[,]'
|
|
268
|
+
serialize([,,]) // -> '[,,]'
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Note that trailing commas are ignored in the same way JavaScript ignores
|
|
272
|
+
them:
|
|
273
|
+
```javascript
|
|
274
|
+
// trailing comma...
|
|
275
|
+
serialize([1,]) // -> '[1]'
|
|
276
|
+
|
|
277
|
+
// sparse element...
|
|
278
|
+
serialize([1,,]) // -> '[1,,]'
|
|
279
|
+
```
|
|
96
280
|
|
|
97
281
|
### BigInt
|
|
98
282
|
|
|
283
|
+
### Infinity
|
|
284
|
+
|
|
99
285
|
### Map / Set
|
|
100
286
|
|
|
101
287
|
### Functions
|
|
@@ -103,6 +289,24 @@ If an object is encountered
|
|
|
103
289
|
|
|
104
290
|
|
|
105
291
|
|
|
292
|
+
## Running tests
|
|
293
|
+
|
|
294
|
+
Get the development dependencies:
|
|
295
|
+
```shell
|
|
296
|
+
$ npm install -D
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Run the tests:
|
|
300
|
+
```shell
|
|
301
|
+
$ npm test
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
To run the tests directly:
|
|
305
|
+
```shell
|
|
306
|
+
$ node ./test.js
|
|
307
|
+
```
|
|
308
|
+
|
|
106
309
|
|
|
107
310
|
|
|
108
311
|
|
|
312
|
+
<!-- vim:set nowrap : -->
|
package/package.json
CHANGED
package/serialize.js
CHANGED
|
@@ -8,6 +8,16 @@
|
|
|
8
8
|
* - get/set object by path
|
|
9
9
|
* Object.get(..) / Object.set(..)
|
|
10
10
|
*
|
|
11
|
+
* XXX try removing paths from refs and use the same indexes, the same
|
|
12
|
+
* system as with functions...
|
|
13
|
+
* + this will make the refs shorter
|
|
14
|
+
* - the deserialization would require the same "seen" logic as
|
|
15
|
+
* serialization -- reuse a function
|
|
16
|
+
* - the "seen" logic can depend on settings, i.e. would either
|
|
17
|
+
* require a sable algorithm that would be settings-agnostic
|
|
18
|
+
* a-la paths (preferred), or a way to transfer serialization
|
|
19
|
+
* settings...
|
|
20
|
+
*
|
|
11
21
|
* XXX the current path implementation is fully complient to the task at
|
|
12
22
|
* hand but it will not suite the diff task as there is no way to know
|
|
13
23
|
* the meaning of the path element without seeing the object (map/set
|
|
@@ -94,6 +104,29 @@ module.MIN_LENGTH_REF = REFERENCE.length * 16
|
|
|
94
104
|
// -> str
|
|
95
105
|
//
|
|
96
106
|
//
|
|
107
|
+
// options format:
|
|
108
|
+
// {
|
|
109
|
+
// // Indent to use for formatting output.
|
|
110
|
+
// // Can be:
|
|
111
|
+
// // <number> - number of spaces to use for indent
|
|
112
|
+
// // <string> - string to use for indent
|
|
113
|
+
// // NOTE: only whitespace characters are supported
|
|
114
|
+
// // currently.
|
|
115
|
+
// indent: undefined,
|
|
116
|
+
//
|
|
117
|
+
// // Top level indent.
|
|
118
|
+
// depth: 0,
|
|
119
|
+
//
|
|
120
|
+
// // Minimum length of string/bigint to reference.
|
|
121
|
+
// min_length_ref: MIN_LENGTH_REF,
|
|
122
|
+
//
|
|
123
|
+
// // Stored functions.
|
|
124
|
+
// // If set to an array functions will be pushed to it and stored
|
|
125
|
+
// // by index.
|
|
126
|
+
// functions: undefined,
|
|
127
|
+
// }
|
|
128
|
+
//
|
|
129
|
+
//
|
|
97
130
|
// Paths
|
|
98
131
|
// A path is a chain of indexes/keys leading to a specific object in
|
|
99
132
|
// tree.
|
|
@@ -144,6 +177,8 @@ module.MIN_LENGTH_REF = REFERENCE.length * 16
|
|
|
144
177
|
var _serialize =
|
|
145
178
|
module._serialize =
|
|
146
179
|
function(obj, path=[], seen=new Map(), indent, depth=0, options={}){
|
|
180
|
+
if(typeof(indent) == 'number'){
|
|
181
|
+
indent = ' '.repeat(indent) }
|
|
147
182
|
var min_length_ref =
|
|
148
183
|
options.min_length_ref
|
|
149
184
|
?? module.MIN_LENGTH_REF
|
|
@@ -212,11 +247,13 @@ function(obj, path=[], seen=new Map(), indent, depth=0, options={}){
|
|
|
212
247
|
if(obj instanceof Array){
|
|
213
248
|
pre = '['
|
|
214
249
|
post = ']'
|
|
250
|
+
elems.length = obj.length
|
|
215
251
|
for(var i=0; i < obj.length; i++){
|
|
216
|
-
|
|
217
|
-
i
|
|
218
|
-
|
|
219
|
-
|
|
252
|
+
if(i in obj){
|
|
253
|
+
elems[i] = _serialize(obj[i], [...path, i], seen, indent, depth+1, options) } }
|
|
254
|
+
// add trailing coma if last elem is empty...
|
|
255
|
+
if(!(elems.length-1 in elems)){
|
|
256
|
+
elems.length++ }
|
|
220
257
|
} else if(obj instanceof Map){
|
|
221
258
|
pre = 'Map(['
|
|
222
259
|
post = '])'
|
|
@@ -260,6 +297,11 @@ function(obj, path=[], seen=new Map(), indent, depth=0, options={}){
|
|
|
260
297
|
var serialize =
|
|
261
298
|
module.serialize =
|
|
262
299
|
function(obj, indent, depth=0, options){
|
|
300
|
+
if(indent
|
|
301
|
+
&& typeof(indent) == 'object'){
|
|
302
|
+
options = indent
|
|
303
|
+
indent = options.indent
|
|
304
|
+
depth = options.depth ?? 0 }
|
|
263
305
|
return _serialize(obj, [], new Map(), indent, depth, options) }
|
|
264
306
|
|
|
265
307
|
|
|
@@ -340,8 +382,13 @@ module.eJSON = {
|
|
|
340
382
|
&& i == path.length-1){
|
|
341
383
|
return [path.slice(0, -1), obj] }
|
|
342
384
|
|
|
343
|
-
obj =
|
|
385
|
+
obj =
|
|
386
|
+
// special case: string key -> attribute...
|
|
387
|
+
typeof(k) == 'string' ?
|
|
388
|
+
obj[k]
|
|
389
|
+
: obj instanceof Set ?
|
|
344
390
|
[...obj][k]
|
|
391
|
+
// takes two items off the path -- [intex, key/value]...
|
|
345
392
|
: obj instanceof Map ?
|
|
346
393
|
[...obj][k][path[++i]]
|
|
347
394
|
: obj[k] }
|
|
@@ -365,6 +412,12 @@ module.eJSON = {
|
|
|
365
412
|
setItem: function(obj, path, value){
|
|
366
413
|
var [p, parent] = this._getItem(obj, path.slice(0, -1))
|
|
367
414
|
var k = path.at(-1)
|
|
415
|
+
|
|
416
|
+
// special case: string kye -> attr...
|
|
417
|
+
if(typeof(k) == 'string'){
|
|
418
|
+
parent[k] = value
|
|
419
|
+
return value }
|
|
420
|
+
|
|
368
421
|
if(parent instanceof Set){
|
|
369
422
|
var elems = [...parent]
|
|
370
423
|
elems[k] = value
|
|
@@ -695,7 +748,12 @@ module.eJSON = {
|
|
|
695
748
|
return this[handler](state, path, match, str, i, line) },
|
|
696
749
|
|
|
697
750
|
|
|
698
|
-
parse: function(str, options
|
|
751
|
+
parse: function(str, options){
|
|
752
|
+
options =
|
|
753
|
+
options === true ?
|
|
754
|
+
{functions: true}
|
|
755
|
+
: (options
|
|
756
|
+
?? {})
|
|
699
757
|
|
|
700
758
|
// stage 1: build the object...
|
|
701
759
|
var state = {functions: options.functions}
|
|
@@ -715,10 +773,6 @@ module.eJSON = {
|
|
|
715
773
|
var deserialize =
|
|
716
774
|
module.deserialize =
|
|
717
775
|
function(str, options){
|
|
718
|
-
options =
|
|
719
|
-
options === true ?
|
|
720
|
-
{functions: true}
|
|
721
|
-
: options
|
|
722
776
|
return eJSON.parse(str, options) }
|
|
723
777
|
|
|
724
778
|
|
package/test.js
CHANGED
|
@@ -82,13 +82,13 @@ var setups = test.Setups({
|
|
|
82
82
|
'array-empty': function(assert){
|
|
83
83
|
return ['[]', json] },
|
|
84
84
|
'array-sparse-a': function(assert){
|
|
85
|
-
return ['[
|
|
85
|
+
return ['[,]'] },
|
|
86
86
|
'array-sparse-b': function(assert){
|
|
87
|
-
return ['["a"
|
|
87
|
+
return ['["a",,]'] },
|
|
88
88
|
'array-sparse-c': function(assert){
|
|
89
|
-
return ['[
|
|
89
|
+
return ['[,"a"]'] },
|
|
90
90
|
'array-sparse-d': function(assert){
|
|
91
|
-
return ['[
|
|
91
|
+
return ['[,1,,,2,,]'] },
|
|
92
92
|
|
|
93
93
|
'object-empty': function(assert){
|
|
94
94
|
return ['{}', json] },
|