[披露:我是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://stackoverflow.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对象?
答案 0 :(得分:49)
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 "User{handle='"+handle+“', name='"+name+"', stackId="+stackId+"\’}”; }
}
我们可以在监听器中使用此类:
Firebase ref = new Firebase("https://stackoverflow.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 "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$1.onDataChange(LoadPartialUsers.java:16)
“无法去抖动类型”表示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)
杰克逊在JSON中发现了一个属性stackId
,并且不知道如何处理它,所以它抛出异常。幸运的是,我们可以使用一个注释来告诉它在将JSON映射到我们的User
类时忽略JSON中的特定属性:
@JsonIgnoreProperties({ “stackId" })
private static class User {
...
}
如果我们不再使用我们的侦听器运行代码,Jackson将知道它可以忽略JSON中的stackId
,并且它将能够再次将JSON反序列化为User对象。
由于在Firebase应用程序中添加JSON属性是一种常见做法,因此您可能会发现简单地告诉Jackson忽略Java类中没有映射的所有属性更方便:
@JsonIgnoreProperties(ignoreUnknown=true)
private static class User {
...
}
现在,如果我们稍后向JSON添加属性,Java代码仍然可以加载User
。请记住,User对象不会包含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 "User{handle='" + handle + "\', name='" + name + "\', displayName='" + getDisplayName() + "'}";
}
}
现在让我们从Firebase中读取用户并将其写回新位置:
Firebase srcRef = new Firebase("https://stackoverflow.firebaseio.com/32108969/users");
final Firebase copyRef = new Firebase("https://stackoverflow.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中。我们通过向JsonIgnore
添加getDisplayName()
注释来解决此问题。
@JsonIgnore
public String getDisplayName() {
return getName() + "(" + getHandle() + ")";
}
在序列化User对象时,Jackson现在将忽略getDisplayName()
方法,我们写出的JSON将与我们得到的相同。
答案 1 :(得分:3)
用于Android / Java的Firebase SDK的9.x(及更高版本)版本已停止,包括Jackson用于序列化/反序列化Java< - > JSON。相对较新的SDK提供了一组最小的自定义注释,可以控制最常见的自定义需求,同时对生成的JAR / APK大小影响最小。
,仍然有效本答案的其余部分介绍了如何在Firebase SDK 9.0或更高版本中处理序列化/反序列化方案。
我们将在Firebase数据库中使用此JSON结构:
{
"-Jx86I5e8JBMZ9tH6W3Q" : {
"handle" : "puf",
"name" : "Frank van Puffelen",
"stackId" : 209103,
"stackOverflowId" : 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 "User{handle='"+handle+"', name='"+name+"', stackId="+stackId+ "'}"; }
}
如果我们声明这些字段是公开的,我们甚至不需要吸气剂:
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 + "''}";
}
}
当我们使用此类从同一个JSON加载用户时,代码会运行(与我在其他答案中提到的Jackson变体不同)。但是您会在日志记录输出中看到警告:
警告:在类Annotations上找不到stackId的setter / field $ PartialUser
因此,我们可以使用@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 "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代码中的字段/ getter / setter的名称。为此:使用@PropertyName()
注释字段/ getter / setter。
private static class UserWithRenamedProperty {
String handle;
String name;
@PropertyName("stackId")
long stackOverflowId;
public String getHandle() { return handle; }
public String getName() { return name; }
@PropertyName("stackId")
public long getStackOverflowId() { return stackOverflowId; }
@Override
public String toString() { return "User{handle='"+handle+"', name='"+name+"', stackId="+stackOverflowId+ "'}"; }
}
通常,最好使用Firebase SDK使用的Java< - > JSON之间的默认映射。但是,当您拥有预先存在的JSON结构时,可能需要@PropertyName
,否则您无法映射到Java类。
答案 2 :(得分:0)