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 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.1",
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
  }
@@ -6,6 +6,10 @@ export {
6
6
  BIG_INT
7
7
  }
8
8
 
9
- export function iArray (v: ValueType | ValueType[]): v is any[]
9
+ export function formatNumber (key: number): string
10
10
 
11
- export function isObject (v: ValueType | ValueType[]): v is object
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
@@ -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[]} v
9
- * @returns {v is any[]}
18
+ * @param {ValueType | ValueType[]} value
19
+ * @returns {value is any[]}
10
20
  */
11
- export function isArray (v) {
12
- return Array.isArray(v)
21
+ export function isArray (value) {
22
+ return Array.isArray(value)
13
23
  }
14
24
 
15
25
  /**
16
- * @param {ValueType | ValueType[]} v
17
- * @returns {v is object}
26
+ * @param {ValueType | ValueType[]} value
27
+ * @returns {value is object}
18
28
  */
19
- export function isObject (v) {
20
- return (v || false) instanceof Object && !Array.isArray(v)
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
- }
@@ -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
- ? `[${key}]`
24
- : ( // valuePath.includes(SP) || valuePath.includes(ST)
25
- /[ .]/.test(key)
26
- ? `.['${key}']`
27
- : `.${key}`
28
- )
25
+ ? formatNumber(key)
26
+ : formatString(key)
29
27
  )
30
28
  )
31
29
  }
@@ -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 [index, value] of context.entries()) {
20
- const valuePath = contextPath + `[${index}]`
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(index),
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
- ? `[${key}]`
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 fromStringToBigInt (value) {
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 toCodePoint (b) {
22
- const c = b.codePointAt(0)
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, toCodePoint) // binary string to code point integer
34
+ const uint8Array = Uint8Array.from(binary, toInteger) // binary string to code point integer
37
35
  return Buffer.from(uint8Array)
38
36
  }
39
37