@awsless/json 0.0.10 → 0.0.11

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 CHANGED
@@ -1,18 +1,19 @@
1
-
2
1
  # @awsless/json
3
2
 
4
3
  The `@awsless/json` package adds support for more JavaScript native types to JSON.
5
4
 
6
5
  Features:
7
- - Lightweight / Using the JS native JSON parser.
8
- - JSON backwards compatible.
9
- - No precision loss.
10
- - Includes support for basic JS types.
11
- - Extendable.
6
+
7
+ - Lightweight / Using the JS native JSON parser.
8
+ - JSON backwards compatible.
9
+ - No precision loss.
10
+ - Includes support for basic JS types.
11
+ - Extendable.
12
12
 
13
13
  ## The Problem
14
14
 
15
15
  JSON doesn't have support for types like:
16
+
16
17
  - `undefined`
17
18
  - `NaN`
18
19
  - `Infinity`
@@ -33,7 +34,7 @@ Additionally, the native `JSON.parse/stringify` functions do not address the pot
33
34
  ## Basic Usage
34
35
 
35
36
  ```ts
36
- import { parse, stringify } from '@awsless/json';
37
+ import { parse, stringify } from '@awsless/json'
37
38
 
38
39
  // The output will be {"$bigint":"1"}
39
40
  const json = stringify(1n)
@@ -47,7 +48,7 @@ const value = parse(json)
47
48
  In some situations, you may not have control over the JSON parser being used. In these instances, your JSON can still be parsed, but the output may be incorrect. We can correct the inaccurate output by using the `patch` function.
48
49
 
49
50
  ```ts
50
- import { stringify, patch } from '@awsless/json';
51
+ import { stringify, patch } from '@awsless/json'
51
52
 
52
53
  const json = stringify(1n)
53
54
 
@@ -63,7 +64,7 @@ const fixed = patch(broken)
63
64
  We let you extend JSON to support your own custom types.
64
65
 
65
66
  ```ts
66
- import { parse, stringify, Serializable } from '@awsless/json';
67
+ import { parse, stringify, Serializable, setGlobalTypes } from '@awsless/json'
67
68
 
68
69
  class Custom {
69
70
  readonly value
@@ -80,10 +81,33 @@ const $custom: Serializable<Custom, string> = {
80
81
  }
81
82
 
82
83
  // Stringify your custom type.
83
- const json = stringify(new Custom('example'), { $custom })
84
+ const json = stringify(new Custom('example'), {
85
+ types: { $custom },
86
+ })
84
87
 
85
88
  // Parse the json with your custom type.
86
- const value = parse(json, { $custom })
89
+ const value = parse(json, {
90
+ types: { $custom },
91
+ })
92
+
93
+ // Additionally, you can globally add your own extensions.
94
+ setGlobalTypes({ $custom })
95
+ ```
96
+
97
+ ## Preserve undefined object properties
98
+
99
+ The native `JSON.stringify` will strip undefined object properties to generate smaller JSON output. We do the same thing but we have a special option to opt out of this behavior if correctness is important to you.
100
+
101
+ ```ts
102
+ const input = { key: undefined }
103
+
104
+ const smallerJson = stringify(input, {
105
+ preserveUndefinedValues: true,
106
+ }) // {}
107
+
108
+ const moreCorrectJson = stringify(input, {
109
+ preserveUndefinedValues: true,
110
+ }) // {"key":{"$undefined":0}}
87
111
  ```
88
112
 
89
113
  ## Precision Loss
@@ -91,15 +115,15 @@ const value = parse(json, { $custom })
91
115
  When using the native `JSON.parse/stringify` functions you could lose precision when parsing native numbers. And you don't always have the ability to extend JSON with your own custom types. For example when you’re communicating with a third-party API. For this reason, we have 2 utility functions that will parse the native JSON number type to your own precision-safe alternative.
92
116
 
93
117
  > [!NOTE]
94
- > These utility functions will only work in environments that support `JSON.rawJSON`
95
- > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/rawJSON#browser_compatibility
118
+ > These utility functions will only work in environments that support `JSON.rawJSON` > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/rawJSON#browser_compatibility
96
119
 
97
120
  ```ts
98
- import { safeNumberParse, safeNumberStringify } from '@awsless/json';
99
- import { BigFloat, eq } from '@awsless/big-float';
121
+ import { safeNumberParse, safeNumberStringify } from '@awsless/json'
122
+ import { BigFloat, eq } from '@awsless/big-float'
123
+
124
+ const one = new BigFloat(1)
100
125
 
101
- const value = new BigFloat(1)
102
- const json = safeNumberStringify(ONE, {
126
+ const json = safeNumberStringify(one, {
103
127
  is: v => v instanceof BigFloat,
104
128
  stringify: v => v.toString(),
105
129
  })
@@ -110,7 +134,7 @@ const result = safeNumberParse('1', {
110
134
  parse: v => new BigFloat(v),
111
135
  })
112
136
 
113
- console.log(eq(value, result)) // true
137
+ console.log(eq(one, result)) // true
114
138
  ```
115
139
 
116
140
  ## Known Issue's
package/dist/index.cjs CHANGED
@@ -20,12 +20,24 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ $bigfloat: () => $bigfloat,
24
+ $bigint: () => $bigint,
25
+ $binary: () => $binary,
26
+ $date: () => $date,
27
+ $duration: () => $duration,
28
+ $infinity: () => $infinity,
29
+ $map: () => $map,
23
30
  $mockdate: () => $mockdate,
31
+ $nan: () => $nan,
32
+ $regexp: () => $regexp,
33
+ $set: () => $set,
34
+ $undefined: () => $undefined,
35
+ $url: () => $url,
24
36
  createReplacer: () => createReplacer,
25
37
  createReviver: () => createReviver,
26
38
  createSafeNumberReplacer: () => createSafeNumberReplacer,
27
39
  createSafeNumberReviver: () => createSafeNumberReviver,
28
- parse: () => parse,
40
+ parse: () => parse2,
29
41
  patch: () => patch,
30
42
  safeNumberParse: () => safeNumberParse,
31
43
  safeNumberStringify: () => safeNumberStringify,
@@ -39,7 +51,7 @@ module.exports = __toCommonJS(index_exports);
39
51
  var import_big_float = require("@awsless/big-float");
40
52
  var $bigfloat = {
41
53
  is: (v) => v instanceof import_big_float.BigFloat,
42
- parse: (v) => new import_big_float.BigFloat(v),
54
+ parse: (v) => (0, import_big_float.parse)(v),
43
55
  stringify: (v) => v.toString()
44
56
  };
45
57
 
@@ -147,11 +159,11 @@ var baseTypes = {
147
159
  };
148
160
 
149
161
  // src/parse.ts
150
- var parse = (json, types = {}) => {
162
+ var parse2 = (json, options) => {
151
163
  const replacements = [];
152
164
  const result = JSON.parse(
153
165
  json,
154
- createReviver(types, (target, key, value) => {
166
+ createReviver(options?.types, (target, key, value) => {
155
167
  replacements.push([target, key, value]);
156
168
  })
157
169
  );
@@ -176,12 +188,10 @@ var createReviver = (types = {}, registerReplacement) => {
176
188
  const stringified = original[typeName];
177
189
  if ("parse" in type) {
178
190
  return type.parse(stringified);
179
- } else if (registerReplacement) {
191
+ } else {
180
192
  const result = type.replace(stringified);
181
- registerReplacement(this, key, result);
193
+ registerReplacement?.(this, key, result);
182
194
  return result;
183
- } else {
184
- return type.replace(stringified);
185
195
  }
186
196
  }
187
197
  }
@@ -191,16 +201,19 @@ var createReviver = (types = {}, registerReplacement) => {
191
201
  };
192
202
 
193
203
  // src/stringify.ts
194
- var stringify = (value, types = {}) => {
195
- return JSON.stringify(value, createReplacer(types));
204
+ var stringify = (value, options) => {
205
+ return JSON.stringify(value, createReplacer(options));
196
206
  };
197
- var createReplacer = (types = {}) => {
198
- types = {
207
+ var createReplacer = (options) => {
208
+ const types = {
199
209
  ...baseTypes,
200
- ...types
210
+ ...options?.types
201
211
  };
202
212
  return function(key, value) {
203
213
  const original = this[key];
214
+ if (!options?.preserveUndefinedValues && key && typeof original === "undefined" && typeof this === "object" && !Array.isArray(this)) {
215
+ return value;
216
+ }
204
217
  for (const [typeName, type] of Object.entries(types)) {
205
218
  if (type.is(original)) {
206
219
  return {
@@ -214,7 +227,7 @@ var createReplacer = (types = {}) => {
214
227
 
215
228
  // src/patch.ts
216
229
  var patch = (value, types = {}) => {
217
- return parse(JSON.stringify(value), types);
230
+ return parse2(JSON.stringify(value), types);
218
231
  };
219
232
  var unpatch = (value, types = {}) => {
220
233
  return JSON.parse(stringify(value, types));
@@ -225,13 +238,6 @@ var setGlobalTypes = (types) => {
225
238
  Object.assign(baseTypes, types);
226
239
  };
227
240
 
228
- // src/type/mockdate.ts
229
- var $mockdate = {
230
- is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function",
231
- parse: (v) => new Date(v),
232
- stringify: (v) => v.toISOString()
233
- };
234
-
235
241
  // src/safe-number/parse.ts
236
242
  var safeNumberParse = (json, props) => {
237
243
  return JSON.parse(
@@ -262,9 +268,28 @@ var createSafeNumberReplacer = (props) => {
262
268
  return value;
263
269
  };
264
270
  };
271
+
272
+ // src/type/mockdate.ts
273
+ var $mockdate = {
274
+ is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function",
275
+ parse: (v) => new Date(v),
276
+ stringify: (v) => v.toISOString()
277
+ };
265
278
  // Annotate the CommonJS export names for ESM import in node:
266
279
  0 && (module.exports = {
280
+ $bigfloat,
281
+ $bigint,
282
+ $binary,
283
+ $date,
284
+ $duration,
285
+ $infinity,
286
+ $map,
267
287
  $mockdate,
288
+ $nan,
289
+ $regexp,
290
+ $set,
291
+ $undefined,
292
+ $url,
268
293
  createReplacer,
269
294
  createReviver,
270
295
  createSafeNumberReplacer,
package/dist/index.d.cts CHANGED
@@ -1,3 +1,6 @@
1
+ import { BigFloat } from '@awsless/big-float';
2
+ import { Duration } from '@awsless/duration';
3
+
1
4
  type Serializable<I, O> = {
2
5
  is: (value: unknown) => boolean;
3
6
  stringify: (value: I) => O;
@@ -11,18 +14,23 @@ type SerializableTypes = Record<string, Serializable<any, any>>;
11
14
  declare const patch: (value: unknown, types?: SerializableTypes) => any;
12
15
  declare const unpatch: (value: unknown, types?: SerializableTypes) => any;
13
16
 
14
- declare const parse: (json: string, types?: SerializableTypes) => any;
17
+ type Options$1 = {
18
+ types?: SerializableTypes;
19
+ };
20
+ declare const parse: (json: string, options?: Options$1) => any;
15
21
  type Reviver$1 = (this: any, key: string, value: any) => any;
16
22
  declare const createReviver: (types?: SerializableTypes, registerReplacement?: (target: any, key: string, value: unknown) => void) => Reviver$1;
17
23
 
18
- declare const stringify: (value: unknown, types?: SerializableTypes) => string;
24
+ type Options = {
25
+ types?: SerializableTypes;
26
+ preserveUndefinedValues?: boolean;
27
+ };
28
+ declare const stringify: (value: unknown, options?: Options) => string;
19
29
  type Replacer$1 = (this: any, key: string, value: any) => any;
20
- declare const createReplacer: (types?: SerializableTypes) => Replacer$1;
30
+ declare const createReplacer: (options?: Options) => Replacer$1;
21
31
 
22
32
  declare const setGlobalTypes: (types: SerializableTypes) => void;
23
33
 
24
- declare const $mockdate: Serializable<Date, string>;
25
-
26
34
  type Props$1 = {
27
35
  parse: (value: string) => unknown;
28
36
  };
@@ -40,4 +48,30 @@ declare const safeNumberStringify: <T>(value: unknown, props: Props<T>) => strin
40
48
  type Replacer = (this: any, key: string, value: any) => any;
41
49
  declare const createSafeNumberReplacer: <T>(props: Props<T>) => Replacer;
42
50
 
43
- export { $mockdate, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch };
51
+ declare const $bigfloat: Serializable<BigFloat, string>;
52
+
53
+ declare const $bigint: Serializable<bigint, string>;
54
+
55
+ declare const $binary: Serializable<Uint8Array, string>;
56
+
57
+ declare const $date: Serializable<Date, string>;
58
+
59
+ declare const $duration: Serializable<Duration, string>;
60
+
61
+ declare const $infinity: Serializable<typeof Infinity, 1 | 0>;
62
+
63
+ declare const $map: Serializable<Map<unknown, unknown>, [unknown, unknown][]>;
64
+
65
+ declare const $mockdate: Serializable<Date, string>;
66
+
67
+ declare const $nan: Serializable<typeof NaN, 0>;
68
+
69
+ declare const $regexp: Serializable<RegExp, [string, string]>;
70
+
71
+ declare const $set: Serializable<Set<unknown>, unknown[]>;
72
+
73
+ declare const $undefined: Serializable<undefined, 0>;
74
+
75
+ declare const $url: Serializable<URL, string>;
76
+
77
+ export { $bigfloat, $bigint, $binary, $date, $duration, $infinity, $map, $mockdate, $nan, $regexp, $set, $undefined, $url, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { BigFloat } from '@awsless/big-float';
2
+ import { Duration } from '@awsless/duration';
3
+
1
4
  type Serializable<I, O> = {
2
5
  is: (value: unknown) => boolean;
3
6
  stringify: (value: I) => O;
@@ -11,18 +14,23 @@ type SerializableTypes = Record<string, Serializable<any, any>>;
11
14
  declare const patch: (value: unknown, types?: SerializableTypes) => any;
12
15
  declare const unpatch: (value: unknown, types?: SerializableTypes) => any;
13
16
 
14
- declare const parse: (json: string, types?: SerializableTypes) => any;
17
+ type Options$1 = {
18
+ types?: SerializableTypes;
19
+ };
20
+ declare const parse: (json: string, options?: Options$1) => any;
15
21
  type Reviver$1 = (this: any, key: string, value: any) => any;
16
22
  declare const createReviver: (types?: SerializableTypes, registerReplacement?: (target: any, key: string, value: unknown) => void) => Reviver$1;
17
23
 
18
- declare const stringify: (value: unknown, types?: SerializableTypes) => string;
24
+ type Options = {
25
+ types?: SerializableTypes;
26
+ preserveUndefinedValues?: boolean;
27
+ };
28
+ declare const stringify: (value: unknown, options?: Options) => string;
19
29
  type Replacer$1 = (this: any, key: string, value: any) => any;
20
- declare const createReplacer: (types?: SerializableTypes) => Replacer$1;
30
+ declare const createReplacer: (options?: Options) => Replacer$1;
21
31
 
22
32
  declare const setGlobalTypes: (types: SerializableTypes) => void;
23
33
 
24
- declare const $mockdate: Serializable<Date, string>;
25
-
26
34
  type Props$1 = {
27
35
  parse: (value: string) => unknown;
28
36
  };
@@ -40,4 +48,30 @@ declare const safeNumberStringify: <T>(value: unknown, props: Props<T>) => strin
40
48
  type Replacer = (this: any, key: string, value: any) => any;
41
49
  declare const createSafeNumberReplacer: <T>(props: Props<T>) => Replacer;
42
50
 
43
- export { $mockdate, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch };
51
+ declare const $bigfloat: Serializable<BigFloat, string>;
52
+
53
+ declare const $bigint: Serializable<bigint, string>;
54
+
55
+ declare const $binary: Serializable<Uint8Array, string>;
56
+
57
+ declare const $date: Serializable<Date, string>;
58
+
59
+ declare const $duration: Serializable<Duration, string>;
60
+
61
+ declare const $infinity: Serializable<typeof Infinity, 1 | 0>;
62
+
63
+ declare const $map: Serializable<Map<unknown, unknown>, [unknown, unknown][]>;
64
+
65
+ declare const $mockdate: Serializable<Date, string>;
66
+
67
+ declare const $nan: Serializable<typeof NaN, 0>;
68
+
69
+ declare const $regexp: Serializable<RegExp, [string, string]>;
70
+
71
+ declare const $set: Serializable<Set<unknown>, unknown[]>;
72
+
73
+ declare const $undefined: Serializable<undefined, 0>;
74
+
75
+ declare const $url: Serializable<URL, string>;
76
+
77
+ export { $bigfloat, $bigint, $binary, $date, $duration, $infinity, $map, $mockdate, $nan, $regexp, $set, $undefined, $url, type Serializable, createReplacer, createReviver, createSafeNumberReplacer, createSafeNumberReviver, parse, patch, safeNumberParse, safeNumberStringify, setGlobalTypes, stringify, unpatch };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/type/bigfloat.ts
2
- import { BigFloat } from "@awsless/big-float";
2
+ import { BigFloat, parse } from "@awsless/big-float";
3
3
  var $bigfloat = {
4
4
  is: (v) => v instanceof BigFloat,
5
- parse: (v) => new BigFloat(v),
5
+ parse: (v) => parse(v),
6
6
  stringify: (v) => v.toString()
7
7
  };
8
8
 
@@ -110,11 +110,11 @@ var baseTypes = {
110
110
  };
111
111
 
112
112
  // src/parse.ts
113
- var parse = (json, types = {}) => {
113
+ var parse2 = (json, options) => {
114
114
  const replacements = [];
115
115
  const result = JSON.parse(
116
116
  json,
117
- createReviver(types, (target, key, value) => {
117
+ createReviver(options?.types, (target, key, value) => {
118
118
  replacements.push([target, key, value]);
119
119
  })
120
120
  );
@@ -139,12 +139,10 @@ var createReviver = (types = {}, registerReplacement) => {
139
139
  const stringified = original[typeName];
140
140
  if ("parse" in type) {
141
141
  return type.parse(stringified);
142
- } else if (registerReplacement) {
142
+ } else {
143
143
  const result = type.replace(stringified);
144
- registerReplacement(this, key, result);
144
+ registerReplacement?.(this, key, result);
145
145
  return result;
146
- } else {
147
- return type.replace(stringified);
148
146
  }
149
147
  }
150
148
  }
@@ -154,16 +152,19 @@ var createReviver = (types = {}, registerReplacement) => {
154
152
  };
155
153
 
156
154
  // src/stringify.ts
157
- var stringify = (value, types = {}) => {
158
- return JSON.stringify(value, createReplacer(types));
155
+ var stringify = (value, options) => {
156
+ return JSON.stringify(value, createReplacer(options));
159
157
  };
160
- var createReplacer = (types = {}) => {
161
- types = {
158
+ var createReplacer = (options) => {
159
+ const types = {
162
160
  ...baseTypes,
163
- ...types
161
+ ...options?.types
164
162
  };
165
163
  return function(key, value) {
166
164
  const original = this[key];
165
+ if (!options?.preserveUndefinedValues && key && typeof original === "undefined" && typeof this === "object" && !Array.isArray(this)) {
166
+ return value;
167
+ }
167
168
  for (const [typeName, type] of Object.entries(types)) {
168
169
  if (type.is(original)) {
169
170
  return {
@@ -177,7 +178,7 @@ var createReplacer = (types = {}) => {
177
178
 
178
179
  // src/patch.ts
179
180
  var patch = (value, types = {}) => {
180
- return parse(JSON.stringify(value), types);
181
+ return parse2(JSON.stringify(value), types);
181
182
  };
182
183
  var unpatch = (value, types = {}) => {
183
184
  return JSON.parse(stringify(value, types));
@@ -188,13 +189,6 @@ var setGlobalTypes = (types) => {
188
189
  Object.assign(baseTypes, types);
189
190
  };
190
191
 
191
- // src/type/mockdate.ts
192
- var $mockdate = {
193
- is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function",
194
- parse: (v) => new Date(v),
195
- stringify: (v) => v.toISOString()
196
- };
197
-
198
192
  // src/safe-number/parse.ts
199
193
  var safeNumberParse = (json, props) => {
200
194
  return JSON.parse(
@@ -225,13 +219,32 @@ var createSafeNumberReplacer = (props) => {
225
219
  return value;
226
220
  };
227
221
  };
222
+
223
+ // src/type/mockdate.ts
224
+ var $mockdate = {
225
+ is: (v) => typeof v === "object" && v !== null && "toISOString" in v && typeof v.toISOString === "function" && "getTime" in v && typeof v.getTime === "function" && "toUTCString" in v && typeof v.toUTCString === "function",
226
+ parse: (v) => new Date(v),
227
+ stringify: (v) => v.toISOString()
228
+ };
228
229
  export {
230
+ $bigfloat,
231
+ $bigint,
232
+ $binary,
233
+ $date,
234
+ $duration,
235
+ $infinity,
236
+ $map,
229
237
  $mockdate,
238
+ $nan,
239
+ $regexp,
240
+ $set,
241
+ $undefined,
242
+ $url,
230
243
  createReplacer,
231
244
  createReviver,
232
245
  createSafeNumberReplacer,
233
246
  createSafeNumberReviver,
234
- parse,
247
+ parse2 as parse,
235
248
  patch,
236
249
  safeNumberParse,
237
250
  safeNumberStringify,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@awsless/json",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -41,12 +41,12 @@
41
41
  }
42
42
  },
43
43
  "peerDependencies": {
44
- "@awsless/big-float": "^0.0.6",
45
- "@awsless/duration": "^0.0.3"
44
+ "@awsless/big-float": "^0.1.5",
45
+ "@awsless/duration": "^0.0.4"
46
46
  },
47
47
  "devDependencies": {
48
- "@awsless/duration": "^0.0.3",
49
- "@awsless/big-float": "^0.0.6"
48
+ "@awsless/big-float": "^0.1.5",
49
+ "@awsless/duration": "^0.0.4"
50
50
  },
51
51
  "scripts": {
52
52
  "test": "pnpm code test",