diff --git a/CHANGELOG b/CHANGELOG index fa397a1..54f7c3b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +#1.20.0 +- feat: add transformKeysToSnake + #1.19.1 - fix: fix isPlainObject to check that the constructor is an object diff --git a/README.md b/README.md index 491b941..7e47d2a 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ Currently supported utils: - `snakeCase` - convert camel case and string/dash separated strings to snake case - `sum` - calculate sum of array items - `sumBy` - calculate sum of array items using iteratee function or string shortcut +- `transformKeysToSnake` - transform every object key to snake case with option to be recursive - `union` - Returns the union of the given arrays - `uniqBy` - get unique values of array by the iteratee function or property path - `uniqWith` - Returns a new array with unique values, using a comparator function diff --git a/index.d.ts b/index.d.ts index adb3c52..3a3c119 100644 --- a/index.d.ts +++ b/index.d.ts @@ -40,6 +40,7 @@ export function shuffle (array: Array): Array export function snakeCase (str: string): string export function sum (values: Array): number export function sumBy (values: Array, iteratee: Function | string): number +export function transformKeysToSnake (obj: Object, opts?: {recursive?: boolean}) : Object export function union (...arrays: Array[]): Array export function uniqBy (array: Array, iteratee: Function | string): Array export function uniqWith (array: Array, comparator: Function): Array diff --git a/index.js b/index.js index 2b00365..db46d74 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,7 @@ const shuffle = require('./src/shuffle') const snakeCase = require('./src/snakeCase') const sum = require('./src/sum') const sumBy = require('./src/sumBy') +const transformKeysToSnake = require('./src/transformKeysToSnake') const union = require('./src/union') const uniqBy = require('./src/uniqBy') const uniqWith = require('./src/uniqWith') @@ -93,6 +94,7 @@ module.exports = { snakeCase, sum, sumBy, + transformKeysToSnake, union, uniqBy, uniqWith, diff --git a/package.json b/package.json index c1594d8..744515b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitfinexcom/lib-js-util-base", - "version": "1.19.1", + "version": "1.20.0", "description": "general utils", "main": "index.js", "scripts": { diff --git a/src/transformKeysToSnake.js b/src/transformKeysToSnake.js new file mode 100644 index 0000000..d125d8d --- /dev/null +++ b/src/transformKeysToSnake.js @@ -0,0 +1,31 @@ +'use strict' + +const isPlainObject = require('./isPlainObject') +const snakeCase = require('./snakeCase') + +/** + * Transform object keys to snake case + * e.g. { fooBar: 1 } => { foo_bar: 1 } + * @param {Object} obj + * @param {Object} [opts] + * @param {boolean} [opts.recursive] + * @returns {Object} + */ +const transformKeysToSnake = (obj, opts) => { + if (Array.isArray(obj)) { + return obj.map(item => transformKeysToSnake(item, opts)) + } + + if (!isPlainObject(obj)) { + return obj + } + + return Object.fromEntries( + Object.entries(obj).map(([k, v]) => [ + snakeCase(k), + opts?.recursive ? transformKeysToSnake(v, opts) : v + ]) + ) +} + +module.exports = transformKeysToSnake diff --git a/test/transformKeystoSnake.js b/test/transformKeystoSnake.js new file mode 100644 index 0000000..fd29304 --- /dev/null +++ b/test/transformKeystoSnake.js @@ -0,0 +1,102 @@ +'use strict' + +/* eslint-env mocha */ + +const assert = require('assert') +const { transformKeysToSnake } = require('../index') +const { itEach } = require('mocha-it-each') + +describe('transformKeysToSnake', () => { + describe('non recursive (default)', () => { + itEach('should return input when it is not an object', [new Date(), null, false], (input) => { + assert.equal(transformKeysToSnake(input), input) + }) + + it('should return input with snake case props recursively', () => { + const input = { + camelProp: 'true', + nestedProp: { + first_name: 'john', + lastName: 'doe', + tags: ['keepCamel'] + }, + someObjs: [ + { someProp: 'value' }, + { foo_bar: 'value', foo: 'value' } + ] + } + const output = { + camel_prop: 'true', + nested_prop: { + first_name: 'john', + lastName: 'doe', + tags: ['keepCamel'] + }, + some_objs: [ + { someProp: 'value' }, + { foo_bar: 'value', foo: 'value' } + ] + } + assert.deepEqual(transformKeysToSnake(input), output) + }) + + it('should work with array mix of object and nested arrays', () => { + const input = { + mixedArray: [123, { someKey: 'value' }, [{ nestedArrayItem: 'test' }]] + } + const output = { + mixed_array: [123, { someKey: 'value' }, [{ nestedArrayItem: 'test' }]] + } + assert.deepEqual(transformKeysToSnake(input), output) + }) + }) + describe('recursive', () => { + itEach('should return input when it is not an object', [new Date(), null, false], (input) => { + assert.equal(transformKeysToSnake(input, { recursive: true }), input) + }) + + it('should return input with snake case props recursively', () => { + const input = { + camelProp: 'true', + nestedProp: { + first_name: 'john', + lastName: 'doe', + level2: { + fooBar: 'x' + }, + tags: ['keepCamel'] + }, + someObjs: [ + { someProp: 'value' }, + { foo_bar: 'value', foo: 'value' } + ] + } + const output = { + camel_prop: 'true', + nested_prop: { + first_name: 'john', + last_name: 'doe', + level2: { + foo_bar: 'x' + }, + tags: ['keepCamel'] + }, + some_objs: [ + { some_prop: 'value' }, + { foo_bar: 'value', foo: 'value' } + ] + } + assert.deepEqual(transformKeysToSnake(input, { recursive: true }), output) + }) + + it('should work with array mix of object and nested arrays', () => { + const input = { + mixedArray: [123, { someKey: 'value' }, [{ nestedArrayItem: 'test' }]] + } + const output = { + mixed_array: [123, { some_key: 'value' }, [{ nested_array_item: 'test' }]] + } + assert.deepEqual(transformKeysToSnake(input, { recursive: true }), output) + }) + }) +})