当我将 JSON 从 Firebase 转换为 Java 对象时,为啥会出现“无法弹回输入”?
Posted
技术标签:
【中文标题】当我将 JSON 从 Firebase 转换为 Java 对象时,为啥会出现“无法弹回输入”?【英文标题】:Why do I get "Failed to bounce to type" when I turn JSON from Firebase into Java objects?当我将 JSON 从 Firebase 转换为 Java 对象时,为什么会出现“无法弹回输入”? 【发布时间】:2015-11-13 13:09:10 【问题描述】:[披露:我是 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://***.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)
);
但我收到此错误:
线程“FirebaseEventTarget”com.firebase.client.FirebaseException 中的异常:无法弹回输入
如何将我的用户读入 Java 对象?
【问题讨论】:
您如何看待这种方法? ***.com/a/36706248/2819864 【参考方案1】:Firebase 使用 Jackson 允许将 Java 对象序列化为 JSON,并将 JSON 反序列化回 Java 对象。您可以在the Jackson website 和page about Jackson annotations 上找到有关杰克逊的更多信息。
在本答案的其余部分,我们将展示一些将 Jackson 与 Firebase 结合使用的常用方法。
加载完整用户
将用户从 Firebase 加载到 android 中的最简单方法是,如果我们创建一个完全模仿 JSON 中的属性的 Java 类:
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 "Userhandle='"+handle+“', name='"+name+"', stackId="+stackId+"\’”;
我们可以在监听器中使用这个类:
Firebase ref = new Firebase("https://***.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)
);
您可能会注意到 User 类遵循 JavaBean property pattern。每个 JSON 属性都映射到 User 类中的一个字段,我们为每个字段提供一个公共 getter 方法。通过确保使用完全相同的名称映射所有属性,我们确保 Jackson 可以自动映射它们。
您还可以通过将 Jackson 注释放在您的 Java 类及其字段和方法上来手动控制映射。我们将在下面介绍两个最常见的注释(@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 "Userhandle='" + 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$1.onDataChange(LoadPartialUsers.java:16)
“failed to debounce type”表示 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
类时忽略来自 JSON 的特定属性:
@JsonIgnoreProperties( "stackId" )
private static class User
...
如果我们不再使用侦听器运行代码,Jackson 将知道它可以忽略 JSON 中的 stackId
,并且能够再次将 JSON 反序列化为 User 对象。
由于向 JSON 添加属性是 Firebase 应用程序中的一种常见做法,您可能会发现简单地告诉 Jackson 忽略 Java 类中没有映射的所有属性会更方便:
@JsonIgnoreProperties(ignoreUnknown=true)
private static class User
...
现在,如果我们稍后向 JSON 添加属性,Java 代码仍然能够加载 User
s。请记住,用户对象不会包含 JSON 中存在的所有信息,因此再次将它们写回 Firebase 时要小心。
部分保存用户
拥有自定义 Java 类的好处的一个原因是我们可以向它添加便利方法。假设我们添加了一个方便的方法来获取要为用户显示的名称:
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 "Userhandle='" + handle + "\', name='" + name + "\', displayName='" + getDisplayName() + "'";
现在让我们从 Firebase 读取用户并将它们写回新位置:
Firebase srcRef = new Firebase("https://***.firebaseio.com/32108969/users");
final Firebase copyRef = new Firebase("https://***.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,因此在其输出的 JSON 中添加了 displayName
属性。我们通过在getDisplayName()
中添加JsonIgnore
注释来解决这个问题。
@JsonIgnore
public String getDisplayName()
return getName() + "(" + getHandle() + ")";
当序列化一个 User 对象时,Jackson 现在将忽略 getDisplayName()
方法,我们写出的 JSON 将与我们得到的一样。
【讨论】:
我对您的严谨回答感到非常兴奋,尤其是使用短语we
。坚持下去
我想补充一点,也可以通过JsonHelpers.getMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
访问Jackson ObjectMapper,您也可以通过这种方式禁用此错误-也许如果您无权访问您的课程。
酷,我不知道。谢谢托尼!
忽略未知属性的警告:如果将对象保存回数据库,最终将删除未映射的属性。见***.com/a/34233696/209103
Firebase 9 不再使用 Jackson。是时候换个新答案了:(【参考方案2】:
适用于 Android/Java 的 Firebase SDK 的 9.x(及更高)版本已停止包含用于序列化/反序列化 JavaJSON 的 Jackson。较新的 SDK 提供了一组最少的自定义注释,以允许控制最常见的自定义需求,同时对生成的 JAR/APK 大小的影响最小。
My original answer 在以下情况下仍然有效:
使用 Firebase 2.x SDK 使用 Firebase 9.0 或更高版本的 SDK,但 use Jackson for serializing/deserializing Java<->JSON。本答案的其余部分介绍了如何在 Firebase SDK 9.0 或更高版本中处理序列化/反序列化场景。
数据结构
我们将从 Firebase 数据库中的这个 JSON 结构开始:
"-Jx86I5e8JBMZ9tH6W3Q" :
"handle" : "puf",
"name" : "Frank van Puffelen",
"stackId" : 209103,
"***Id" : 209103
,
"-Jx86Ke_fk44EMl8hRnP" :
"handle" : "mimming",
"name" : "Jenny Tong",
"stackId" : 839465
,
"-Jx86N4qeUNzThqlSMer" :
"handle" : "kato",
"name" : "Kato Wulf",
"stackId" : 394010
加载完整用户
在最基本的情况下,我们可以将每个用户从这个 JSON 加载到以下 Java 类中:
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 "Userhandle='"+handle+"', name='"+name+"', stackId="+stackId+ "'";
如果我们声明这些字段是公开的,我们甚至不需要 getter:
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 "Userhandle='" + handle + "', NAME='" + name + "''";
当我们使用此类从相同的 JSON 加载用户时,代码会运行(与我在其他答案中提到的 Jackson 变体不同)。但是您会在日志输出中看到警告:
警告:在类 Annotations$PartialUser 上找不到 stackId 的设置器/字段
所以摆脱它,我们可以用 @IgnoreExtraProperties
注释类:
@IgnoreExtraProperties
private static class PartialUser
String handle;
String name;
public String getHandle()
return handle;
public String getName() return name;
@Override
public String toString()
return "Userhandle='" + handle + "', NAME='" + name + "''";
部分保存用户
和以前一样,您可能希望向用户添加计算属性。将数据保存回数据库时,您可能希望忽略此类属性。为此,您可以使用 @Exclude
注释属性/getter/setter/字段:
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 "Userhandle='"+handle+"', name='"+name+"', stackId="+stackId+ "'";
现在在将用户写入数据库时,getTag()
的值将被忽略。
在 JSON 中使用与 Java 代码中不同的属性名称
您还可以指定 Java 代码中的字段/getter/setter 应在数据库中的 JSON 中获取什么名称。为此:使用 @PropertyName()
注释字段/getter/setter。
private static class UserWithRenamedProperty
String handle;
String name;
@PropertyName("stackId")
long ***Id;
public String getHandle() return handle;
public String getName() return name;
@PropertyName("stackId")
public long get***Id() return ***Id;
@Override
public String toString() return "Userhandle='"+handle+"', name='"+name+"', stackId="+***Id+ "'";
一般来说,最好使用 Firebase SDK 使用的 JavaJSON 之间的默认映射。但是,当您有一个无法映射到 Java 类的预先存在的 JSON 结构时,可能需要@PropertyName
。
【讨论】:
【参考方案3】:因为您在根目录中的查询路径文件夹错误。这是示例
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"
【讨论】:
感谢@YtgDoc 的回答。不过,我不确定我是否理解它与问题的关系。你能再解释一下吗? 我也遇到了同样的问题,调试器说错误“无法弹回键入”,因为行子级就在根“人”后面一层,当行子级在根后面时它才可以运行“人”两个层次:)。对不起,我的英语水平很差以上是关于当我将 JSON 从 Firebase 转换为 Java 对象时,为啥会出现“无法弹回输入”?的主要内容,如果未能解决你的问题,请参考以下文章
如何在flutter中以排序方式从firebase数据库中检索数据..当我将其检索为Map时出现问题
如何将 firbease 的 DataSnapshot 转换为 json 文件或如何将 firebase 数据库检索为 json 文件