11const util = require ( 'util' )
22const _data = Symbol ( 'data' )
33const _delete = Symbol ( 'delete' )
4+ const _append = Symbol ( 'append' )
45
5- const sqBracketsMatcher = str => str . match ( / ( .+ ) \[ ( [ ^ \] ] + ) \] ( .* ) $ / )
6+ const sqBracketsMatcher = str => str . match ( / ( .+ ) \[ ( [ ^ \] ] + ) \] \. ? ( .* ) $ / )
67
7- const cleanLeadingDot = str =>
8- str && str . startsWith ( '.' ) ? str . substr ( 1 ) : str
8+ // replaces any occurence of an empty-brackets (e.g: []) with a special
9+ // Symbol(append) to represent it, this is going to be useful for the setter
10+ // method that will push values to the end of the array when finding these
11+ const replaceAppendSymbols = str => {
12+ const matchEmptyBracket = str . match ( / ^ ( .* ) \[ \] \. ? ( .* ) $ / )
13+
14+ if ( matchEmptyBracket ) {
15+ const [ , pre , post ] = matchEmptyBracket
16+ return [ ...replaceAppendSymbols ( pre ) , _append , post ] . filter ( Boolean )
17+ }
18+
19+ return [ str ]
20+ }
921
1022const parseKeys = ( key ) => {
1123 const sqBracketItems = new Set ( )
24+ sqBracketItems . add ( _append )
1225 const parseSqBrackets = ( str ) => {
1326 const index = sqBracketsMatcher ( str )
1427
@@ -21,7 +34,7 @@ const parseKeys = (key) => {
2134 // foo.bar[foo.bar] should split into { foo: { bar: { 'foo.bar': {} } }
2235 /* eslint-disable-next-line no-new-wrappers */
2336 const foundKey = new String ( index [ 2 ] )
24- const postSqBracketPortion = cleanLeadingDot ( index [ 3 ] )
37+ const postSqBracketPortion = index [ 3 ]
2538
2639 // we keep track of items found during this step to make sure
2740 // we don't try to split-separate keys that were defined within
@@ -43,7 +56,11 @@ const parseKeys = (key) => {
4356 ]
4457 }
4558
46- return [ str ]
59+ // at the end of parsing, any usage of the special empty-bracket syntax
60+ // (e.g: foo.array[]) has not yet been parsed, here we'll take care
61+ // of parsing it and adding a special symbol to represent it in
62+ // the resulting list of keys
63+ return replaceAppendSymbols ( str )
4764 }
4865
4966 const res = [ ]
@@ -79,6 +96,14 @@ const getter = ({ data, key }) => {
7996 let label = ''
8097
8198 for ( const k of keys ) {
99+ // empty-bracket-shortcut-syntax is not supported on getter
100+ if ( k === _append ) {
101+ throw Object . assign (
102+ new Error ( 'Empty brackets are not valid syntax for retrieving values.' ) ,
103+ { code : 'EINVALIDSYNTAX' }
104+ )
105+ }
106+
82107 // extra logic to take into account printing array, along with its
83108 // special syntax in which using a dot-sep property name after an
84109 // arry will expand it's results, e.g:
@@ -119,13 +144,39 @@ const setter = ({ data, key, value, force }) => {
119144 // ['foo', 'bar', 'baz'] -> { foo: { bar: { baz: {} } }
120145 const keys = parseKeys ( key )
121146 const setKeys = ( _data , _key ) => {
122- // handles array indexes, making sure the new array is created if
123- // missing and properly casting the index to a number
124- const maybeIndex = Number ( _key )
125- if ( ! Number . isNaN ( maybeIndex ) ) {
147+ // handles array indexes, converting valid integers to numbers,
148+ // note that occurences of Symbol(append) will throw,
149+ // so we just ignore these for now
150+ let maybeIndex = Number . NaN
151+ try {
152+ maybeIndex = Number ( _key )
153+ } catch ( err ) { }
154+ if ( ! Number . isNaN ( maybeIndex ) )
126155 _key = maybeIndex
127- if ( ! Object . keys ( _data ) . length )
128- _data = [ ]
156+
157+ // creates new array in case key is an index
158+ // and the array obj is not yet defined
159+ const keyIsAnArrayIndex = _key === maybeIndex || _key === _append
160+ const dataHasNoItems = ! Object . keys ( _data ) . length
161+ if ( keyIsAnArrayIndex && dataHasNoItems && ! Array . isArray ( _data ) )
162+ _data = [ ]
163+
164+ // converting from array to an object is also possible, in case the
165+ // user is using force mode, we should also convert existing arrays
166+ // to an empty object if the current _data is an array
167+ if ( force && Array . isArray ( _data ) && ! keyIsAnArrayIndex )
168+ _data = { ..._data }
169+
170+ // the _append key is a special key that is used to represent
171+ // the empty-bracket notation, e.g: arr[] -> arr[arr.length]
172+ if ( _key === _append ) {
173+ if ( ! Array . isArray ( _data ) ) {
174+ throw Object . assign (
175+ new Error ( `Can't use append syntax in non-Array element` ) ,
176+ { code : 'ENOAPPEND' }
177+ )
178+ }
179+ _key = _data . length
129180 }
130181
131182 // retrieves the next data object to recursively iterate on,
@@ -141,20 +192,30 @@ const setter = ({ data, key, value, force }) => {
141192 // appended to the resulting obj is not an array index, then it
142193 // should throw since we can't append arbitrary props to arrays
143194 const shouldNotAddPropsToArrays =
195+ typeof keys [ 0 ] !== 'symbol' &&
144196 Array . isArray ( _data [ _key ] ) &&
145197 Number . isNaN ( Number ( keys [ 0 ] ) )
146198
147199 const overrideError =
148200 haveContents &&
149- ( shouldNotOverrideLiteralValue || shouldNotAddPropsToArrays )
150-
201+ shouldNotOverrideLiteralValue
151202 if ( overrideError ) {
152203 throw Object . assign (
153- new Error ( `Property ${ key } already has a value in place .` ) ,
204+ new Error ( `Property ${ _key } already exists and is not an Array or Object .` ) ,
154205 { code : 'EOVERRIDEVALUE' }
155206 )
156207 }
157208
209+ const addPropsToArrayError =
210+ haveContents &&
211+ shouldNotAddPropsToArrays
212+ if ( addPropsToArrayError ) {
213+ throw Object . assign (
214+ new Error ( `Can't add property ${ key } to an Array.` ) ,
215+ { code : 'ENOADDPROP' }
216+ )
217+ }
218+
158219 return typeof _data [ _key ] === 'object' ? _data [ _key ] || { } : { }
159220 }
160221
0 commit comments