@squiz/dx-common-lib 1.21.1-alpha.9 → 1.22.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.
Files changed (54) hide show
  1. package/.npm/_logs/2023-03-29T01_26_08_740Z-debug-0.log +37 -0
  2. package/lib/index.d.ts +1 -0
  3. package/lib/index.js +1 -0
  4. package/lib/index.js.map +1 -1
  5. package/lib/json-order/index.d.ts +6 -0
  6. package/lib/json-order/index.js +11 -0
  7. package/lib/json-order/index.js.map +1 -0
  8. package/lib/json-order/key.d.ts +2 -0
  9. package/lib/json-order/key.js +44 -0
  10. package/lib/json-order/key.js.map +1 -0
  11. package/lib/json-order/key.spec.d.ts +1 -0
  12. package/lib/json-order/key.spec.js +40 -0
  13. package/lib/json-order/key.spec.js.map +1 -0
  14. package/lib/json-order/models.d.ts +7 -0
  15. package/lib/json-order/models.js +3 -0
  16. package/lib/json-order/models.js.map +1 -0
  17. package/lib/json-order/order.d.ts +11 -0
  18. package/lib/json-order/order.js +75 -0
  19. package/lib/json-order/order.js.map +1 -0
  20. package/lib/json-order/order.spec.d.ts +1 -0
  21. package/lib/json-order/order.spec.js +191 -0
  22. package/lib/json-order/order.spec.js.map +1 -0
  23. package/lib/json-order/parse.d.ts +11 -0
  24. package/lib/json-order/parse.js +47 -0
  25. package/lib/json-order/parse.js.map +1 -0
  26. package/lib/json-order/parse.spec.d.ts +1 -0
  27. package/lib/json-order/parse.spec.js +260 -0
  28. package/lib/json-order/parse.spec.js.map +1 -0
  29. package/lib/json-order/stringify.d.ts +12 -0
  30. package/lib/json-order/stringify.js +20 -0
  31. package/lib/json-order/stringify.js.map +1 -0
  32. package/lib/json-order/stringify.spec.d.ts +1 -0
  33. package/lib/json-order/stringify.spec.js +123 -0
  34. package/lib/json-order/stringify.spec.js.map +1 -0
  35. package/lib/server-utils/errorMiddleware.js +1 -0
  36. package/lib/server-utils/errorMiddleware.js.map +1 -1
  37. package/lib/server-utils/errorMiddleware.spec.js +1 -0
  38. package/lib/server-utils/errorMiddleware.spec.js.map +1 -1
  39. package/package.json +9 -6
  40. package/src/index.ts +1 -0
  41. package/src/json-order/index.ts +6 -0
  42. package/src/json-order/key.spec.ts +39 -0
  43. package/src/json-order/key.ts +40 -0
  44. package/src/json-order/models.ts +8 -0
  45. package/src/json-order/order.spec.ts +251 -0
  46. package/src/json-order/order.ts +95 -0
  47. package/src/json-order/parse.spec.ts +302 -0
  48. package/src/json-order/parse.ts +59 -0
  49. package/src/json-order/stringify.spec.ts +170 -0
  50. package/src/json-order/stringify.ts +17 -0
  51. package/src/server-utils/errorMiddleware.spec.ts +1 -0
  52. package/src/server-utils/errorMiddleware.ts +2 -0
  53. package/tsconfig.tsbuildinfo +1 -1
  54. package/.npm/_logs/2023-03-08T03_21_21_200Z-debug-0.log +0 -39
@@ -0,0 +1,251 @@
1
+ import order from './order';
2
+ import { PropertyMap } from './models';
3
+
4
+ describe('order()', () => {
5
+ const expectObject = <T extends object>(obj: T, map: PropertyMap | null, res: string) =>
6
+ expect(JSON.stringify(order(obj, map, '.'))).toBe(res);
7
+
8
+ it('returns nothing for a blank JSON string', () => expectObject({}, {}, '{}'));
9
+
10
+ it('throws error if separator is an empty string', () => {
11
+ expect(() => order({}, {}, '')).toThrowError('Separator should not be an empty string.');
12
+ });
13
+
14
+ it('throws error if separator is a slash', () => {
15
+ expect(() => order({}, {}, '\\')).toThrowError('Separator cannot be "\\".');
16
+ });
17
+
18
+ it('ignores properties not found in source', () => expectObject({}, { $: ['a'] }, '{}'));
19
+
20
+ it('returns regular json string if map is undefined', () =>
21
+ expectObject({ a: '1', b: '2' }, null, '{"a":"1","b":"2"}'));
22
+
23
+ it('ignores properties not found in map', () => expectObject({ a: '1', b: '2' }, { $: ['b'] }, '{"b":"2"}'));
24
+
25
+ it('returns first level object properties in order', () =>
26
+ expectObject({ a: 2, b: 1 }, { $: ['b', 'a'] }, '{"b":1,"a":2}'));
27
+
28
+ it('returns first level array value in order', () =>
29
+ expectObject({ a: ['2', 1, true] }, { $: ['a'] }, '{"a":["2",1,true]}'));
30
+
31
+ it('returns nested [array] > [object] properties in expected order', () =>
32
+ expectObject({ a: [1, { c: '3', d: '2' }] }, { $: ['a'], '$.a.1': ['d', 'c'] }, '{"a":[1,{"d":"2","c":"3"}]}'));
33
+
34
+ it('ignores nested [array] > [object] properties partially not found in map', () =>
35
+ expectObject({ a: [1, { b: 2, c: 3 }, 4] }, { $: ['a'], '$.a.1': ['c'] }, '{"a":[1,{"c":3},4]}'));
36
+
37
+ it('ignores nested [array] > [object] properties not found in map', () =>
38
+ expectObject({ a: [1, { b: 2, c: 3 }, 4] }, { $: ['a'] }, '{"a":[1,{},4]}'));
39
+
40
+ it('handles multi-character prefix', () => {
41
+ expect(
42
+ order(
43
+ {
44
+ a: {
45
+ b: {
46
+ c: 3,
47
+ d: 4,
48
+ },
49
+ e: {
50
+ f: 4,
51
+ g: 5,
52
+ },
53
+ h: 6,
54
+ },
55
+ i: 7,
56
+ },
57
+ {
58
+ ab: ['i', 'a'],
59
+ 'ab.a': ['e', 'h', 'b'],
60
+ 'ab.a.e': ['g', 'f'],
61
+ 'ab.a.b': ['d', 'c'],
62
+ },
63
+ '.',
64
+ ),
65
+ ).toEqual({ i: 7, a: { e: { g: 5, f: 4 }, h: 6, b: { d: 4, c: 3 } } });
66
+ });
67
+
68
+ it('handles multi-character separator', () => {
69
+ expect(
70
+ order(
71
+ {
72
+ a: {
73
+ b: {
74
+ c: 3,
75
+ d: 4,
76
+ },
77
+ e: {
78
+ f: 4,
79
+ g: 5,
80
+ },
81
+ h: 6,
82
+ },
83
+ i: 7,
84
+ },
85
+ {
86
+ $: ['i', 'a'],
87
+ '$~|a': ['e', 'h', 'b'],
88
+ '$~|a~|e': ['g', 'f'],
89
+ '$~|a~|b': ['d', 'c'],
90
+ },
91
+ '~|',
92
+ ),
93
+ ).toEqual({ i: 7, a: { e: { g: 5, f: 4 }, h: 6, b: { d: 4, c: 3 } } });
94
+ });
95
+
96
+ it('returns nested [object] > [object] properties in expected order', () =>
97
+ expectObject(
98
+ {
99
+ a: {
100
+ b: {
101
+ c: 3,
102
+ d: 4,
103
+ },
104
+ e: {
105
+ f: 4,
106
+ g: 5,
107
+ },
108
+ h: 6,
109
+ },
110
+ i: 7,
111
+ },
112
+ {
113
+ $: ['i', 'a'],
114
+ '$.a': ['e', 'h', 'b'],
115
+ '$.a.e': ['g', 'f'],
116
+ '$.a.b': ['d', 'c'],
117
+ },
118
+ '{"i":7,"a":{"e":{"g":5,"f":4},"h":6,"b":{"d":4,"c":3}}}',
119
+ ));
120
+
121
+ it('returns nested [object] > [array] > [object] > [array] > [object] properties in expected order', () =>
122
+ expectObject(
123
+ {
124
+ a: {
125
+ b: [
126
+ 8,
127
+ {
128
+ c: 9,
129
+ d: [
130
+ {
131
+ e: 12,
132
+ f: {
133
+ g: true,
134
+ h: 'h',
135
+ },
136
+ },
137
+ 10,
138
+ ],
139
+ },
140
+ 11,
141
+ ],
142
+ },
143
+ i: 7,
144
+ },
145
+ {
146
+ $: ['i', 'a'],
147
+ '$.a': ['b'],
148
+ '$.a.b.1': ['d', 'c'],
149
+ '$.a.b.1.d.0': ['f', 'e'],
150
+ '$.a.b.1.d.0.f': ['h', 'g'],
151
+ },
152
+ '{"i":7,"a":{"b":[8,{"d":[{"f":{"h":"h","g":true},"e":12},10],"c":9},11]}}',
153
+ ));
154
+
155
+ it('handles keys with no name', () => {
156
+ expectObject(
157
+ {
158
+ '': {
159
+ b: 'str',
160
+ a: 'str',
161
+ c: 'str',
162
+ },
163
+ },
164
+ {
165
+ $: [''],
166
+ '$.': ['c', 'b', 'a'],
167
+ },
168
+ '{"":{"c":"str","b":"str","a":"str"}}',
169
+ );
170
+ });
171
+
172
+ it('handles escape sequences in the object', () => {
173
+ expectObject(
174
+ {
175
+ '.a': {
176
+ b: { t: 'str' },
177
+ c: { u: 'str' },
178
+ a: { s: 'str' },
179
+ },
180
+ '\\': {
181
+ a: { v: 'str' },
182
+ },
183
+ '\\.': {
184
+ a: { w: 'str' },
185
+ b: { x: 'str' },
186
+ },
187
+ '.': {
188
+ b: { y: 'str' },
189
+ a: { z: 'str' },
190
+ },
191
+ },
192
+ {
193
+ $: ['.', '\\.', '\\', '.a'],
194
+ '$.\\.': ['a', 'b'],
195
+ '$.\\..a': ['z'],
196
+ '$.\\..b': ['y'],
197
+ '$.\\\\\\.': ['b', 'a'],
198
+ '$.\\\\\\..b': ['x'],
199
+ '$.\\\\\\..a': ['w'],
200
+ '$.\\\\': ['a'],
201
+ '$.\\\\.a': ['v'],
202
+ '$.\\.a': ['c', 'b', 'a'],
203
+ '$.\\.a.c': ['u'],
204
+ '$.\\.a.b': ['t'],
205
+ '$.\\.a.a': ['s'],
206
+ },
207
+ '{".":{"a":{"z":"str"},"b":{"y":"str"}},' +
208
+ '"\\\\.":{"b":{"x":"str"},"a":{"w":"str"}},' +
209
+ '"\\\\":{"a":{"v":"str"}},' +
210
+ '".a":{"c":{"u":"str"},"b":{"t":"str"},"a":{"s":"str"}}}',
211
+ );
212
+ });
213
+
214
+ it('handles escape sequences in child properties of the object', () => {
215
+ expectObject(
216
+ {
217
+ property: {
218
+ '..': { '.': 4, '..': 3 },
219
+ '.': { '..': 0, '...': 2, '.': 1 },
220
+ '...': { '.': 5 },
221
+ },
222
+ },
223
+ {
224
+ $: ['property'],
225
+ '$.property': ['.', '..', '...'],
226
+ '$.property.\\.': ['..', '.', '...'],
227
+ '$.property.\\.\\.': ['..', '.'],
228
+ '$.property.\\.\\.\\.': ['.'],
229
+ },
230
+ '{"property":{".":{"..":0,".":1,"...":2},"..":{"..":3,".":4},"...":{".":5}}}',
231
+ );
232
+ });
233
+
234
+ it('numeric key order defined in map is lost', () => {
235
+ // Numeric keys aren't ordered per map but instead appear first in ascending order.
236
+ // See: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
237
+ expectObject(
238
+ {
239
+ 4: 'str',
240
+ a: 'str',
241
+ 3: 'str',
242
+ b: 'str',
243
+ 2: 'str',
244
+ },
245
+ {
246
+ $: ['a', '4', 'b', '3', '2'],
247
+ },
248
+ '{"2":"str","3":"str","4":"str","a":"str","b":"str"}',
249
+ );
250
+ });
251
+ });
@@ -0,0 +1,95 @@
1
+ import clonedeep from 'lodash.clonedeep';
2
+ import { escapeKey, splitKey } from './key';
3
+ import { PropertyMap } from './models';
4
+
5
+ interface GetResult {
6
+ exists: boolean;
7
+ value: object;
8
+ }
9
+
10
+ const getProperty = (obj: object, key: string, separator: string): GetResult => {
11
+ let exists = true;
12
+
13
+ const value = splitKey(key, separator)
14
+ .slice(1)
15
+ .reduce((o: Record<string, any>, x: string) => {
16
+ exists = o && x in o;
17
+
18
+ if (!exists) {
19
+ return undefined;
20
+ }
21
+
22
+ return o[x];
23
+ }, obj);
24
+
25
+ return { exists, value };
26
+ };
27
+
28
+ const setProperty = (obj: object, key: string, value: object, separator: string) => {
29
+ splitKey(key, separator)
30
+ .slice(1)
31
+ .reduce((o: Record<string, any>, x: string, idx: number, src: Array<string>): object => {
32
+ if (idx === src.length - 1) {
33
+ const valueToSet = Array.isArray(value) ? clonedeep(value).map((p) => (typeof p === 'object' ? {} : p)) : value;
34
+ o[x] = valueToSet;
35
+ }
36
+
37
+ return o[x];
38
+ }, obj);
39
+ };
40
+
41
+ const copyProperty = (sourceObject: object, resultObject: object, propertyPath: string, separator: string) => {
42
+ const result = getProperty(sourceObject, propertyPath, separator);
43
+ if (result.exists) {
44
+ setProperty(resultObject, propertyPath, result.value, separator);
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Duplicate a JS object but containing a particular property order
50
+ *
51
+ * @param sourceObject an object with the properties in any order
52
+ * @param map the `PropertyMap` generated by the `parse` method
53
+ * @param separator a non-empty `string` that controls what the key separator is in the generated map. Defaults to `~`.
54
+ * @returns the source object ordered as per the map
55
+ */
56
+ const order = <T extends object>(sourceObject: T, map: PropertyMap | null, separator = '~'): T => {
57
+ if (separator.length < 1) {
58
+ throw new Error('Separator should not be an empty string.');
59
+ } else if (separator === '\\') {
60
+ throw new Error('Separator cannot be "\\".');
61
+ }
62
+
63
+ if (!map) {
64
+ return sourceObject;
65
+ }
66
+
67
+ const mapKeys = Object.keys(map);
68
+ const prefixLength = (mapKeys[0] && mapKeys[0].length) || 0;
69
+
70
+ const resultObject = {};
71
+ mapKeys.forEach((mk) => {
72
+ const childKeys = map[mk];
73
+
74
+ // Remove prefix
75
+ const parentKey = mk.substr(prefixLength);
76
+
77
+ const parent = getProperty(sourceObject, parentKey, separator);
78
+
79
+ if (parent.exists) {
80
+ // Set a default value for the property
81
+ const defaultValue = Array.isArray(parent.value) ? parent.value : {};
82
+
83
+ setProperty(resultObject, parentKey, defaultValue, separator);
84
+
85
+ // Fetch value from source and set on output
86
+ childKeys.forEach((key) =>
87
+ copyProperty(sourceObject, resultObject, `${parentKey}${separator}${escapeKey(key, separator)}`, separator),
88
+ );
89
+ }
90
+ });
91
+
92
+ return resultObject as T;
93
+ };
94
+
95
+ export default order;
@@ -0,0 +1,302 @@
1
+ import { PropertyMap } from './models';
2
+ import parse from './parse';
3
+
4
+ describe('parse ', () => {
5
+ const expectMap = (input: string, map: PropertyMap) => expect(parse(input, '$', '.').map).toEqual(map);
6
+
7
+ it('returns nothing for a blank JSON string', () => expectMap('{}', {}));
8
+
9
+ it('throws error if prefix is an empty string', () => {
10
+ expect(() => parse('', '', '.')).toThrowError('Prefix should not be an empty string.');
11
+ });
12
+
13
+ it('throws error if separator is an empty string', () => {
14
+ expect(() => parse('', '$', '')).toThrowError('Separator should not be an empty string.');
15
+ });
16
+
17
+ it('throws error if separator is a slash', () => {
18
+ expect(() => parse('', '$', '\\')).toThrowError('Separator cannot be "\\".');
19
+ });
20
+
21
+ it('handles top level values for of primitive types', () => {
22
+ const input = `
23
+ {
24
+ "b": "str",
25
+ "c": 3,
26
+ "a": true
27
+ }`;
28
+
29
+ const map = {
30
+ $: ['b', 'c', 'a'],
31
+ };
32
+
33
+ expectMap(input, map);
34
+ });
35
+
36
+ it('handles top level values of type [object]', () => {
37
+ const input = `
38
+ {
39
+ "a": {
40
+ "az": "str",
41
+ "ay": "str"
42
+ }
43
+ }`;
44
+
45
+ const map = {
46
+ $: ['a'],
47
+ '$.a': ['az', 'ay'],
48
+ };
49
+
50
+ expectMap(input, map);
51
+ });
52
+
53
+ it('handles top level values of a blank [object]', () => {
54
+ const input = `
55
+ {
56
+ "a": { }
57
+ }`;
58
+
59
+ const map = {
60
+ $: ['a'],
61
+ };
62
+
63
+ expectMap(input, map);
64
+ });
65
+
66
+ it('handles multi-character prefix', () => {
67
+ const input = `
68
+ {
69
+ "a": {
70
+ "a2": {
71
+ "b": "str"
72
+ },
73
+ "a1": {
74
+ "d": 2,
75
+ "c": false
76
+ }
77
+ }
78
+ }`;
79
+
80
+ const map = {
81
+ ab: ['a'],
82
+ 'ab.a': ['a2', 'a1'],
83
+ 'ab.a.a2': ['b'],
84
+ 'ab.a.a1': ['d', 'c'],
85
+ };
86
+
87
+ expect(parse(input, 'ab', '.').map).toEqual(map);
88
+ });
89
+
90
+ it('handles multi-character separator', () => {
91
+ const input = `
92
+ {
93
+ "a": {
94
+ "a2": {
95
+ "b": "str"
96
+ },
97
+ "a1": {
98
+ "d": 2,
99
+ "c": false
100
+ }
101
+ }
102
+ }`;
103
+
104
+ const map = {
105
+ $: ['a'],
106
+ '$~|a': ['a2', 'a1'],
107
+ '$~|a~|a2': ['b'],
108
+ '$~|a~|a1': ['d', 'c'],
109
+ };
110
+
111
+ expect(parse(input, '$', '~|').map).toEqual(map);
112
+ });
113
+
114
+ it('handles nesting [object] > [object]', () => {
115
+ const input = `
116
+ {
117
+ "a": {
118
+ "a2": {
119
+ "b": "str"
120
+ },
121
+ "a1": {
122
+ "d": 2,
123
+ "c": false
124
+ }
125
+ }
126
+ }`;
127
+
128
+ const map = {
129
+ $: ['a'],
130
+ '$.a': ['a2', 'a1'],
131
+ '$.a.a2': ['b'],
132
+ '$.a.a1': ['d', 'c'],
133
+ };
134
+
135
+ expectMap(input, map);
136
+ });
137
+
138
+ it('should not return mappings for primitive elements of an [array]', () => {
139
+ const input = `
140
+ {
141
+ "a": [1, "2", "three"]
142
+ }`;
143
+
144
+ const map = {
145
+ $: ['a'],
146
+ };
147
+
148
+ expectMap(input, map);
149
+ });
150
+
151
+ it('handles nesting [array] > [array]', () => {
152
+ const input = `
153
+ {
154
+ "a": [1, "two", [ 3, "four", true]]
155
+ }`;
156
+
157
+ const map = {
158
+ $: ['a'],
159
+ };
160
+
161
+ expectMap(input, map);
162
+ });
163
+
164
+ it('handles nesting [array] > [object]', () => {
165
+ const input = `
166
+ {
167
+ "a": [1, "two", {
168
+ "b": "str"
169
+ }]
170
+ }`;
171
+
172
+ const map = {
173
+ $: ['a'],
174
+ '$.a.2': ['b'],
175
+ };
176
+
177
+ expectMap(input, map);
178
+ });
179
+
180
+ it('handles nesting [array] > [array] > [object]', () => {
181
+ const input = `
182
+ {
183
+ "c": [1, "two", [3, {
184
+ "b": "str"
185
+ }, 4], {
186
+ "a": "str"
187
+ }]
188
+ }`;
189
+
190
+ const map = {
191
+ $: ['c'],
192
+ '$.c.2.1': ['b'],
193
+ '$.c.3': ['a'],
194
+ };
195
+
196
+ expectMap(input, map);
197
+ });
198
+
199
+ it('handles nesting [array] > [array] > [object] > [array] > [object]', () => {
200
+ const input = `
201
+ {
202
+ "d": [
203
+ 1, "two", [
204
+ 3, {
205
+ "c": "str",
206
+ "b": [4, {
207
+ "a": "str"
208
+ }]
209
+ }
210
+ ]
211
+ ]
212
+ }`;
213
+
214
+ const map = {
215
+ $: ['d'],
216
+ '$.d.2.1': ['c', 'b'],
217
+ '$.d.2.1.b.1': ['a'],
218
+ };
219
+
220
+ expectMap(input, map);
221
+ });
222
+
223
+ it('handles keys with no name', () => {
224
+ const input = `
225
+ {
226
+ "": {
227
+ "c": "str",
228
+ "b": "str",
229
+ "a": "str"
230
+ }
231
+ }`;
232
+
233
+ const map = {
234
+ $: [''],
235
+ '$.': ['c', 'b', 'a'],
236
+ };
237
+
238
+ expectMap(input, map);
239
+ });
240
+
241
+ it('escapes slashes as well as the separator when it exists in the object keys', () => {
242
+ // slashes in the encoded JSON are double escapes, so "\\\\" is actually equivalent to "\".
243
+ const input = `
244
+ {
245
+ ".": {
246
+ "a": {"z": "str"},
247
+ "b": {"y": "str"}
248
+ },
249
+ "\\\\.": {
250
+ "b": {"x": "str"},
251
+ "a": {"w": "str"}
252
+ },
253
+ "\\\\": {
254
+ "a": {"v": "str"}
255
+ },
256
+ ".a": {
257
+ "c": {"u": "str"},
258
+ "b": {"t": "str"},
259
+ "a": {"s": "str"}
260
+ }
261
+ }`;
262
+
263
+ // all below slashes are escaped so "\\" is actually equivalent to "\".
264
+ const map = {
265
+ $: ['.', '\\.', '\\', '.a'],
266
+ '$.\\.': ['a', 'b'],
267
+ '$.\\..a': ['z'],
268
+ '$.\\..b': ['y'],
269
+ '$.\\\\\\.': ['b', 'a'],
270
+ '$.\\\\\\..b': ['x'],
271
+ '$.\\\\\\..a': ['w'],
272
+ '$.\\\\': ['a'],
273
+ '$.\\\\.a': ['v'],
274
+ '$.\\.a': ['c', 'b', 'a'],
275
+ '$.\\.a.c': ['u'],
276
+ '$.\\.a.b': ['t'],
277
+ '$.\\.a.a': ['s'],
278
+ };
279
+
280
+ expectMap(input, map);
281
+ });
282
+
283
+ it('handles keys with different types of values', () => {
284
+ const input = `
285
+ {
286
+ "a": "a",
287
+ "b": 2,
288
+ "c": 2.3,
289
+ "d": true,
290
+ "e": false,
291
+ "f": null,
292
+ "g": {},
293
+ "h": []
294
+ }`;
295
+
296
+ const map = {
297
+ $: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
298
+ };
299
+
300
+ expectMap(input, map);
301
+ });
302
+ });
@@ -0,0 +1,59 @@
1
+ import { escapeKey } from './key';
2
+ import { OrderedParseResult, PropertyMap } from './models';
3
+
4
+ const traverseObject = <T extends Record<string, any>>(
5
+ obj: T,
6
+ map: PropertyMap,
7
+ parentKey: string,
8
+ separator: string,
9
+ ) => {
10
+ const childKeys = Object.keys(obj);
11
+
12
+ if (childKeys.length === 0) {
13
+ return;
14
+ }
15
+
16
+ // Ignore storing keys for arrays
17
+ if (!Array.isArray(obj)) {
18
+ map[`${parentKey}`] = childKeys;
19
+ }
20
+
21
+ childKeys.forEach((childKey) => {
22
+ const value = obj[childKey];
23
+
24
+ if (value !== null && typeof value === 'object') {
25
+ traverseObject(value, map, `${parentKey}${separator}${escapeKey(childKey, separator)}`, separator);
26
+ }
27
+ });
28
+ };
29
+
30
+ /**
31
+ * Parse a JSON string and generate a map
32
+ *
33
+ * @param jsonString a json string
34
+ * @param prefix a non-empty `string` that controls what the key prefix value is in the generated map. Defaults to `$`.
35
+ * @param separator a non-empty `string` that controls what the key separator is in the generated map. Defaults to `~`.
36
+ * @returns an object containing the parsed `object: T` and the `map: PropertyMap`
37
+ */
38
+ const parse = <T extends object>(jsonString: string, prefix = '$', separator = '~'): OrderedParseResult<T> => {
39
+ if (prefix.length < 1) {
40
+ throw new Error('Prefix should not be an empty string.');
41
+ }
42
+
43
+ if (separator.length < 1) {
44
+ throw new Error('Separator should not be an empty string.');
45
+ } else if (separator === '\\') {
46
+ throw new Error('Separator cannot be "\\".');
47
+ }
48
+
49
+ const obj: T = JSON.parse(jsonString);
50
+
51
+ const map = {};
52
+ traverseObject(obj, map, escapeKey(prefix, separator), separator);
53
+ return {
54
+ object: obj,
55
+ map,
56
+ };
57
+ };
58
+
59
+ export default parse;