@@ -44,7 +44,6 @@ import {
44
44
ServerTimestampTransform
45
45
} from '../model/transform_operation' ;
46
46
import { JsonProtoSerializer } from '../remote/serializer' ;
47
- import { SortedSet } from '../util/sorted_set' ;
48
47
import { Blob } from './blob' ;
49
48
import {
50
49
FieldPath as ExternalFieldPath ,
@@ -337,10 +336,10 @@ export class UserDataReader {
337
336
let fieldTransforms : FieldTransform [ ] ;
338
337
339
338
if ( ! fieldPaths ) {
340
- fieldMask = FieldMask . fromArray ( context . fieldMask ) ;
339
+ fieldMask = new FieldMask ( context . fieldMask ) ;
341
340
fieldTransforms = context . fieldTransforms ;
342
341
} else {
343
- let validatedFieldPaths = new SortedSet < FieldPath > ( FieldPath . comparator ) ;
342
+ const validatedFieldPaths : FieldPath [ ] = [ ] ;
344
343
345
344
for ( const stringOrFieldPath of fieldPaths ) {
346
345
let fieldPath : FieldPath ;
@@ -365,10 +364,12 @@ export class UserDataReader {
365
364
) ;
366
365
}
367
366
368
- validatedFieldPaths = validatedFieldPaths . add ( fieldPath ) ;
367
+ if ( ! fieldMaskContains ( validatedFieldPaths , fieldPath ) ) {
368
+ validatedFieldPaths . push ( fieldPath ) ;
369
+ }
369
370
}
370
371
371
- fieldMask = FieldMask . fromSet ( validatedFieldPaths ) ;
372
+ fieldMask = new FieldMask ( validatedFieldPaths ) ;
372
373
fieldTransforms = context . fieldTransforms . filter ( transform =>
373
374
fieldMask . covers ( transform . field )
374
375
) ;
@@ -389,7 +390,7 @@ export class UserDataReader {
389
390
) ;
390
391
validatePlainObject ( 'Data must be an object, but it was:' , context , input ) ;
391
392
392
- let fieldMaskPaths = new SortedSet < FieldPath > ( FieldPath . comparator ) ;
393
+ const fieldMaskPaths : FieldPath [ ] = [ ] ;
393
394
const updateData = new ObjectValueBuilder ( ) ;
394
395
forEach ( input as Dict < unknown > , ( key , value ) => {
395
396
const path = fieldPathFromDotSeparatedString ( methodName , key ) ;
@@ -398,17 +399,17 @@ export class UserDataReader {
398
399
value = this . runPreConverter ( value , childContext ) ;
399
400
if ( value instanceof DeleteFieldValueImpl ) {
400
401
// Add it to the field mask, but don't add anything to updateData.
401
- fieldMaskPaths = fieldMaskPaths . add ( path ) ;
402
+ fieldMaskPaths . push ( path ) ;
402
403
} else {
403
404
const parsedValue = this . parseData ( value , childContext ) ;
404
405
if ( parsedValue != null ) {
405
- fieldMaskPaths = fieldMaskPaths . add ( path ) ;
406
+ fieldMaskPaths . push ( path ) ;
406
407
updateData . set ( path , parsedValue ) ;
407
408
}
408
409
}
409
410
} ) ;
410
411
411
- const mask = FieldMask . fromSet ( fieldMaskPaths ) ;
412
+ const mask = new FieldMask ( fieldMaskPaths ) ;
412
413
return new ParsedUpdateData (
413
414
updateData . build ( ) ,
414
415
mask ,
@@ -449,26 +450,30 @@ export class UserDataReader {
449
450
values . push ( moreFieldsAndValues [ i + 1 ] ) ;
450
451
}
451
452
452
- let fieldMaskPaths = new SortedSet < FieldPath > ( FieldPath . comparator ) ;
453
+ const fieldMaskPaths : FieldPath [ ] = [ ] ;
453
454
const updateData = new ObjectValueBuilder ( ) ;
454
455
455
- for ( let i = 0 ; i < keys . length ; ++ i ) {
456
- const path = keys [ i ] ;
457
- const childContext = context . childContextForFieldPath ( path ) ;
458
- const value = this . runPreConverter ( values [ i ] , childContext ) ;
459
- if ( value instanceof DeleteFieldValueImpl ) {
460
- // Add it to the field mask, but don't add anything to updateData.
461
- fieldMaskPaths = fieldMaskPaths . add ( path ) ;
462
- } else {
463
- const parsedValue = this . parseData ( value , childContext ) ;
464
- if ( parsedValue != null ) {
465
- fieldMaskPaths = fieldMaskPaths . add ( path ) ;
466
- updateData . set ( path , parsedValue ) ;
456
+ // We iterate in reverse order to pick the last value for a field if the
457
+ // user specified the field multiple times.
458
+ for ( let i = keys . length - 1 ; i >= 0 ; -- i ) {
459
+ if ( ! fieldMaskContains ( fieldMaskPaths , keys [ i ] ) ) {
460
+ const path = keys [ i ] ;
461
+ const childContext = context . childContextForFieldPath ( path ) ;
462
+ const value = this . runPreConverter ( values [ i ] , childContext ) ;
463
+ if ( value instanceof DeleteFieldValueImpl ) {
464
+ // Add it to the field mask, but don't add anything to updateData.
465
+ fieldMaskPaths . push ( path ) ;
466
+ } else {
467
+ const parsedValue = this . parseData ( value , childContext ) ;
468
+ if ( parsedValue != null ) {
469
+ fieldMaskPaths . push ( path ) ;
470
+ updateData . set ( path , parsedValue ) ;
471
+ }
467
472
}
468
473
}
469
474
}
470
475
471
- const mask = FieldMask . fromSet ( fieldMaskPaths ) ;
476
+ const mask = new FieldMask ( fieldMaskPaths ) ;
472
477
return new ParsedUpdateData (
473
478
updateData . build ( ) ,
474
479
mask ,
@@ -841,3 +846,8 @@ function fieldPathFromDotSeparatedString(
841
846
function errorMessage ( error : Error | object ) : string {
842
847
return error instanceof Error ? error . message : error . toString ( ) ;
843
848
}
849
+
850
+ /** Checks `haystack` if FieldPath `needle` is present. Runs in O(n). */
851
+ function fieldMaskContains ( haystack : FieldPath [ ] , needle : FieldPath ) : boolean {
852
+ return haystack . some ( v => v . isEqual ( needle ) ) ;
853
+ }
0 commit comments