fat-json 0.0.1 → 0.0.2
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 +87 -0
- package/package.json +8 -5
- package/src/common/index.d.mts +6 -2
- package/src/common/index.mjs +18 -8
- package/src/index.mjs +0 -63
- package/src/replacer/index.mjs +4 -6
- package/src/reviver/index.mjs +10 -14
- package/src/transformers/index.mjs +4 -6
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Fat Json
|
|
2
|
+
|
|
3
|
+
Stringify and parse non-standard JSON data types: _reviver_ and _replacer_ functions for the built-in `JSON` object
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i fat-json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Serialising and de-serialising
|
|
12
|
+
|
|
13
|
+
Transforming _standard data types_ using the built-in `JSON` object is simple, but the JavaScript language now features other types like `BigInt` which don't serialise/cannot be coerced to another type without potential data loss
|
|
14
|
+
|
|
15
|
+
Serialising and de-serialising with `JSON.stringify()` and `JSON.parse()` provides _some_ opportunity to safely transform data from one type to another with _replacer_ and _reviver_ functions, but while it's not hard to argue with the simplicity of transforming from a `BigInt` to a `String` ...
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
function replacer (key, value) {
|
|
19
|
+
if (typeof value === 'bigint') return String(value)
|
|
20
|
+
|
|
21
|
+
return value
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
... it's harder to see how to transform back from a `String` to a `BigInt` without additional information
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
function reviver (key, value) {
|
|
29
|
+
if (typeof value === 'string') return BigInt(value)
|
|
30
|
+
|
|
31
|
+
return value
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Should we use a catch block? Format the _key_ or _value_ in such away that we can test them, and then decide what to do?
|
|
36
|
+
|
|
37
|
+
_Fat Json_ will supply a _[JSONPath](https://en.wikipedia.org/wiki/JSONPath)_ as the third argument to your _replacer_ or _reviver_ function. If you know the structure of your data in advance you can serialise and de-serialise safely and in one step
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import {
|
|
41
|
+
useReplacerWithPath,
|
|
42
|
+
useReviverWithPath
|
|
43
|
+
} from 'fat-json'
|
|
44
|
+
|
|
45
|
+
const d = {
|
|
46
|
+
id: BigInt(Number.MAX_SAFE_INTEGER)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const s = JSON.stringify(d, useReplacerWithPath((key, value, path) => {
|
|
50
|
+
if (if path === '$.id') return String(value)
|
|
51
|
+
|
|
52
|
+
return value
|
|
53
|
+
}))
|
|
54
|
+
|
|
55
|
+
const o = JSON.parse(s, useReviverWithPath((key, value, path) => {
|
|
56
|
+
if (path === '$.id') return BigInt(value)
|
|
57
|
+
|
|
58
|
+
return value
|
|
59
|
+
}))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
_Any data_ which can be represented as a `String` can be serialised and de-serialised in the same way
|
|
63
|
+
|
|
64
|
+
### _[From Wikipedia](https://en.wikipedia.org/wiki/JSON)_
|
|
65
|
+
|
|
66
|
+
> JSON's basic data types are:
|
|
67
|
+
>
|
|
68
|
+
> - Number: a signed decimal number that may contain a fractional part and may
|
|
69
|
+
> use exponential E notation but cannot include non-numbers such as NaN. The
|
|
70
|
+
> format makes no distinction between integer and floating-point. JavaScript
|
|
71
|
+
> uses IEEE-754 double-precision floating-point format for all its numeric
|
|
72
|
+
> values (later also supporting BigInt), but other languages implementing JSON
|
|
73
|
+
> may encode numbers differently.
|
|
74
|
+
> - String: a sequence of zero or more Unicode characters. Strings are delimited
|
|
75
|
+
> with double quotation marks and support a backslash escaping syntax.
|
|
76
|
+
> - Boolean: either of the values true or false
|
|
77
|
+
> - Array: an ordered list of zero or more elements, each of which may be of any
|
|
78
|
+
> type. Arrays use square bracket notation with comma-separated elements.
|
|
79
|
+
> - Object: a collection of name–value pairs where the names (also called keys)
|
|
80
|
+
> are strings. The current ECMA standard states, "The JSON syntax does not
|
|
81
|
+
> impose any restrictions on the strings used as names, does not require that
|
|
82
|
+
> name strings be unique, and does not assign any significance to the ordering
|
|
83
|
+
> of name/value pairs." Objects are delimited with curly brackets and use
|
|
84
|
+
> commas to separate each pair, while within each pair, the colon ":" character
|
|
85
|
+
> separates the key or name from its value.
|
|
86
|
+
> - null: an empty value, using the word null
|
|
87
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fat-json",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"main": "./src/index.mjs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./src/index.d.mts",
|
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
},
|
|
17
17
|
"scripts": {
|
|
18
18
|
"lint": "eslint .",
|
|
19
|
-
"lint:fix": "npm run lint -- --fix"
|
|
19
|
+
"lint:fix": "npm run lint -- --fix",
|
|
20
|
+
"prepare": "husky",
|
|
21
|
+
"test": "mocha test --recursive"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
24
|
"@sequencemedia/eslint-config-standard": "^0.2.82",
|
|
23
25
|
"@sequencemedia/eslint-config-typescript": "^0.1.141",
|
|
24
|
-
"@sequencemedia/sinon-chai": "^0.0.186",
|
|
25
26
|
"@types/chai": "^5.2.2",
|
|
26
27
|
"@types/mocha": "^10.0.10",
|
|
27
28
|
"@types/node": "^24.5.0",
|
|
@@ -29,12 +30,14 @@
|
|
|
29
30
|
"chai": "^6.0.1",
|
|
30
31
|
"eslint": "^9.35.0",
|
|
31
32
|
"globals": "^16.4.0",
|
|
33
|
+
"husky": "^9.1.7",
|
|
32
34
|
"mocha": "^11.7.2",
|
|
33
|
-
"sinon": "^21.0.0"
|
|
35
|
+
"sinon": "^21.0.0",
|
|
36
|
+
"sinon-chai": "^4.0.1"
|
|
34
37
|
},
|
|
35
38
|
"imports": {
|
|
36
|
-
"#fat-json": "./src/index.mjs",
|
|
37
39
|
"#common": "./src/common/index.mjs",
|
|
40
|
+
"#fat-json": "./src/index.mjs",
|
|
38
41
|
"#transformers": "./src/transformers/index.mjs"
|
|
39
42
|
}
|
|
40
43
|
}
|
package/src/common/index.d.mts
CHANGED
|
@@ -6,6 +6,10 @@ export {
|
|
|
6
6
|
BIG_INT
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export function
|
|
9
|
+
export function formatNumber (key: number): string
|
|
10
10
|
|
|
11
|
-
export function
|
|
11
|
+
export function formatString (key: string): string
|
|
12
|
+
|
|
13
|
+
export function isArray (value: ValueType | ValueType[]): value is any[]
|
|
14
|
+
|
|
15
|
+
export function isObject (value: ValueType | ValueType[]): value is object
|
package/src/common/index.mjs
CHANGED
|
@@ -4,18 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
export const BIG_INT = /^(\d+)n$/
|
|
6
6
|
|
|
7
|
+
export function formatNumber (key) {
|
|
8
|
+
return `[${key}]`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatString (key) {
|
|
12
|
+
return /[ .]/.test(key)
|
|
13
|
+
? `.['${key}']`
|
|
14
|
+
: `.${key}`
|
|
15
|
+
}
|
|
16
|
+
|
|
7
17
|
/**
|
|
8
|
-
* @param {ValueType | ValueType[]}
|
|
9
|
-
* @returns {
|
|
18
|
+
* @param {ValueType | ValueType[]} value
|
|
19
|
+
* @returns {value is any[]}
|
|
10
20
|
*/
|
|
11
|
-
export function isArray (
|
|
12
|
-
return Array.isArray(
|
|
21
|
+
export function isArray (value) {
|
|
22
|
+
return Array.isArray(value)
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
/**
|
|
16
|
-
* @param {ValueType | ValueType[]}
|
|
17
|
-
* @returns {
|
|
26
|
+
* @param {ValueType | ValueType[]} value
|
|
27
|
+
* @returns {value is object}
|
|
18
28
|
*/
|
|
19
|
-
export function isObject (
|
|
20
|
-
return (
|
|
29
|
+
export function isObject (value) {
|
|
30
|
+
return (value || false) instanceof Object && !Array.isArray(value)
|
|
21
31
|
}
|
package/src/index.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {FatJsonTypes.ValueType} ValueType
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
BIG_INT
|
|
6
|
-
} from './common/index.mjs'
|
|
7
4
|
import getPath from './replacer/index.mjs'
|
|
8
5
|
import genPath from './reviver/index.mjs'
|
|
9
6
|
|
|
@@ -89,63 +86,3 @@ export function useReviverWithPath (reviver) {
|
|
|
89
86
|
return value
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* @param {PropertyKey} key
|
|
95
|
-
* @param {ValueType} value
|
|
96
|
-
* @returns {ValueType}
|
|
97
|
-
*/
|
|
98
|
-
export function replacer (key, value) {
|
|
99
|
-
if (typeof value === 'bigint') return String(value) + 'n'
|
|
100
|
-
|
|
101
|
-
return value
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* @param {PropertyKey} key
|
|
106
|
-
* @param {ValueType} value
|
|
107
|
-
* @returns {ValueType}
|
|
108
|
-
*/
|
|
109
|
-
export function reviver (key, value) {
|
|
110
|
-
if (typeof value === 'string' && BIG_INT.test(value)) return BigInt (value.replace(BIG_INT, '$1'))
|
|
111
|
-
|
|
112
|
-
return value
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* @param {Set<PropertyKey>} [set]
|
|
117
|
-
* @returns {(key: PropertyKey, value: ValueType) => ValueType}
|
|
118
|
-
*/
|
|
119
|
-
export function getReplacerFor (set = new Set()) {
|
|
120
|
-
/**
|
|
121
|
-
* @param {PropertyKey} key
|
|
122
|
-
* @param {ValueType} value
|
|
123
|
-
* @returns {ValueType}
|
|
124
|
-
*/
|
|
125
|
-
return function replacer (key, value) {
|
|
126
|
-
if (set.has(key)) {
|
|
127
|
-
if (typeof value === 'bigint') return String(value)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return value
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @param {Set<PropertyKey>} [set]
|
|
136
|
-
* @returns {(key: PropertyKey, value: ValueType) => ValueType}
|
|
137
|
-
*/
|
|
138
|
-
export function getReviverFor (set = new Set()) {
|
|
139
|
-
/**
|
|
140
|
-
* @param {PropertyKey} key
|
|
141
|
-
* @param {ValueType} value
|
|
142
|
-
* @returns {ValueType}
|
|
143
|
-
*/
|
|
144
|
-
return function reviver (key, value) {
|
|
145
|
-
if (set.has(key)) {
|
|
146
|
-
if (typeof value === 'string') return BigInt(value)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return value
|
|
150
|
-
}
|
|
151
|
-
}
|
package/src/replacer/index.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
formatNumber,
|
|
3
|
+
formatString,
|
|
2
4
|
isArray,
|
|
3
5
|
isObject
|
|
4
6
|
} from '#common'
|
|
@@ -20,12 +22,8 @@ export default function getPath (key, value, context) {
|
|
|
20
22
|
valuePath = (
|
|
21
23
|
contextPath + (
|
|
22
24
|
Array.isArray(context)
|
|
23
|
-
?
|
|
24
|
-
: (
|
|
25
|
-
/[ .]/.test(key)
|
|
26
|
-
? `.['${key}']`
|
|
27
|
-
: `.${key}`
|
|
28
|
-
)
|
|
25
|
+
? formatNumber(key)
|
|
26
|
+
: formatString(key)
|
|
29
27
|
)
|
|
30
28
|
)
|
|
31
29
|
}
|
package/src/reviver/index.mjs
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
+
formatNumber,
|
|
10
|
+
formatString,
|
|
9
11
|
isArray,
|
|
10
12
|
isObject
|
|
11
13
|
} from '#common'
|
|
@@ -16,15 +18,17 @@ import {
|
|
|
16
18
|
* @returns {IterableIterator<{ key: PropertyKey; value: unknown; path: string; context: any }>}
|
|
17
19
|
*/
|
|
18
20
|
function * genPathForArray (context, contextPath) {
|
|
19
|
-
for (const [
|
|
20
|
-
const valuePath =
|
|
21
|
+
for (const [key, value] of context.entries()) {
|
|
22
|
+
const valuePath = (
|
|
23
|
+
contextPath + formatNumber(key)
|
|
24
|
+
)
|
|
21
25
|
|
|
22
26
|
if (isArray(value)) yield * genPathForArray(value, valuePath)
|
|
23
27
|
|
|
24
28
|
if (isObject(value)) yield * genPathForObject(value, valuePath)
|
|
25
29
|
|
|
26
30
|
yield {
|
|
27
|
-
key: String(
|
|
31
|
+
key: String(key),
|
|
28
32
|
value,
|
|
29
33
|
path: valuePath,
|
|
30
34
|
context
|
|
@@ -40,11 +44,7 @@ function * genPathForArray (context, contextPath) {
|
|
|
40
44
|
function * genPathForObject (context, contextPath) {
|
|
41
45
|
for (const [key, value] of Object.entries(context)) {
|
|
42
46
|
const valuePath = (
|
|
43
|
-
contextPath + (
|
|
44
|
-
/[ .]/.test(key)
|
|
45
|
-
? `.['${key}']`
|
|
46
|
-
: `.${key}`
|
|
47
|
-
)
|
|
47
|
+
contextPath + formatString(key)
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
if (isArray(value)) yield * genPathForArray(value, valuePath)
|
|
@@ -69,12 +69,8 @@ function * genPathForObject (context, contextPath) {
|
|
|
69
69
|
export default function * genPath (key, value, context) {
|
|
70
70
|
const valuePath = key
|
|
71
71
|
? typeof key === 'number'
|
|
72
|
-
?
|
|
73
|
-
: (
|
|
74
|
-
/[ .]/.test(key)
|
|
75
|
-
? `.['${key}']`
|
|
76
|
-
: `.${key}`
|
|
77
|
-
)
|
|
72
|
+
? formatNumber(key)
|
|
73
|
+
: formatString(key)
|
|
78
74
|
: '$'
|
|
79
75
|
|
|
80
76
|
if (isArray(value)) yield * genPathForArray(value, valuePath)
|
|
@@ -10,7 +10,7 @@ export function fromBigIntToString (value) {
|
|
|
10
10
|
* @param {string} value
|
|
11
11
|
* @returns {BigInt}
|
|
12
12
|
*/
|
|
13
|
-
export function
|
|
13
|
+
export function formatStringToBigInt (value) {
|
|
14
14
|
return BigInt(value)
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -18,10 +18,8 @@ export function fromStringToBigInt (value) {
|
|
|
18
18
|
* @param {string} b
|
|
19
19
|
* @returns {number}
|
|
20
20
|
*/
|
|
21
|
-
function
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return Number(c)
|
|
21
|
+
function toInteger (b) {
|
|
22
|
+
return b.codePointAt(0) ?? 0
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/**
|
|
@@ -33,7 +31,7 @@ function toCodePoint (b) {
|
|
|
33
31
|
*/
|
|
34
32
|
export function fromBase64ToBuffer (base64) {
|
|
35
33
|
const binary = atob(base64) // base64 string to binary string
|
|
36
|
-
const uint8Array = Uint8Array.from(binary,
|
|
34
|
+
const uint8Array = Uint8Array.from(binary, toInteger) // binary string to code point integer
|
|
37
35
|
return Buffer.from(uint8Array)
|
|
38
36
|
}
|
|
39
37
|
|