@leather.io/utils
Advanced tools
| import { flattenObject } from './flatten-object'; | ||
| describe(flattenObject.name, () => { | ||
| describe('Object flattening', () => { | ||
| test('flattens simple nested objects', () => { | ||
| const input = { layer1: { layer2: 'value' } }; | ||
| const expected = { 'layer1.layer2': 'value' }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('flattens deeply nested objects', () => { | ||
| const input = { | ||
| level1: { | ||
| level2: { | ||
| level3: { | ||
| level4: 'deep value', | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| const expected = { 'level1.level2.level3.level4': 'deep value' }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('flattens objects with multiple properties', () => { | ||
| const input = { | ||
| user: { | ||
| name: 'John', | ||
| age: 30, | ||
| address: { | ||
| street: '123 Main St', | ||
| city: 'Anytown', | ||
| }, | ||
| }, | ||
| status: 'active', | ||
| }; | ||
| const expected = { | ||
| 'user.name': 'John', | ||
| 'user.age': 30, | ||
| 'user.address.street': '123 Main St', | ||
| 'user.address.city': 'Anytown', | ||
| status: 'active', | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles empty objects', () => { | ||
| expect(flattenObject({})).toEqual({}); | ||
| }); | ||
| }); | ||
| describe('Array flattening', () => { | ||
| test('flattens simple arrays with objects', () => { | ||
| const input = [{ key: 'value' }]; | ||
| const expected = { '[0].key': 'value' }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('flattens arrays with multiple objects', () => { | ||
| const input = [ | ||
| { name: 'John', age: 30 }, | ||
| { name: 'Jane', age: 25 }, | ||
| ]; | ||
| const expected = { | ||
| '[0].name': 'John', | ||
| '[0].age': 30, | ||
| '[1].name': 'Jane', | ||
| '[1].age': 25, | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('flattens arrays with primitive values', () => { | ||
| const input = ['first', 'second', 'third']; | ||
| const expected = { | ||
| '[0]': 'first', | ||
| '[1]': 'second', | ||
| '[2]': 'third', | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('flattens nested arrays', () => { | ||
| const input = [ | ||
| [1, 2], | ||
| [3, 4], | ||
| ]; | ||
| const expected = { | ||
| '[0][0]': 1, | ||
| '[0][1]': 2, | ||
| '[1][0]': 3, | ||
| '[1][1]': 4, | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles empty arrays', () => { | ||
| expect(flattenObject([])).toEqual({}); | ||
| }); | ||
| }); | ||
| describe('Mixed object and array flattening', () => { | ||
| test('flattens objects containing arrays', () => { | ||
| const input = { | ||
| users: [ | ||
| { name: 'John', age: 30 }, | ||
| { name: 'Jane', age: 25 }, | ||
| ], | ||
| meta: { | ||
| count: 2, | ||
| }, | ||
| }; | ||
| const expected = { | ||
| 'users[0].name': 'John', | ||
| 'users[0].age': 30, | ||
| 'users[1].name': 'Jane', | ||
| 'users[1].age': 25, | ||
| 'meta.count': 2, | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('flattens arrays containing objects with nested arrays', () => { | ||
| const input = [ | ||
| { | ||
| id: 1, | ||
| tags: ['tag1', 'tag2'], | ||
| }, | ||
| { | ||
| id: 2, | ||
| tags: ['tag3'], | ||
| }, | ||
| ]; | ||
| const expected = { | ||
| '[0].id': 1, | ||
| '[0].tags[0]': 'tag1', | ||
| '[0].tags[1]': 'tag2', | ||
| '[1].id': 2, | ||
| '[1].tags[0]': 'tag3', | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles complex nested structures', () => { | ||
| const input = { | ||
| data: { | ||
| items: [ | ||
| { | ||
| name: 'item1', | ||
| properties: { | ||
| color: 'red', | ||
| sizes: ['small', 'medium'], | ||
| }, | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
| const expected = { | ||
| 'data.items[0].name': 'item1', | ||
| 'data.items[0].properties.color': 'red', | ||
| 'data.items[0].properties.sizes[0]': 'small', | ||
| 'data.items[0].properties.sizes[1]': 'medium', | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| }); | ||
| describe('Primitive value handling', () => { | ||
| test('handles null values', () => { | ||
| const input = { key: null }; | ||
| const expected = { key: null }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles boolean values', () => { | ||
| const input = { active: true, disabled: false }; | ||
| const expected = { active: true, disabled: false }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles number values', () => { | ||
| const input = { | ||
| integer: 42, | ||
| float: 3.14, | ||
| zero: 0, | ||
| negative: -10, | ||
| }; | ||
| const expected = { | ||
| integer: 42, | ||
| float: 3.14, | ||
| zero: 0, | ||
| negative: -10, | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles string values', () => { | ||
| const input = { | ||
| text: 'hello world', | ||
| empty: '', | ||
| unicode: '🚀', | ||
| }; | ||
| const expected = { | ||
| text: 'hello world', | ||
| empty: '', | ||
| unicode: '🚀', | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles root primitive values', () => { | ||
| expect(flattenObject('hello')).toEqual({ value: 'hello' }); | ||
| expect(flattenObject(42)).toEqual({ value: 42 }); | ||
| expect(flattenObject(true)).toEqual({ value: true }); | ||
| expect(flattenObject(null)).toEqual({ value: null }); | ||
| }); | ||
| }); | ||
| describe('Edge cases', () => { | ||
| test('handles objects with array-like property names', () => { | ||
| const input = { | ||
| '0': 'first', | ||
| '1': 'second', | ||
| length: 2, | ||
| }; | ||
| const expected = { | ||
| '0': 'first', | ||
| '1': 'second', | ||
| length: 2, | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles keys with special characters', () => { | ||
| const input = { | ||
| 'key-with-dash': 'value1', | ||
| key_with_underscore: 'value2', | ||
| 'key with space': 'value3', | ||
| 'key.with.dot': 'value4', | ||
| }; | ||
| const expected = { | ||
| 'key-with-dash': 'value1', | ||
| key_with_underscore: 'value2', | ||
| 'key with space': 'value3', | ||
| 'key.with.dot': 'value4', | ||
| }; | ||
| expect(flattenObject(input)).toEqual(expected); | ||
| }); | ||
| test('handles sparse arrays', () => { | ||
| const input: any[] = []; | ||
| input[0] = 'first'; | ||
| input[2] = 'third'; | ||
| const result = flattenObject(input); | ||
| expect(result).toEqual({ | ||
| '[0]': 'first', | ||
| '[1]': undefined, | ||
| '[2]': 'third', | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
| type ObjectValue = string | number | boolean | null | NestedObject | ObjectValue[]; | ||
| interface NestedObject { | ||
| [key: string]: ObjectValue; | ||
| } | ||
| interface FlattenedObject { | ||
| [key: string]: string | number | boolean | null; | ||
| } | ||
| /** | ||
| * Flattens a nested object or array into a flat object with dot-notation key paths. | ||
| * | ||
| * @param input - The object or array to flatten | ||
| * @param prefix - Internal parameter for recursion, represents the current key path | ||
| * @returns A flat object where keys represent the path to the original nested value | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Object example | ||
| * flattenObject({ layer1: { layer2: 'value' } }) | ||
| * // Returns: { 'layer1.layer2': 'value' } | ||
| * | ||
| * // Array example | ||
| * flattenObject([{ key: 'value' }]) | ||
| * // Returns: { '[0].key': 'value' } | ||
| * | ||
| * // Mixed example | ||
| * flattenObject({ users: [{ name: 'John', age: 30 }] }) | ||
| * // Returns: { 'users[0].name': 'John', 'users[0].age': 30 } | ||
| * ``` | ||
| */ | ||
| export function flattenObject(input: ObjectValue, prefix = ''): FlattenedObject { | ||
| const result: FlattenedObject = {}; | ||
| if (input === null || typeof input !== 'object') { | ||
| if (prefix === '') return { value: input }; | ||
| return { [prefix]: input }; | ||
| } | ||
| if (Array.isArray(input)) { | ||
| input.forEach((item, index) => { | ||
| const key = prefix === '' ? `[${index}]` : `${prefix}[${index}]`; | ||
| const flattened = flattenObject(item, key); | ||
| Object.assign(result, flattened); | ||
| }); | ||
| } else { | ||
| Object.entries(input).forEach(([key, value]) => { | ||
| const newKey = prefix === '' ? key : `${prefix}.${key}`; | ||
| const flattened = flattenObject(value, newKey); | ||
| Object.assign(result, flattened); | ||
| }); | ||
| } | ||
| return result; | ||
| } |
| > @leather.io/utils@0.43.0 build /home/runner/work/mono/mono/packages/utils | ||
| > @leather.io/utils@0.44.0 build /home/runner/work/mono/mono/packages/utils | ||
| > tsup | ||
@@ -11,7 +11,7 @@ | ||
| [34mESM[39m Build start | ||
| [32mESM[39m [1mdist/index.js [22m[32m71.76 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m135.06 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 33ms | ||
| [32mESM[39m [1mdist/index.js [22m[32m72.48 KB[39m | ||
| [32mESM[39m [1mdist/index.js.map [22m[32m137.40 KB[39m | ||
| [32mESM[39m ⚡️ Build success in 32ms | ||
| [34mDTS[39m Build start | ||
| [32mDTS[39m ⚡️ Build success in 1800ms | ||
| [32mDTS[39m [1mdist/index.d.ts [22m[32m13.38 KB[39m | ||
| [32mDTS[39m ⚡️ Build success in 1841ms | ||
| [32mDTS[39m [1mdist/index.d.ts [22m[32m14.39 KB[39m |
+7
-0
@@ -335,2 +335,9 @@ # Changelog | ||
| ## [0.44.0](https://github.com/leather-io/mono/compare/@leather.io/utils-v0.43.0...@leather.io/utils-v0.44.0) (2025-08-27) | ||
| ### Features | ||
| * flatten object util ([cdebfec](https://github.com/leather-io/mono/commit/cdebfec46d68cb1ac9d12b844b9b4c7e976237d4)) | ||
| ## [0.43.0](https://github.com/leather-io/mono/compare/@leather.io/utils-v0.42.4...@leather.io/utils-v0.43.0) (2025-08-26) | ||
@@ -337,0 +344,0 @@ |
+32
-1
@@ -173,2 +173,33 @@ import BigNumber, { BigNumber as BigNumber$1 } from 'bignumber.js'; | ||
| type ObjectValue = string | number | boolean | null | NestedObject | ObjectValue[]; | ||
| interface NestedObject { | ||
| [key: string]: ObjectValue; | ||
| } | ||
| interface FlattenedObject { | ||
| [key: string]: string | number | boolean | null; | ||
| } | ||
| /** | ||
| * Flattens a nested object or array into a flat object with dot-notation key paths. | ||
| * | ||
| * @param input - The object or array to flatten | ||
| * @param prefix - Internal parameter for recursion, represents the current key path | ||
| * @returns A flat object where keys represent the path to the original nested value | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * // Object example | ||
| * flattenObject({ layer1: { layer2: 'value' } }) | ||
| * // Returns: { 'layer1.layer2': 'value' } | ||
| * | ||
| * // Array example | ||
| * flattenObject([{ key: 'value' }]) | ||
| * // Returns: { '[0].key': 'value' } | ||
| * | ||
| * // Mixed example | ||
| * flattenObject({ users: [{ name: 'John', age: 30 }] }) | ||
| * // Returns: { 'users[0].name': 'John', 'users[0].age': 30 } | ||
| * ``` | ||
| */ | ||
| declare function flattenObject(input: ObjectValue, prefix?: string): FlattenedObject; | ||
| interface SpamFilterArgs { | ||
@@ -253,2 +284,2 @@ input: string; | ||
| export { type CreateInscriptionData, type FormatAmountOptions, aggregateBaseCryptoAssetBalances, aggregateBtcBalances, aggregateStxBalances, assertExistence, assertIsTruthy, assertUnreachable, baseCurrencyAmountInQuote, baseCurrencyAmountInQuoteWithFallback, btcToSat, calculateMeanAverage, capitalize, convertAmountToBaseUnit, convertAmountToFractionalUnit, convertToMoneyTypeWithDefaultOfZero, countDecimals, createAccountAddresses, createBaseCryptoAssetBalance, createBtcBalance, createCounter, createCurrencyFormatter, createInscriptionAsset, createMoney, createMoneyFromDecimal, createNullArrayOfLength, createNumArrayOfRange, createStxBalance, dateToUnixTimestamp, daysInMs, daysInSec, defaultWalletKeyId, delay, ensureArray, extractPhraseFromString, fibonacciGenerator, fiveMinInMs, getAssetDisplayName, getAssetId, getTicker, hasBitcoinAddress, hasStacksAddress, hexToNumber, hoursInMs, hoursInSec, increaseValueByOneMicroStx, initBigNumber, invertExchangeRate, isBigInt, isBoolean, isDefined, isEmpty, isEmptyArray, isEmptyString, isError, isEven, isFiatCurrencyCode, isFulfilled, isFunction, isHexString, isMoney, isMoneyGreaterThanZero, isNumber, isNumberOrNumberList, isObject, isRejected, isString, isTypedArray, isUndefined, isValidPrecision, makeNumberRange, makeStacksTxExplorerLink, mapObject, match, matchesAssetId, maxMoney, microStxToStx, migratePositiveAssetBalancesToTop, minMoney, minutesInMs, minutesInSec, moneyToBaseUnit, noop, oneDayInMs, oneMinInMs, oneWeekInMs, propIfDefined, pxStringToNumber, quoteCurrencyAmountToBase, rebaseMarketData, removeTrailingNullCharacters, reverseBytes, safelyFormatHexTxid, sanitizeContent, satToBtc, scaleValue, secondsInMs, sortAssetsByName, spamFilter, stxToMicroStx, subtractMoney, sumMoney, sumNumbers, toHexString, truncateMiddle, undefinedIfLengthZero, uniqueArray, unitToFractionalUnit, weeksInMs, weeksInSec, whenInscriptionMimeType, whenNetwork }; | ||
| export { type CreateInscriptionData, type FormatAmountOptions, aggregateBaseCryptoAssetBalances, aggregateBtcBalances, aggregateStxBalances, assertExistence, assertIsTruthy, assertUnreachable, baseCurrencyAmountInQuote, baseCurrencyAmountInQuoteWithFallback, btcToSat, calculateMeanAverage, capitalize, convertAmountToBaseUnit, convertAmountToFractionalUnit, convertToMoneyTypeWithDefaultOfZero, countDecimals, createAccountAddresses, createBaseCryptoAssetBalance, createBtcBalance, createCounter, createCurrencyFormatter, createInscriptionAsset, createMoney, createMoneyFromDecimal, createNullArrayOfLength, createNumArrayOfRange, createStxBalance, dateToUnixTimestamp, daysInMs, daysInSec, defaultWalletKeyId, delay, ensureArray, extractPhraseFromString, fibonacciGenerator, fiveMinInMs, flattenObject, getAssetDisplayName, getAssetId, getTicker, hasBitcoinAddress, hasStacksAddress, hexToNumber, hoursInMs, hoursInSec, increaseValueByOneMicroStx, initBigNumber, invertExchangeRate, isBigInt, isBoolean, isDefined, isEmpty, isEmptyArray, isEmptyString, isError, isEven, isFiatCurrencyCode, isFulfilled, isFunction, isHexString, isMoney, isMoneyGreaterThanZero, isNumber, isNumberOrNumberList, isObject, isRejected, isString, isTypedArray, isUndefined, isValidPrecision, makeNumberRange, makeStacksTxExplorerLink, mapObject, match, matchesAssetId, maxMoney, microStxToStx, migratePositiveAssetBalancesToTop, minMoney, minutesInMs, minutesInSec, moneyToBaseUnit, noop, oneDayInMs, oneMinInMs, oneWeekInMs, propIfDefined, pxStringToNumber, quoteCurrencyAmountToBase, rebaseMarketData, removeTrailingNullCharacters, reverseBytes, safelyFormatHexTxid, sanitizeContent, satToBtc, scaleValue, secondsInMs, sortAssetsByName, spamFilter, stxToMicroStx, subtractMoney, sumMoney, sumNumbers, toHexString, truncateMiddle, undefinedIfLengthZero, uniqueArray, unitToFractionalUnit, weeksInMs, weeksInSec, whenInscriptionMimeType, whenNetwork }; |
+3
-3
@@ -5,3 +5,3 @@ { | ||
| "description": "Shared bitcoin utilities", | ||
| "version": "0.43.0", | ||
| "version": "0.44.0", | ||
| "license": "MIT", | ||
@@ -32,4 +32,4 @@ "homepage": "https://github.com/leather-io/mono/tree/dev/packages/utils", | ||
| "vitest": "2.1.9", | ||
| "@leather.io/tsconfig-config": "0.11.0", | ||
| "@leather.io/prettier-config": "0.8.1" | ||
| "@leather.io/prettier-config": "0.8.1", | ||
| "@leather.io/tsconfig-config": "0.11.0" | ||
| }, | ||
@@ -36,0 +36,0 @@ "keywords": [ |
+1
-0
@@ -19,2 +19,3 @@ import { BigNumber } from 'bignumber.js'; | ||
| export * from './currency-formatter/currency-formatter'; | ||
| export * from './flatten-object'; | ||
@@ -21,0 +22,0 @@ export { spamFilter } from './spam-filter/spam-filter'; |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
413499
3.23%61
3.39%9147
3.93%