159 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			159 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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
 | |
| }
 |