| module.exports = flatten |
| flatten.flatten = flatten |
| flatten.unflatten = unflatten |
| |
| function isBuffer (obj) { |
| return obj && |
| obj.constructor && |
| (typeof obj.constructor.isBuffer === 'function') && |
| obj.constructor.isBuffer(obj) |
| } |
| |
| function keyIdentity (key) { |
| return key |
| } |
| |
| function flatten (target, opts) { |
| opts = opts || {} |
| |
| const delimiter = opts.delimiter || '.' |
| const maxDepth = opts.maxDepth |
| const transformKey = opts.transformKey || keyIdentity |
| const output = {} |
| |
| function step (object, prev, currentDepth) { |
| currentDepth = currentDepth || 1 |
| Object.keys(object).forEach(function (key) { |
| const value = object[key] |
| const isarray = opts.safe && Array.isArray(value) |
| const type = Object.prototype.toString.call(value) |
| const isbuffer = isBuffer(value) |
| const isobject = ( |
| type === '[object Object]' || |
| type === '[object Array]' |
| ) |
| |
| const newKey = prev |
| ? prev + delimiter + transformKey(key) |
| : transformKey(key) |
| |
| if (!isarray && !isbuffer && isobject && Object.keys(value).length && |
| (!opts.maxDepth || currentDepth < maxDepth)) { |
| return step(value, newKey, currentDepth + 1) |
| } |
| |
| output[newKey] = value |
| }) |
| } |
| |
| step(target) |
| |
| return output |
| } |
| |
| function unflatten (target, opts) { |
| opts = opts || {} |
| |
| const delimiter = opts.delimiter || '.' |
| const overwrite = opts.overwrite || false |
| const transformKey = opts.transformKey || keyIdentity |
| const result = {} |
| |
| const isbuffer = isBuffer(target) |
| if (isbuffer || Object.prototype.toString.call(target) !== '[object Object]') { |
| return target |
| } |
| |
| // safely ensure that the key is |
| // an integer. |
| function getkey (key) { |
| const parsedKey = Number(key) |
| |
| return ( |
| isNaN(parsedKey) || |
| key.indexOf('.') !== -1 || |
| opts.object |
| ) ? key |
| : parsedKey |
| } |
| |
| function addKeys (keyPrefix, recipient, target) { |
| return Object.keys(target).reduce(function (result, key) { |
| result[keyPrefix + delimiter + key] = target[key] |
| |
| return result |
| }, recipient) |
| } |
| |
| function isEmpty (val) { |
| const type = Object.prototype.toString.call(val) |
| const isArray = type === '[object Array]' |
| const isObject = type === '[object Object]' |
| |
| if (!val) { |
| return true |
| } else if (isArray) { |
| return !val.length |
| } else if (isObject) { |
| return !Object.keys(val).length |
| } |
| } |
| |
| target = Object.keys(target).reduce(function (result, key) { |
| const type = Object.prototype.toString.call(target[key]) |
| const isObject = (type === '[object Object]' || type === '[object Array]') |
| if (!isObject || isEmpty(target[key])) { |
| result[key] = target[key] |
| return result |
| } else { |
| return addKeys( |
| key, |
| result, |
| flatten(target[key], opts) |
| ) |
| } |
| }, {}) |
| |
| Object.keys(target).forEach(function (key) { |
| const split = key.split(delimiter).map(transformKey) |
| let key1 = getkey(split.shift()) |
| let key2 = getkey(split[0]) |
| let recipient = result |
| |
| while (key2 !== undefined) { |
| if (key1 === '__proto__') { |
| return |
| } |
| |
| const type = Object.prototype.toString.call(recipient[key1]) |
| const isobject = ( |
| type === '[object Object]' || |
| type === '[object Array]' |
| ) |
| |
| // do not write over falsey, non-undefined values if overwrite is false |
| if (!overwrite && !isobject && typeof recipient[key1] !== 'undefined') { |
| return |
| } |
| |
| if ((overwrite && !isobject) || (!overwrite && recipient[key1] == null)) { |
| recipient[key1] = ( |
| typeof key2 === 'number' && |
| !opts.object ? [] : {} |
| ) |
| } |
| |
| recipient = recipient[key1] |
| if (split.length > 0) { |
| key1 = getkey(split.shift()) |
| key2 = getkey(split[0]) |
| } |
| } |
| |
| // unflatten again for 'messy objects' |
| recipient[key1] = unflatten(target[key], opts) |
| }) |
| |
| return result |
| } |