简体   繁体   中英

Firestore DocumentReference data type can't be serialized/deserialized using the Gson library

I am experiencing the following exception when attempting to serialize/deserialize the DocumentReference data type in a Firestore database using the Gson library. I am using google play services plugin (version 4.0.1), the gson library (version 2.8.5), and a Nexus 5X API 25 (Android 7.1.1 (Google Play)) virtual device running through the emulator.

W/System.err: java.lang.AssertionError: impossible
D/FA: Logging event (FE): session_start(_s), Bundle[{firebase_event_origin(_o)=auto, firebase_screen_class(_sc)=MainActivity, firebase_screen_id(_si)=-2086822989708624881}]
W/System.err:     at java.lang.Enum$1.create(Enum.java:269)
                  at java.lang.Enum$1.create(Enum.java:260)
                  at libcore.util.BasicLruCache.get(BasicLruCache.java:58)
                  at java.lang.Enum.getSharedConstants(Enum.java:286)
                  at java.lang.Class.getEnumConstantsShared(Class.java:2291)
W/System.err:     at java.lang.Class.getEnumConstants(Class.java:2279)
                  at com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter.<init>(TypeAdapters.java:779)
                  at com.google.gson.internal.bind.TypeAdapters$30.create(TypeAdapters.java:818)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.CollectionTypeAdapterFactory.create(CollectionTypeAdapterFactory.java:53)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
W/System.err:     at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
                  at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
                  at com.google.gson.Gson.getAdapter(Gson.java:458)
                  at com.google.gson.Gson.toJson(Gson.java:696)
                  at com.google.gson.Gson.toJson(Gson.java:683)
                  at com.google.gson.Gson.toJson(Gson.java:638)
                  at com.involveunation.involveu.data.cache.Serializer.serialize(Serializer.java:28)
W/System.err:     at com.involveunation.involveu.data.cache.CacheImpl.put(CacheImpl.java:73)
                  at com.involveunation.involveu.data.repository.feed.FeedCloudDataStore.lambda$getFeedEntityData$2$FeedCloudDataStore(FeedCloudDataStore.java:57)
                  at com.involveunation.involveu.data.repository.feed.FeedCloudDataStore$$Lambda$0.apply(Unknown Source)
                  at io.reactivex.internal.operators.observable.ObservableFlatMap$MergeObserver.onNext(ObservableFlatMap.java:121)
                  at io.reactivex.internal.operators.observable.ObservableCreate$CreateEmitter.onNext(ObservableCreate.java:67)
                  at com.involveunation.involveu.data.network.FirebaseApiImplementation.lambda$null$0$FirebaseApiImplementation(FirebaseApiImplementation.java:74)
                  at com.involveunation.involveu.data.network.FirebaseApiImplementation$$Lambda$9.onComplete(Unknown Source)
                  at com.google.android.gms.tasks.zzj.run(Unknown Source)
                  at android.os.Handler.handleCallback(Handler.java:751)
                  at android.os.Handler.dispatchMessage(Handler.java:95)
                  at android.os.Looper.loop(Looper.java:154)
                  at android.app.ActivityThread.main(ActivityThread.java:6119)
W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
                  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
              Caused by: java.lang.NoSuchMethodException: values []
                  at java.lang.Class.getMethod(Class.java:1981)
                  at java.lang.Class.getDeclaredMethod(Class.java:1960)
                  at java.lang.Enum$1.create(Enum.java:265)
                ... 49 more

I am able to to retrieve the DocumentReference correctly and cast it to my ReferenceEntity class using the following code.

@Override
public Observable<FeedEntity> getFeedEntityData(String id) {
    return this.firebaseApi.getDocument(this.firebaseFirestore.collection("feed").document(id))
            .flatMap((DocumentSnapshot response) -> {

                // Creates the ReferenceEntity by casting the result to class DocumentSnapshot.
                ReferenceEntity referenceEntity = response.toObject(ReferenceEntity.class);
                // Sets the id of the entity for getting it from the cache later.
                referenceEntity.setId("ThrowawayId");

                // Prints out the DocumentReference path from the database.
                System.out.println("ownerReference = " + referenceEntity.getOwnerReference().getPath());

                // Puts the newly made referenceEntity into the cache.
                FeedCloudDataStore.this.cache.put(referenceEntity, ReferenceEntity.class);

                // Gets the referenceEntity from the cache using its key (first parameter).
                // This never gets called due to the exception being thrown in the method above.
                FeedCloudDataStore.this.cache.get("ThrowawayId", ReferenceEntity.class);

                ...
            });
}

Here is the ReferenceEntity class and the BaseEntity class files (@SerializedName annotation is for gson, @PropertyName annotation is for Firestore):

import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.PropertyName;
import com.google.gson.annotations.SerializedName;

public class ReferenceEntity extends BaseEntity {

    public ReferenceEntity() {

    }

    @SerializedName("owner_ref")
    private DocumentReference ownerReference;

    @PropertyName("owner_ref")
    public DocumentReference getOwnerReference() {
        return ownerReference;
    }

    @PropertyName("owner_ref")
    public void setOwnerReference(DocumentReference ownerReference) {
        this.ownerReference = ownerReference;
    }
}

...

public abstract class BaseEntity {

    private String id;

    public String getId() {
        return this.id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

The above code works since the print statement correctly spits out:

I/System.out: ownerReference = organizations/-Kd2GYk3EN9YI4FwmLyu 

This is the caching method and the serialize method:

@Override
public <T> void put(T entity, Class<T> clazz) {
    if (entity != null) {
        final File file = this.createFile(((BaseEntity) entity).getId());
        if (!isCached(((BaseEntity) entity).getId())) {
            final String json = this.serializer.serialize(entity, clazz);
            ...
        }
    }
}

...

public <T> String serialize(T entity, Class<T> clazz) {
    return gson.toJson(entity, clazz);
}

The exception occurs in the serialize method above. I have researched this problem for the past few days and have not yet found anything useful. Since this exception doesn't occur for the GeoPoint and Timestamp data types (which are both composed of simple primitives I think), it may be caused due to the properties of the DocumentReference class. The code above is working correctly for the other entities that I have in the project (any that do not have a Reference data type). Any help would be appreciated. Thank you.

Solution:

Instead of serializing/deserializing the Reference itself, serialize it as a string by using the getPath() method on the reference and deserialize it by returning a DocumentReference created using FirebaseFirestore.getInstance().document(). Here is the full code for the Serializer class that does this:

import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializer;

import java.lang.reflect.Type;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * Json Serializer/Deserializer.
 */
@Singleton
public class Serializer {

    private final Gson gson;

    @Inject
    Serializer() {
        // Custom serializer that accepts a DocumentReference data type and returns the
        // reference path as a string.
        JsonSerializer<DocumentReference> referenceSerializer = (src, type, context) ->
                src == null ? null : new JsonPrimitive(src.getPath());
        // Custom deserializer that accepts a json string for the DocumentReference data type and
        // returns a new DocumentReference that is created using the string reference path.
        JsonDeserializer<DocumentReference> referenceDeserializer = (JsonElement json, Type type,
                                                                     JsonDeserializationContext context) ->
                json == null ? null : FirebaseFirestore.getInstance().document(json.getAsString());
        // Builds the gson object using our custom DocumentReference serializer/deserializer above.
        gson = new GsonBuilder()
                .registerTypeAdapter(DocumentReference.class, referenceSerializer)
                .registerTypeAdapter(DocumentReference.class, referenceDeserializer).create();
    }

    /**
     * Serialize an object to Json.
     *
     * @param entity Object to serialize.
     * @param clazz Type of the entity to serialize.
     */
    public <T> String serialize(T entity, Class<T> clazz) {
        return gson.toJson(entity, clazz);
    }

    /**
     * Deserialize a json representation of an object.
     *
     * @param string Entity json string to deserialize.
     * @param clazz Type of the entity to deserialize.
     */
    public <T> T deserialize(String string, Class<T> clazz) {
        return gson.fromJson(string, clazz);
    }
}

Don't try to serialize a DocumentReference directly. The only important piece of data in a DocumenetReference that you would need to serialize is the path string. If you serialize that, you can reconstitute a DocumentReference object, if necessary.

DocumentReference ref = ...
String path = ref.getPath();
DocumentReference ref2 = FirebaseFirestore.getInstance().document(path);
// Now ref and ref2 point to the same document

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM