当我将 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 代码仍然能够加载 Users。请记住,用户对象不会包含 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 文件

Flutter 从 Json 转换嵌套对象返回 null

如何将我上传的文件转换为 json

javascript 将json firebase转换为数组。

从Firebase响应嵌套JSON对象获取键/值对