1515 * limitations under the License.
1616 */
1717
18- import { BundleConverterImpl } from '../core/bundle_impl' ;
18+ import { BundleConverterImpl , BundleLoader } from '../core/bundle_impl' ;
1919import { createBundleReaderSync } from '../core/firestore_client' ;
2020import { newQueryComparator } from '../core/query' ;
2121import { ChangeType , ViewSnapshot } from '../core/view_snapshot' ;
@@ -36,9 +36,14 @@ import {
3636} from '../lite-api/snapshot' ;
3737import { UntypedFirestoreDataConverter } from '../lite-api/user_data_reader' ;
3838import { AbstractUserDataWriter } from '../lite-api/user_data_writer' ;
39+ import { fromBundledQuery } from '../local/local_serializer' ;
40+ import { documentKeySet } from '../model/collections' ;
3941import { Document } from '../model/document' ;
4042import { DocumentKey } from '../model/document_key' ;
43+ import { DocumentSet } from '../model/document_set' ;
44+ import { ResourcePath } from '../model/path' ;
4145import { newSerializer } from '../platform/serializer' ;
46+ import { fromDocument } from '../remote/serializer' ;
4247import { debugAssert , fail } from '../util/assert' ;
4348import {
4449 BundleBuilder ,
@@ -507,6 +512,11 @@ export class DocumentSnapshot<
507512 return undefined ;
508513 }
509514
515+ /**
516+ * Returns a JSON-serializable representation of this `DocumentSnapshot` instance.
517+ *
518+ * @returns a JSON representation of this object.
519+ */
510520 toJSON ( ) : object {
511521 const document = this . _document ;
512522 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -548,6 +558,16 @@ export class DocumentSnapshot<
548558 return result ;
549559 }
550560
561+ /**
562+ * Builds a `DocumentSnapshot` instance from a JSON object created by
563+ * {@link DocumentSnapshot.toJSON}.
564+ *
565+ * @param firestore - The {@link Firestore} instance the snapshot should be loaded for.
566+ * @param json - a JSON object represention of a `DocumentSnapshot` instance.
567+ * @param converter - Converts objects to and from Firestore.
568+ * @returns an instance of {@link DocumentSnapshot} if the JSON object could be
569+ * parsed. Throws a {@link FirestoreError} if an error occurs.
570+ */
551571 static fromJSON <
552572 AppModelType ,
553573 DbModelType extends DocumentData = DocumentData
@@ -777,6 +797,11 @@ export class QuerySnapshot<
777797 return this . _cachedChanges ;
778798 }
779799
800+ /**
801+ * Returns a JSON-serializable representation of this `QuerySnapshot` instance.
802+ *
803+ * @returns a JSON representation of this object.
804+ */
780805 toJSON ( ) : object {
781806 // eslint-disable-next-line @typescript-eslint/no-explicit-any
782807 const result : any = { } ;
@@ -825,6 +850,111 @@ export class QuerySnapshot<
825850 result [ 'bundle' ] = builder . build ( ) ;
826851 return result ;
827852 }
853+
854+ /**
855+ * Builds a `QuerySnapshot` instance from a JSON object created by
856+ * {@link QuerySnapshot.toJSON}.
857+ *
858+ * @param firestore - The {@link Firestore} instance the snapshot should be loaded for.
859+ * @param json - a JSON object represention of a `QuerySnapshot` instance.
860+ * @returns an instance of {@link QuerySnapshot} if the JSON object could be
861+ * parsed. Throws a {@link FirestoreError} if an error occurs.
862+ */
863+ static fromJSON <
864+ AppModelType ,
865+ DbModelType extends DocumentData = DocumentData
866+ > (
867+ db : Firestore ,
868+ json : object
869+ ) : QuerySnapshot < AppModelType , DbModelType > | null {
870+ const requiredFields = [ 'bundle' , 'bundleName' , 'bundleSource' ] ;
871+ let error : string | undefined = undefined ;
872+ let bundleString : string = '' ;
873+ for ( const key of requiredFields ) {
874+ if ( ! ( key in json ) ) {
875+ error = `json missing required field: ${ key } ` ;
876+ }
877+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
878+ const value = ( json as any ) [ key ] ;
879+ if ( key === 'bundleSource' ) {
880+ if ( typeof value !== 'string' ) {
881+ error = `json field 'bundleSource' must be a string.` ;
882+ break ;
883+ } else if ( value !== 'QuerySnapshot' ) {
884+ error = "Expected 'bundleSource' field to equal 'QuerySnapshot'" ;
885+ break ;
886+ }
887+ } else if ( key === 'bundle' ) {
888+ if ( typeof value !== 'string' ) {
889+ error = `json field 'bundle' must be a string.` ;
890+ break ;
891+ }
892+ bundleString = value ;
893+ }
894+ }
895+ if ( error ) {
896+ throw new FirestoreError ( Code . INVALID_ARGUMENT , error ) ;
897+ }
898+ // Parse the bundle data.
899+ const serializer = newSerializer ( db . _databaseId ) ;
900+ const bundleReader = createBundleReaderSync ( bundleString , serializer ) ;
901+ const bundleMetadata = bundleReader . getMetadata ( ) ;
902+ const elements = bundleReader . getElements ( ) ;
903+ if ( elements . length === 0 ) {
904+ throw new FirestoreError (
905+ Code . INVALID_ARGUMENT ,
906+ 'No snapshat data was found in the bundle.'
907+ ) ;
908+ }
909+
910+ const bundleLoader : BundleLoader = new BundleLoader (
911+ bundleMetadata ,
912+ serializer
913+ ) ;
914+ for ( const element of elements ) {
915+ bundleLoader . addSizedElement ( element ) ;
916+ }
917+ const parsedNamedQueries = bundleLoader . queries ;
918+ if ( parsedNamedQueries . length !== 1 ) {
919+ throw new FirestoreError (
920+ Code . INVALID_ARGUMENT ,
921+ 'Snapshot data contained more than one named query.'
922+ ) ;
923+ }
924+
925+ const query = fromBundledQuery ( parsedNamedQueries [ 0 ] . bundledQuery ! ) ;
926+ // convert bundle data into the types that the DocumentSnapshot constructore requires.
927+ const liteUserDataWriter = new LiteUserDataWriter ( db ) ;
928+
929+ const bundledDocuments = bundleLoader . documents ;
930+ const documentSet = new DocumentSet ( ) ;
931+ const documentKeys = documentKeySet ( ) ;
932+ for ( const bundledDocumet of bundledDocuments ) {
933+ const document = fromDocument ( serializer , bundledDocumet . document ! ) ;
934+ documentSet . add ( document ) ;
935+ const documentPath = ResourcePath . fromString (
936+ bundledDocumet . metadata . name !
937+ ) ;
938+ documentKeys . add ( new DocumentKey ( documentPath ) ) ;
939+ }
940+
941+ const viewSnapshot = ViewSnapshot . fromInitialDocuments (
942+ query ,
943+ documentSet ,
944+ documentKeys ,
945+ false , // fromCache
946+ false // hasCachedResults
947+ ) ;
948+
949+ const externalQuery = new Query < AppModelType , DbModelType > ( db , null , query ) ;
950+
951+ return new QuerySnapshot < AppModelType , DbModelType > (
952+ db ,
953+ liteUserDataWriter ,
954+ externalQuery ,
955+ viewSnapshot
956+ ) ;
957+ }
828958}
829959
830960/** Calculates the array of `DocumentChange`s for a given `ViewSnapshot`. */
0 commit comments