@@ -116,20 +116,36 @@ function npGet(cont, parts) {
116116
117117/*
118118 * Can this value be deleted? We can delete any empty object (null, undefined, [], {})
119- * EXCEPT empty data arrays. Info arrays can be safely deleted, but not deleting them
120- * has no ill effects other than leaving a trace or layout object with some cruft in it.
121- * But deleting data arrays can change the meaning of the object, as `[]` means there is
119+ * EXCEPT empty data arrays, {} inside an array, or anything INSIDE an *args* array.
120+ *
121+ * Info arrays can be safely deleted, but not deleting them has no ill effects other
122+ * than leaving a trace or layout object with some cruft in it.
123+ *
124+ * Deleting data arrays can change the meaning of the object, as `[]` means there is
122125 * data for this attribute, it's just empty right now while `undefined` means the data
123- * should be filled in with defaults to match other data arrays. So we do some simple
124- * tests here to find known non-data arrays but don't worry too much about not deleting
125- * some arrays that would actually be safe to delete.
126+ * should be filled in with defaults to match other data arrays.
127+ *
128+ * `{}` inside an array means "the default object" which is clearly different from
129+ * popping it off the end of the array, or setting it `undefined` inside the array.
130+ *
131+ * *args* arrays get passed directly to API methods and we should respect precisely
132+ * what the user has put there - although if the whole *args* array is empty it's fine
133+ * to delete that.
134+ *
135+ * So we do some simple tests here to find known non-data arrays but don't worry too
136+ * much about not deleting some arrays that would actually be safe to delete.
126137 */
127- var INFO_PATTERNS = / ( ^ | .) ( ( d o m a i n | r a n g e ) ( \. [ x y ] ) ? | a r g s | p a r a l l e l s ) $ / ;
138+ var INFO_PATTERNS = / ( ^ | \. ) ( ( d o m a i n | r a n g e ) ( \. [ x y ] ) ? | a r g s | p a r a l l e l s ) $ / ;
139+ var ARGS_PATTERN = / ( ^ | \. ) a r g s \[ / ;
128140function isDeletable ( val , propStr ) {
129- if ( ! emptyObj ( val ) ) return false ;
141+ if ( ! emptyObj ( val ) ||
142+ ( isPlainObject ( val ) && propStr . charAt ( propStr . length - 1 ) === ']' ) ||
143+ ( propStr . match ( ARGS_PATTERN ) && val !== undefined )
144+ ) {
145+ return false ;
146+ }
130147 if ( ! isArray ( val ) ) return true ;
131148
132- // domain and range are special - they show up in lots of places so hard code here.
133149 if ( propStr . match ( INFO_PATTERNS ) ) return true ;
134150
135151 var match = containerArrayMatch ( propStr ) ;
@@ -186,8 +202,11 @@ function npSet(cont, parts, propStr) {
186202}
187203
188204function joinPropStr ( propStr , newPart ) {
189- if ( ! propStr ) return newPart ;
190- return propStr + isNumeric ( newPart ) ? ( '[' + newPart + ']' ) : ( '.' + newPart ) ;
205+ var toAdd = newPart ;
206+ if ( isNumeric ( newPart ) ) toAdd = '[' + newPart + ']' ;
207+ else if ( propStr ) toAdd = '.' + newPart ;
208+
209+ return propStr + toAdd ;
191210}
192211
193212// handle special -1 array index
@@ -244,11 +263,7 @@ function pruneContainers(containerLevels) {
244263 remainingKeys = false ;
245264 if ( isArray ( curCont ) ) {
246265 for ( j = curCont . length - 1 ; j >= 0 ; j -- ) {
247- // If there's a plain object in an array, it's a container array
248- // so we don't delete empty containers because they still have meaning.
249- // `editContainerArray` handles the API for adding/removing objects
250- // in this case.
251- if ( emptyObj ( curCont [ j ] ) && ! isPlainObject ( curCont [ j ] ) ) {
266+ if ( isDeletable ( curCont [ j ] , joinPropStr ( propPart , j ) ) ) {
252267 if ( remainingKeys ) curCont [ j ] = undefined ;
253268 else curCont . pop ( ) ;
254269 }
0 commit comments