当我将JSON从Firebase转换为Java对象时,为什么会出现"无法退回到键入"?

Fra*_*len 41 java android firebase firebase-realtime-database

[披露:我是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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我正在阅读以下代码:

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) { }
});
Run Code Online (Sandbox Code Playgroud)

但我得到这个错误:

线程"FirebaseEventTarget"中的异常com.firebase.client.FirebaseException:无法跳转到类型

如何将用户读入Java对象?

Fra*_*len 50

Firebase使用Jackson允许将Java对象序列化为JSON并将JSON反序列化为Java对象.你可以在Jackson网站和这个关于Jackson注释的页面找到更多关于Jackson 信息.

在本回答的其余部分中,我们将展示使用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+"\’}”; }
}
Run Code Online (Sandbox Code Playgroud)

我们可以在监听器中使用这个类:

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) { }
});
Run Code Online (Sandbox Code Playgroud)

您可能会注意到User类遵循JavaBean属性模式.每个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 + "\’}”; 
  }
}
Run Code Online (Sandbox Code Playgroud)

如果我们现在像以前一样附加相同的监听器并运行程序,它将抛出异常:

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)
Run Code Online (Sandbox Code Playgroud)

"未能去抖动类型"表示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)
Run Code Online (Sandbox Code Playgroud)

杰克逊stackId在JSON中发现了一个属性,并且不知道如何处理它,所以它抛出异常.幸运的是,我们可以使用一个注释来告诉它在将JSON映射到我们的User类时忽略JSON中的特定属性:

@JsonIgnoreProperties({ “stackId" })
private static class User {
  ...
}
Run Code Online (Sandbox Code Playgroud)

如果我们不再使用我们的监听器运行代码,Jackson将知道它可以stackId在JSON中忽略,并且它将能够再次将JSON反序列化为User对象.

由于在Firebase应用程序中添加JSON属性是一种常见做法,因此您可能会发现简单地告诉Jackson忽略Java类中没有映射的所有属性更方便:

@JsonIgnoreProperties(ignoreUnknown=true)
private static class User {
  ...
}
Run Code Online (Sandbox Code Playgroud)

现在,如果我们稍后向JSON添加属性,Java代码仍然可以加载Users.请记住,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() + "'}"; 
  }
}
Run Code Online (Sandbox Code Playgroud)

现在让我们从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) { }
});
Run Code Online (Sandbox Code Playgroud)

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"
    }
}
Run Code Online (Sandbox Code Playgroud)

这与源JSON不同,因为Jackson将新getDisplayName()方法识别为JavaBean getter,从而displayName为其输出的JSON 添加了一个属性.我们通过添加JsonIgnore注释来解决这个问题getDisplayName().

    @JsonIgnore
    public String getDisplayName() {
        return getName() + "(" + getHandle() + ")";
    }
Run Code Online (Sandbox Code Playgroud)

在序列化User对象时,Jackson现在将忽略该getDisplayName()方法,我们写出的JSON将与我们得到的相同.

  • 我想添加它也可以通过`JsonHelpers.getMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,false);`来访问Jackson ObjectMapper,你可以用这种方式禁用这个错误 - 也许你不这样做可以访问你的课程. (9认同)
  • 我对你的答案中的严谨感到激动,特别是使用短语"we".保持 (4认同)
  • Firebase 9不再使用Jackson了.时间换个新答案:( (4认同)
  • 忽略未知属性的一个警告:如果将对象保存回数据库,最终将删除未映射的属性.请参见http://stackoverflow.com/a/34233696/209103 (2认同)