@@ -335,6 +335,88 @@ function compareMaps(left: api.MapValue, right: api.MapValue): number {
335
335
return primitiveComparator ( leftKeys . length , rightKeys . length ) ;
336
336
}
337
337
338
+ /**
339
+ * Generates the canonical ID for the provided field value (as used in Target
340
+ * serialization).
341
+ */
342
+ export function canonicalId ( value : api . Value ) : string {
343
+ return canonifyValue ( value ) ;
344
+ }
345
+
346
+ function canonifyValue ( value : api . Value ) : string {
347
+ if ( 'nullValue' in value ) {
348
+ return 'null' ;
349
+ } else if ( 'booleanValue' in value ) {
350
+ return '' + value . booleanValue ! ;
351
+ } else if ( 'integerValue' in value ) {
352
+ return '' + value . integerValue ! ;
353
+ } else if ( 'doubleValue' in value ) {
354
+ return '' + value . doubleValue ! ;
355
+ } else if ( 'timestampValue' in value ) {
356
+ return canonifyTimestamp ( value . timestampValue ! ) ;
357
+ } else if ( 'stringValue' in value ) {
358
+ return value . stringValue ! ;
359
+ } else if ( 'bytesValue' in value ) {
360
+ return canonifyByteString ( value . bytesValue ! ) ;
361
+ } else if ( 'referenceValue' in value ) {
362
+ // TODO(mrschmidt): Use document key only
363
+ return value . referenceValue ! ;
364
+ } else if ( 'geoPointValue' in value ) {
365
+ return canonifyGeoPoint ( value . geoPointValue ! ) ;
366
+ } else if ( 'arrayValue' in value ) {
367
+ return canonifyArray ( value . arrayValue ! ) ;
368
+ } else if ( 'mapValue' in value ) {
369
+ return canonifyMap ( value . mapValue ! ) ;
370
+ } else {
371
+ return fail ( 'Invalid value type: ' + JSON . stringify ( value ) ) ;
372
+ }
373
+ }
374
+
375
+ function canonifyByteString ( byteString : string | Uint8Array ) : string {
376
+ return normalizeByteString ( byteString ) . toBase64 ( ) ;
377
+ }
378
+
379
+ function canonifyTimestamp ( timestamp : ProtoTimestampValue ) : string {
380
+ const normalizedTimestamp = normalizeTimestamp ( timestamp ) ;
381
+ return `time(${ normalizedTimestamp . seconds } ,${ normalizedTimestamp . nanos } )` ;
382
+ }
383
+
384
+ function canonifyGeoPoint ( geoPoint : api . LatLng ) : string {
385
+ return `geo(${ geoPoint . latitude } ,${ geoPoint . longitude } )` ;
386
+ }
387
+
388
+ function canonifyMap ( mapValue : api . MapValue ) : string {
389
+ // Iteration order in JavaScript is not guaranteed. To ensure that we generate
390
+ // matching canonical IDs for identical maps, we need to sort the keys.
391
+ const sortedKeys = keys ( mapValue . fields || { } ) . sort ( ) ;
392
+
393
+ let result = '{' ;
394
+ let first = true ;
395
+ for ( const key of sortedKeys ) {
396
+ if ( ! first ) {
397
+ result += ',' ;
398
+ } else {
399
+ first = false ;
400
+ }
401
+ result += `${ key } :${ canonifyValue ( mapValue . fields ! [ key ] ) } ` ;
402
+ }
403
+ return result + '}' ;
404
+ }
405
+
406
+ function canonifyArray ( arrayValue : api . ArrayValue ) : string {
407
+ let result = '[' ;
408
+ let first = true ;
409
+ for ( const value of arrayValue . values || [ ] ) {
410
+ if ( ! first ) {
411
+ result += ',' ;
412
+ } else {
413
+ first = false ;
414
+ }
415
+ result += canonifyValue ( value ) ;
416
+ }
417
+ return result + ']' ;
418
+ }
419
+
338
420
/**
339
421
* Converts the possible Proto values for a timestamp value into a "seconds and
340
422
* nanos" representation.
0 commit comments