| 124 | // e.g: ['foo', 'bar', 'baz'] -> { foo: { bar: { baz: {} } } |
| 125 | const keys = parseKeys(key) |
| 126 | const setKeys = (_data, _key) => { |
| 127 | if (FORBIDDEN_KEYS.has(String(_key))) { |
| 128 | throw Object.assign(new Error(`Forbidden key: "${_key}"`), { code: 'EFORBIDDENKEY' }) |
| 129 | } |
| 130 | // handles array indexes, converting valid integers to numbers |
| 131 | // note that occurrences of Symbol(append) will throw so we just ignore these for now |
| 132 | let maybeIndex = Number.NaN |
| 133 | try { |
| 134 | maybeIndex = Number(_key) |
| 135 | } catch { |
| 136 | // leave it NaN |
| 137 | } |
| 138 | if (!Number.isNaN(maybeIndex)) { |
| 139 | _key = maybeIndex |
| 140 | } |
| 141 | |
| 142 | // creates new array in case key is an index and the array obj is not yet defined |
| 143 | const keyIsAnArrayIndex = _key === maybeIndex || _key === _append |
| 144 | const dataHasNoItems = !Object.keys(_data).length |
| 145 | if (keyIsAnArrayIndex && dataHasNoItems && !Array.isArray(_data)) { |
| 146 | _data = [] |
| 147 | } |
| 148 | |
| 149 | // converting from array to an object is also possible, in case the user is using force mode |
| 150 | // we should also convert existing arrays to an empty object if the current _data is an array |
| 151 | if (force && Array.isArray(_data) && !keyIsAnArrayIndex) { |
| 152 | _data = { ..._data } |
| 153 | } |
| 154 | |
| 155 | // the _append key is a special key that is used to represent the empty-bracket notation |
| 156 | // e.g: arr[] -> arr[arr.length] |
| 157 | if (_key === _append) { |
| 158 | if (!Array.isArray(_data)) { |
| 159 | throw Object.assign(new Error(`Can't use append syntax in non-Array element`), { |
| 160 | code: 'ENOAPPEND', |
| 161 | }) |
| 162 | } |
| 163 | _key = _data.length |
| 164 | } |
| 165 | |
| 166 | // retrieves the next data object to recursively iterate on |
| 167 | // throws if trying to override a literal value or add props to an array |
| 168 | const next = () => { |
| 169 | const haveContents = !force && _data[_key] != null && value !== _delete |
| 170 | const shouldNotOverrideLiteralValue = !(typeof _data[_key] === 'object') |
| 171 | // if the next obj to recurse is an array and the next key to be appended to the resulting obj is not an array index, then it should throw since we can't append arbitrary props to arrays |
| 172 | const shouldNotAddPropsToArrays = |
| 173 | typeof keys[0] !== 'symbol' && Array.isArray(_data[_key]) && Number.isNaN(Number(keys[0])) |
| 174 | |
| 175 | const overrideError = haveContents && shouldNotOverrideLiteralValue |
| 176 | if (overrideError) { |
| 177 | throw Object.assign( |
| 178 | new Error(`Property ${_key} already exists and is not an Array or Object.`), |
| 179 | { code: 'EOVERRIDEVALUE' } |
| 180 | ) |
| 181 | } |
| 182 | |
| 183 | const addPropsToArrayError = haveContents && shouldNotAddPropsToArrays |