@leather.io/utils 0.43.0 → 0.44.1

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@leather.io/utils",
3
3
  "author": "Leather.io contact@leather.io",
4
4
  "description": "Shared bitcoin utilities",
5
- "version": "0.43.0",
5
+ "version": "0.44.1",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/leather-io/mono/tree/dev/packages/utils",
8
8
  "repository": {
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "bignumber.js": "9.1.2",
21
21
  "dompurify": "3.2.4",
22
- "@leather.io/models": "0.40.0",
23
- "@leather.io/constants": "0.25.2"
22
+ "@leather.io/constants": "0.25.3",
23
+ "@leather.io/models": "0.41.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@types/dompurify": "3.0.5",
@@ -28,8 +28,8 @@
28
28
  "tsup": "8.4.0",
29
29
  "typescript": "5.8.3",
30
30
  "vitest": "2.1.9",
31
- "@leather.io/tsconfig-config": "0.11.0",
32
- "@leather.io/prettier-config": "0.8.1"
31
+ "@leather.io/prettier-config": "0.8.1",
32
+ "@leather.io/tsconfig-config": "0.11.0"
33
33
  },
34
34
  "keywords": [
35
35
  "leather",
@@ -0,0 +1,263 @@
1
+ import { flattenObject } from './flatten-object';
2
+
3
+ describe(flattenObject.name, () => {
4
+ describe('Object flattening', () => {
5
+ test('flattens simple nested objects', () => {
6
+ const input = { layer1: { layer2: 'value' } };
7
+ const expected = { 'layer1.layer2': 'value' };
8
+ expect(flattenObject(input)).toEqual(expected);
9
+ });
10
+
11
+ test('flattens deeply nested objects', () => {
12
+ const input = {
13
+ level1: {
14
+ level2: {
15
+ level3: {
16
+ level4: 'deep value',
17
+ },
18
+ },
19
+ },
20
+ };
21
+ const expected = { 'level1.level2.level3.level4': 'deep value' };
22
+ expect(flattenObject(input)).toEqual(expected);
23
+ });
24
+
25
+ test('flattens objects with multiple properties', () => {
26
+ const input = {
27
+ user: {
28
+ name: 'John',
29
+ age: 30,
30
+ address: {
31
+ street: '123 Main St',
32
+ city: 'Anytown',
33
+ },
34
+ },
35
+ status: 'active',
36
+ };
37
+ const expected = {
38
+ 'user.name': 'John',
39
+ 'user.age': 30,
40
+ 'user.address.street': '123 Main St',
41
+ 'user.address.city': 'Anytown',
42
+ status: 'active',
43
+ };
44
+ expect(flattenObject(input)).toEqual(expected);
45
+ });
46
+
47
+ test('handles empty objects', () => {
48
+ expect(flattenObject({})).toEqual({});
49
+ });
50
+ });
51
+
52
+ describe('Array flattening', () => {
53
+ test('flattens simple arrays with objects', () => {
54
+ const input = [{ key: 'value' }];
55
+ const expected = { '[0].key': 'value' };
56
+ expect(flattenObject(input)).toEqual(expected);
57
+ });
58
+
59
+ test('flattens arrays with multiple objects', () => {
60
+ const input = [
61
+ { name: 'John', age: 30 },
62
+ { name: 'Jane', age: 25 },
63
+ ];
64
+ const expected = {
65
+ '[0].name': 'John',
66
+ '[0].age': 30,
67
+ '[1].name': 'Jane',
68
+ '[1].age': 25,
69
+ };
70
+ expect(flattenObject(input)).toEqual(expected);
71
+ });
72
+
73
+ test('flattens arrays with primitive values', () => {
74
+ const input = ['first', 'second', 'third'];
75
+ const expected = {
76
+ '[0]': 'first',
77
+ '[1]': 'second',
78
+ '[2]': 'third',
79
+ };
80
+ expect(flattenObject(input)).toEqual(expected);
81
+ });
82
+
83
+ test('flattens nested arrays', () => {
84
+ const input = [
85
+ [1, 2],
86
+ [3, 4],
87
+ ];
88
+ const expected = {
89
+ '[0][0]': 1,
90
+ '[0][1]': 2,
91
+ '[1][0]': 3,
92
+ '[1][1]': 4,
93
+ };
94
+ expect(flattenObject(input)).toEqual(expected);
95
+ });
96
+
97
+ test('handles empty arrays', () => {
98
+ expect(flattenObject([])).toEqual({});
99
+ });
100
+ });
101
+
102
+ describe('Mixed object and array flattening', () => {
103
+ test('flattens objects containing arrays', () => {
104
+ const input = {
105
+ users: [
106
+ { name: 'John', age: 30 },
107
+ { name: 'Jane', age: 25 },
108
+ ],
109
+ meta: {
110
+ count: 2,
111
+ },
112
+ };
113
+ const expected = {
114
+ 'users[0].name': 'John',
115
+ 'users[0].age': 30,
116
+ 'users[1].name': 'Jane',
117
+ 'users[1].age': 25,
118
+ 'meta.count': 2,
119
+ };
120
+ expect(flattenObject(input)).toEqual(expected);
121
+ });
122
+
123
+ test('flattens arrays containing objects with nested arrays', () => {
124
+ const input = [
125
+ {
126
+ id: 1,
127
+ tags: ['tag1', 'tag2'],
128
+ },
129
+ {
130
+ id: 2,
131
+ tags: ['tag3'],
132
+ },
133
+ ];
134
+ const expected = {
135
+ '[0].id': 1,
136
+ '[0].tags[0]': 'tag1',
137
+ '[0].tags[1]': 'tag2',
138
+ '[1].id': 2,
139
+ '[1].tags[0]': 'tag3',
140
+ };
141
+ expect(flattenObject(input)).toEqual(expected);
142
+ });
143
+
144
+ test('handles complex nested structures', () => {
145
+ const input = {
146
+ data: {
147
+ items: [
148
+ {
149
+ name: 'item1',
150
+ properties: {
151
+ color: 'red',
152
+ sizes: ['small', 'medium'],
153
+ },
154
+ },
155
+ ],
156
+ },
157
+ };
158
+ const expected = {
159
+ 'data.items[0].name': 'item1',
160
+ 'data.items[0].properties.color': 'red',
161
+ 'data.items[0].properties.sizes[0]': 'small',
162
+ 'data.items[0].properties.sizes[1]': 'medium',
163
+ };
164
+ expect(flattenObject(input)).toEqual(expected);
165
+ });
166
+ });
167
+
168
+ describe('Primitive value handling', () => {
169
+ test('handles null values', () => {
170
+ const input = { key: null };
171
+ const expected = { key: null };
172
+ expect(flattenObject(input)).toEqual(expected);
173
+ });
174
+
175
+ test('handles boolean values', () => {
176
+ const input = { active: true, disabled: false };
177
+ const expected = { active: true, disabled: false };
178
+ expect(flattenObject(input)).toEqual(expected);
179
+ });
180
+
181
+ test('handles number values', () => {
182
+ const input = {
183
+ integer: 42,
184
+ float: 3.14,
185
+ zero: 0,
186
+ negative: -10,
187
+ };
188
+ const expected = {
189
+ integer: 42,
190
+ float: 3.14,
191
+ zero: 0,
192
+ negative: -10,
193
+ };
194
+ expect(flattenObject(input)).toEqual(expected);
195
+ });
196
+
197
+ test('handles string values', () => {
198
+ const input = {
199
+ text: 'hello world',
200
+ empty: '',
201
+ unicode: '🚀',
202
+ };
203
+ const expected = {
204
+ text: 'hello world',
205
+ empty: '',
206
+ unicode: '🚀',
207
+ };
208
+ expect(flattenObject(input)).toEqual(expected);
209
+ });
210
+
211
+ test('handles root primitive values', () => {
212
+ expect(flattenObject('hello')).toEqual({ value: 'hello' });
213
+ expect(flattenObject(42)).toEqual({ value: 42 });
214
+ expect(flattenObject(true)).toEqual({ value: true });
215
+ expect(flattenObject(null)).toEqual({ value: null });
216
+ });
217
+ });
218
+
219
+ describe('Edge cases', () => {
220
+ test('handles objects with array-like property names', () => {
221
+ const input = {
222
+ '0': 'first',
223
+ '1': 'second',
224
+ length: 2,
225
+ };
226
+ const expected = {
227
+ '0': 'first',
228
+ '1': 'second',
229
+ length: 2,
230
+ };
231
+ expect(flattenObject(input)).toEqual(expected);
232
+ });
233
+
234
+ test('handles keys with special characters', () => {
235
+ const input = {
236
+ 'key-with-dash': 'value1',
237
+ key_with_underscore: 'value2',
238
+ 'key with space': 'value3',
239
+ 'key.with.dot': 'value4',
240
+ };
241
+ const expected = {
242
+ 'key-with-dash': 'value1',
243
+ key_with_underscore: 'value2',
244
+ 'key with space': 'value3',
245
+ 'key.with.dot': 'value4',
246
+ };
247
+ expect(flattenObject(input)).toEqual(expected);
248
+ });
249
+
250
+ test('handles sparse arrays', () => {
251
+ const input: any[] = [];
252
+ input[0] = 'first';
253
+ input[2] = 'third';
254
+
255
+ const result = flattenObject(input);
256
+ expect(result).toEqual({
257
+ '[0]': 'first',
258
+ '[1]': undefined,
259
+ '[2]': 'third',
260
+ });
261
+ });
262
+ });
263
+ });
@@ -0,0 +1,55 @@
1
+ type ObjectValue = string | number | boolean | null | NestedObject | ObjectValue[];
2
+ interface NestedObject {
3
+ [key: string]: ObjectValue;
4
+ }
5
+
6
+ interface FlattenedObject {
7
+ [key: string]: string | number | boolean | null;
8
+ }
9
+
10
+ /**
11
+ * Flattens a nested object or array into a flat object with dot-notation key paths.
12
+ *
13
+ * @param input - The object or array to flatten
14
+ * @param prefix - Internal parameter for recursion, represents the current key path
15
+ * @returns A flat object where keys represent the path to the original nested value
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * // Object example
20
+ * flattenObject({ layer1: { layer2: 'value' } })
21
+ * // Returns: { 'layer1.layer2': 'value' }
22
+ *
23
+ * // Array example
24
+ * flattenObject([{ key: 'value' }])
25
+ * // Returns: { '[0].key': 'value' }
26
+ *
27
+ * // Mixed example
28
+ * flattenObject({ users: [{ name: 'John', age: 30 }] })
29
+ * // Returns: { 'users[0].name': 'John', 'users[0].age': 30 }
30
+ * ```
31
+ */
32
+ export function flattenObject(input: ObjectValue, prefix = ''): FlattenedObject {
33
+ const result: FlattenedObject = {};
34
+
35
+ if (input === null || typeof input !== 'object') {
36
+ if (prefix === '') return { value: input };
37
+ return { [prefix]: input };
38
+ }
39
+
40
+ if (Array.isArray(input)) {
41
+ input.forEach((item, index) => {
42
+ const key = prefix === '' ? `[${index}]` : `${prefix}[${index}]`;
43
+ const flattened = flattenObject(item, key);
44
+ Object.assign(result, flattened);
45
+ });
46
+ } else {
47
+ Object.entries(input).forEach(([key, value]) => {
48
+ const newKey = prefix === '' ? key : `${prefix}.${key}`;
49
+ const flattened = flattenObject(value, newKey);
50
+ Object.assign(result, flattened);
51
+ });
52
+ }
53
+
54
+ return result;
55
+ }
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export * from './truncate-middle';
16
16
  export * from './time';
17
17
  export * from './market-data';
18
18
  export * from './currency-formatter/currency-formatter';
19
+ export * from './flatten-object';
19
20
 
20
21
  export { spamFilter } from './spam-filter/spam-filter';
21
22
  export { extractPhraseFromString } from './extract-phrase-from-string/extract-phrase-from-string';