当我将 JSON 从 Firebase 转换为 Java 对象时,为什么会得到 "Failed to bounce to type"?

Why do I get "Failed to bounce to type" when I turn JSON from Firebase into Java objects?

[披露:我是 Firebase 的一名工程师。本题作为参考题,一口气回答很多问题。]

我的 Firebase 数据库中有以下 JSON 结构:

{  
  "users": {
    "-Jx5vuRqItEF-7kAgVWy": {
        "handle": "puf",
        "name": "Frank van Puffelen",
        "soId": 209103
    },
    "-Jx5w3IOHD2kRFFgkMbh": {
        "handle": "kato",
        "name": "Kato Wulf",
        "soId": 394010
    },
    "-Jx5x1VWs08Zc5S-0U4p": {
        "handle": "mimming",
        "name": "Jenny Tong",
        "soId": 839465
    }
  }
}

我正在使用以下代码阅读此内容:

private static class User {
    String handle;
    String name;

    public String getHandle() { return handle; }
    public String getName() { return name; }
}

Firebase ref = new Firebase("https://Whosebug.firebaseio.com/32108969/users");

ref.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot usersSnapshot) {
        for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
          User user = userSnapshot.getValue(User.class);
          System.out.println(user.toString());
        }
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) { }
});

但是我得到这个错误:

Exception in thread "FirebaseEventTarget" com.firebase.client.FirebaseException: Failed to bounce to type

如何将我的用户读取到 Java 个对象中?

Firebase 使用 Jackson 允许将 Java 对象序列化为 JSON 并将 JSON 反序列化为 Java 对象。您可以在 the Jackson website and this page about Jackson annotations.

上找到更多关于杰克逊的信息

在本回答的其余部分,我们将展示一些将 Jackson 与 Firebase 结合使用的常用方法。

正在加载完整用户

将用户从 Firebase 加载到 Android 的最简单方法是,如果我们创建一个完全模仿 JSON 中的属性的 Java class:

private static class User {
  String handle;
  String name;
  long stackId;

  public String getHandle() { return handle; }
  public String getName() { return name; }
  public long getStackId() { return stackId; }

  @Override
  public String toString() { return "User{handle='"+handle+“', name='"+name+"', stackId="+stackId+"\’}”; }
}

我们可以在侦听器中使用这个 class:

Firebase ref = new Firebase("https://Whosebug.firebaseio.com/32108969/users");

ref.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot usersSnapshot) {
    for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
      User user = userSnapshot.getValue(User.class);
      System.out.println(user.toString());
    }
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) { }
});

您可能会注意到用户 class 关注 JavaBean property pattern。每个 JSON 属性 都映射到用户 class 中的一个字段,我们为每个字段提供了一个 public getter 方法。通过确保使用完全相同的名称映射所有属性,我们确保 Jackson 可以自动映射它们。

您还可以通过在 Java class 及其字段和方法上放置 Jackson 注释来手动控制映射。我们将在下面介绍两个最常见的注释(@JsonIgnore@JsonIgnoreProperties)。

部分加载用户

假设您只关心 Java 代码中的用户名和句柄。让我们删除 stackId 看看会发生什么:

private static class User {
  String handle;
  String name;

  public String getHandle() { return handle; }
  public String getName() { return name; }

  @Override
  public String toString() { 
    return "User{handle='" + handle + “\', name='" + name + "\’}”; 
  }
}

如果我们现在附加与以前相同的侦听器和 运行 程序,它将抛出异常:

Exception in thread "FirebaseEventTarget" com.firebase.client.FirebaseException: Failed to bounce to type

at com.firebase.client.DataSnapshot.getValue(DataSnapshot.java:187)

at com.firebase.LoadPartialUsers.onDataChange(LoadPartialUsers.java:16)

“debounce 类型失败”表示 Jackson 无法将 JSON 反序列化为 User 对象。在嵌套异常中它告诉我们原因:

Caused by: com.shaded.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "stackId" (class com.firebase.LoadPartialUsers$User), not marked as ignorable (2 known properties: , "handle", "name"])

 at [Source: java.io.StringReader@43079089; line: 1, column: 15] (through reference chain: com.firebase.User["stackId"])

at com.shaded.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:79)

Jackson在JSON中发现了一个属性stackId,不知道如何处理,所以抛出异常。幸运的是,我们可以使用注释来告诉它在将 JSON 映射到我们的 User class:

时忽略来自 JSON 的特定属性
@JsonIgnoreProperties({ "stackId" })
private static class User {
  ...
}

如果我们不再 运行 我们的侦听器的代码,Jackson 将知道它可以忽略 JSON 中的 stackId 并且它将能够反序列化 JSON 再次进入用户对象。

由于向 JSON 添加属性是 Firebase 应用程序中的常见做法,您可能会发现简单地告诉 Jackson 忽略所有在 [=79 中没有映射的属性会更方便=] class:

@JsonIgnoreProperties(ignoreUnknown=true)
private static class User {
  ...
}

现在,如果我们稍后向 JSON 添加属性,Java 代码仍将能够加载 User。请记住,用户对象不会包含 JSON 中存在的所有信息,因此再次将它们写回 Firebase 时要小心。

部分节省用户

自定义 Java class 很好的一个原因是我们可以向它添加方便的方法。假设我们添加了一个方便的方法来获取要为用户显示的名称:

private static class User {
  String handle;
  String name;

  public String getHandle() { return handle; }
  public String getName() { return name; }

  @JsonIgnore
  public String getDisplayName() {
    return getName() + " (" + getHandle() + ")";
  }

  @Override
  public String toString() { 
    return "User{handle='" + handle + "\', name='" + name + "\', displayName='" + getDisplayName() + "'}"; 
  }
}

现在让我们从 Firebase 读取用户并将它们写回新位置:

Firebase srcRef = new Firebase("https://Whosebug.firebaseio.com/32108969/users");
final Firebase copyRef = new Firebase("https://Whosebug.firebaseio.com/32108969/copiedusers");

srcRef.addListenerForSingleValueEvent(new ValueEventListener() {
  @Override
  public void onDataChange(DataSnapshot usersSnapshot) {
    for (DataSnapshot userSnapshot : usersSnapshot.getChildren()) {
      User user = userSnapshot.getValue(User.class);
      copyRef.child(userSnapshot.getKey()).setValue(user);
    }
  }

  @Override
  public void onCancelled(FirebaseError firebaseError) { }
});

copiedusers 节点中的 JSON 如下所示:

"copiedusers": {
    "-Jx5vuRqItEF-7kAgVWy": {
        "displayName": "Frank van Puffelen (puf)",
        "handle": "puf",
        "name": "Frank van Puffelen"
    },
    "-Jx5w3IOHD2kRFFgkMbh": {
        "displayName": "Kato Wulf (kato)",
        "handle": "kato",
        "name": "Kato Wulf"
    },
    "-Jx5x1VWs08Zc5S-0U4p": {
        "displayName": "Jenny Tong (mimming)",
        "handle": "mimming",
        "name": "Jenny Tong"
    }
}

这与来源 JSON 不同,因为 Jackson 将新的 getDisplayName() 方法识别为 JavaBean getter,因此添加了 displayName 属性 到它输出的 JSON 。我们通过在 getDisplayName().

中添加一个 JsonIgnore 注释来解决这个问题
    @JsonIgnore
    public String getDisplayName() {
        return getName() + "(" + getHandle() + ")";
    }

当序列化一个 User 对象时,Jackson 现在将忽略 getDisplayName() 方法并且我们写出的 JSON 将与我们得到的相同。

因为您在 root.this 中的错误查询路径文件夹是示例

My code:
    private void GetUpdates(DataSnapshot snapshot){
        romchat.clear();
        for (DataSnapshot ds: snapshot.getChildren()){
            Rowitemroom row = new Rowitemroom();
            row.setCaption(ds.getValue(Rowitemroom.class).getCaption());
            row.setFileUrl(ds.getValue(Rowitemroom.class).getFileUrl());
            romchat.add(row);
/*            Rowitemroom row = snapshot.getValue(Rowitemroom.class);
            String caption = row.getCaption();
            String url = row.getFileUrl();*/

        }
        if (romchat.size()>0){
            adapter = new CustomRoom(context,romchat);
            recyclerView.setAdapter(adapter);
        }else {
            Toast.makeText(context, "No data", Toast.LENGTH_SHORT).show();
        }
    }
        db_url ="your apps`enter code here`.appspot.com/admins"

用于 Android/Java 的 9.x(及更高)版本的 Firebase SDK 停止包括用于 serializing/deserializing Java<->JSON 的 Jackson。较新的 SDK 提供了一组最小的自定义注释,以允许控制最常见的自定义需求,同时对结果 JAR/APK 大小的影响最小。


如果您满足以下条件,

仍然有效:

  • 使用 Firebase 2.x SDK
  • 使用 Firebase 9.0 或更高版本的 SDK,但

此答案的其余部分涵盖如何处理 serialization/deserialization Firebase SDK 9.0 或更高版本中的场景。


数据结构

我们将从 Firebase 数据库中的这个 JSON 结构开始:

{
  "-Jx86I5e8JBMZ9tH6W3Q" : {
    "handle" : "puf",
    "name" : "Frank van Puffelen",
    "stackId" : 209103,
    "WhosebugId" : 209103
  },
  "-Jx86Ke_fk44EMl8hRnP" : {
    "handle" : "mimming",
    "name" : "Jenny Tong",
    "stackId" : 839465
  },
  "-Jx86N4qeUNzThqlSMer" : {
    "handle" : "kato",
    "name" : "Kato Wulf",
    "stackId" : 394010
  }
}

正在加载完整用户

在最基本的情况下,我们可以将此 JSON 中的每个用户加载到以下 Java class:

private static class CompleteUser {
    String handle;
    String name;
    long stackId;

    public String getHandle() { return handle; }
    public String getName() { return name; }
    public long getStackId() { return stackId; }

    @Override
    public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+stackId+ "'}"; }
}

如果我们将字段声明为 public,我们甚至不需要吸气剂:

private static class CompleteUser {
    public String handle;
    public String name;
    public long stackId;
}

部分加载用户

我们也可以部分加载用户,例如:

private static class PartialUser {
    String handle;
    String name;

    public String getHandle() {
        return handle;
    }
    public String getName() { return name; }

    @Override
    public String toString() {
        return "User{handle='" + handle + "', NAME='" + name + "''}";
    }
}

当我们使用此 class 从同一个 JSON 加载用户时,代码会运行(与我在其他答案中提到的 Jackson 变体不同)。但是您会在日志输出中看到一条警告:

WARNING: No setter/field for stackId found on class Annotations$PartialUser

所以摆脱它,我们可以用 @IgnoreExtraProperties:

注释 class
@IgnoreExtraProperties
private static class PartialUser {
    String handle;
    String name;

    public String getHandle() {
        return handle;
    }
    public String getName() { return name; }

    @Override
    public String toString() {
        return "User{handle='" + handle + "', NAME='" + name + "''}";
    }
}

部分节省用户

和以前一样,您可能希望向用户添加计算的 属性。在将数据保存回数据库时,您希望忽略这样的 属性。为此,您可以使用 @Exclude:

注释 property/getter/setter/field
private static class OvercompleteUser {
    String handle;
    String name;
    long stackId;

    public String getHandle() { return handle; }
    public String getName() { return name; }
    public long getStackId() { return stackId; }

    @Exclude
    public String getTag() { return getName() + " ("+getHandle()+")"; }

    @Override
    public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+stackId+ "'}"; }
}

现在将用户写入数据库时​​,getTag() 的值将被忽略。

在 JSON 中使用与在 Java 代码中不同的 属性 名称

您还可以指定 Java 代码中的 field/getter/setter 应该在数据库中的 JSON 中得到什么名称。为此:用 @PropertyName().

注释 field/getter/setter
private static class UserWithRenamedProperty {
    String handle;
    String name;
    @PropertyName("stackId")
    long WhosebugId;

    public String getHandle() { return handle; }
    public String getName() { return name; }
    @PropertyName("stackId")
    public long getWhosebugId() { return WhosebugId; }

    @Override
    public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+WhosebugId+ "'}"; }
}

一般来说,最好使用 Firebase SDK 使用的 Java<->JSON 之间的默认映射。但是当你有一个预先存在的 JSON 结构时,你可能需要 @PropertyName 否则你无法映射到 Java classes.