1 | module.exports = flatten
|
---|
2 | flatten.flatten = flatten
|
---|
3 | flatten.unflatten = unflatten
|
---|
4 |
|
---|
5 | function isBuffer (obj) {
|
---|
6 | return obj &&
|
---|
7 | obj.constructor &&
|
---|
8 | (typeof obj.constructor.isBuffer === 'function') &&
|
---|
9 | obj.constructor.isBuffer(obj)
|
---|
10 | }
|
---|
11 |
|
---|
12 | function keyIdentity (key) {
|
---|
13 | return key
|
---|
14 | }
|
---|
15 |
|
---|
16 | function flatten (target, opts) {
|
---|
17 | opts = opts || {}
|
---|
18 |
|
---|
19 | const delimiter = opts.delimiter || '.'
|
---|
20 | const maxDepth = opts.maxDepth
|
---|
21 | const transformKey = opts.transformKey || keyIdentity
|
---|
22 | const output = {}
|
---|
23 |
|
---|
24 | function step (object, prev, currentDepth) {
|
---|
25 | currentDepth = currentDepth || 1
|
---|
26 | Object.keys(object).forEach(function (key) {
|
---|
27 | const value = object[key]
|
---|
28 | const isarray = opts.safe && Array.isArray(value)
|
---|
29 | const type = Object.prototype.toString.call(value)
|
---|
30 | const isbuffer = isBuffer(value)
|
---|
31 | const isobject = (
|
---|
32 | type === '[object Object]' ||
|
---|
33 | type === '[object Array]'
|
---|
34 | )
|
---|
35 |
|
---|
36 | const newKey = prev
|
---|
37 | ? prev + delimiter + transformKey(key)
|
---|
38 | : transformKey(key)
|
---|
39 |
|
---|
40 | if (!isarray && !isbuffer && isobject && Object.keys(value).length &&
|
---|
41 | (!opts.maxDepth || currentDepth < maxDepth)) {
|
---|
42 | return step(value, newKey, currentDepth + 1)
|
---|
43 | }
|
---|
44 |
|
---|
45 | output[newKey] = value
|
---|
46 | })
|
---|
47 | }
|
---|
48 |
|
---|
49 | step(target)
|
---|
50 |
|
---|
51 | return output
|
---|
52 | }
|
---|
53 |
|
---|
54 | function unflatten (target, opts) {
|
---|
55 | opts = opts || {}
|
---|
56 |
|
---|
57 | const delimiter = opts.delimiter || '.'
|
---|
58 | const overwrite = opts.overwrite || false
|
---|
59 | const transformKey = opts.transformKey || keyIdentity
|
---|
60 | const result = {}
|
---|
61 |
|
---|
62 | const isbuffer = isBuffer(target)
|
---|
63 | if (isbuffer || Object.prototype.toString.call(target) !== '[object Object]') {
|
---|
64 | return target
|
---|
65 | }
|
---|
66 |
|
---|
67 | // safely ensure that the key is
|
---|
68 | // an integer.
|
---|
69 | function getkey (key) {
|
---|
70 | const parsedKey = Number(key)
|
---|
71 |
|
---|
72 | return (
|
---|
73 | isNaN(parsedKey) ||
|
---|
74 | key.indexOf('.') !== -1 ||
|
---|
75 | opts.object
|
---|
76 | ) ? key
|
---|
77 | : parsedKey
|
---|
78 | }
|
---|
79 |
|
---|
80 | function addKeys (keyPrefix, recipient, target) {
|
---|
81 | return Object.keys(target).reduce(function (result, key) {
|
---|
82 | result[keyPrefix + delimiter + key] = target[key]
|
---|
83 |
|
---|
84 | return result
|
---|
85 | }, recipient)
|
---|
86 | }
|
---|
87 |
|
---|
88 | function isEmpty (val) {
|
---|
89 | const type = Object.prototype.toString.call(val)
|
---|
90 | const isArray = type === '[object Array]'
|
---|
91 | const isObject = type === '[object Object]'
|
---|
92 |
|
---|
93 | if (!val) {
|
---|
94 | return true
|
---|
95 | } else if (isArray) {
|
---|
96 | return !val.length
|
---|
97 | } else if (isObject) {
|
---|
98 | return !Object.keys(val).length
|
---|
99 | }
|
---|
100 | }
|
---|
101 |
|
---|
102 | target = Object.keys(target).reduce(function (result, key) {
|
---|
103 | const type = Object.prototype.toString.call(target[key])
|
---|
104 | const isObject = (type === '[object Object]' || type === '[object Array]')
|
---|
105 | if (!isObject || isEmpty(target[key])) {
|
---|
106 | result[key] = target[key]
|
---|
107 | return result
|
---|
108 | } else {
|
---|
109 | return addKeys(
|
---|
110 | key,
|
---|
111 | result,
|
---|
112 | flatten(target[key], opts)
|
---|
113 | )
|
---|
114 | }
|
---|
115 | }, {})
|
---|
116 |
|
---|
117 | Object.keys(target).forEach(function (key) {
|
---|
118 | const split = key.split(delimiter).map(transformKey)
|
---|
119 | let key1 = getkey(split.shift())
|
---|
120 | let key2 = getkey(split[0])
|
---|
121 | let recipient = result
|
---|
122 |
|
---|
123 | while (key2 !== undefined) {
|
---|
124 | if (key1 === '__proto__') {
|
---|
125 | return
|
---|
126 | }
|
---|
127 |
|
---|
128 | const type = Object.prototype.toString.call(recipient[key1])
|
---|
129 | const isobject = (
|
---|
130 | type === '[object Object]' ||
|
---|
131 | type === '[object Array]'
|
---|
132 | )
|
---|
133 |
|
---|
134 | // do not write over falsey, non-undefined values if overwrite is false
|
---|
135 | if (!overwrite && !isobject && typeof recipient[key1] !== 'undefined') {
|
---|
136 | return
|
---|
137 | }
|
---|
138 |
|
---|
139 | if ((overwrite && !isobject) || (!overwrite && recipient[key1] == null)) {
|
---|
140 | recipient[key1] = (
|
---|
141 | typeof key2 === 'number' &&
|
---|
142 | !opts.object ? [] : {}
|
---|
143 | )
|
---|
144 | }
|
---|
145 |
|
---|
146 | recipient = recipient[key1]
|
---|
147 | if (split.length > 0) {
|
---|
148 | key1 = getkey(split.shift())
|
---|
149 | key2 = getkey(split[0])
|
---|
150 | }
|
---|
151 | }
|
---|
152 |
|
---|
153 | // unflatten again for 'messy objects'
|
---|
154 | recipient[key1] = unflatten(target[key], opts)
|
---|
155 | })
|
---|
156 |
|
---|
157 | return result
|
---|
158 | }
|
---|