java - 无法使用 Gson 库对 Firestore DocumentReference 数据类型进行序列化/反序列化

标签 java android firebase gson google-cloud-firestore

我在尝试序列化/反序列化 DocumentReference 时遇到以下异常使用 Gson 的 Firestore 数据库中的数据类型图书馆。我正在使用通过模拟器运行的 google play 服务插件(版本 4.0.1)、gson 库(版本 2.8.5)和 Nexus 5X API 25(Android 7.1.1(Google Play))虚拟设备。

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

我能够使用以下代码正确检索 DocumentReference 并将其转换为我的 ReferenceEntity 类。

@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);

                ...
            });
}

这是 ReferenceEntity 类和 BaseEntity 类文件(@SerializedName 注解用于 gson,@PropertyName 注解用于 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;
    }
}

上面的代码可以正常工作,因为 print 语句正确输出:

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

这是缓存方法和序列化方法:

@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);
}

异常发生在上面的serialize方法中。这几天我一直在研究这个问题,但还没有发现任何有用的东西。由于 GeoPoint 和 Timestamp 数据类型(我认为它们都是由简单的原语组成)不会发生此异常,因此可能是由于 DocumentReference 类的属性引起的。上面的代码适用于我在项目中拥有的其他实体(任何没有引用数据类型的实体)。任何帮助,将不胜感激。谢谢。

解决方案:

不是序列化/反序列化引用本身,而是通过对引用使用 getPath() 方法将其序列化为字符串,并通过返回使用 FirebaseFirestore.getInstance().document() 创建的 DocumentReference 来反序列化它。以下是执行此操作的 Serializer 类的完整代码:

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);
    }
}

最佳答案

不要尝试直接序列化 DocumentReference。 DocumenetReference 中唯一需要序列化的重要数据是路径字符串。如果将其序列化,则可以根据需要重新构造 DocumentReference 对象。

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

关于java - 无法使用 Gson 库对 Firestore DocumentReference 数据类型进行序列化/反序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51052338/

相关文章:

java - 如何在java中验证图像标题

android - 从Android中的ADB Shell手动安装SD卡

android - ScrollView 抛出底部 View

android - 通过指定的 SIM 从 android 调用

javascript - 注销后清除 Firebase 持久性索引数据库

java - 如何从 Firebase 存储获取 URL getDownloadURL

java - JDBC 如何在带有 where 子句的准备好的语句中使用占位符?

java - BufferedWriter 没有写入 ByteArrayOutputStream

java - 从 Flex-AS3 向 Java EJB 服务发送日期时出现问题

javascript - 如何获取 Firebase 实时数据库 AuthToken