Why do I get “Failed to bounce to type” when I tur

2018-12-31 19:41发布

问题:

[Disclosure: I am an engineer at Firebase. This question is meant to be a reference question to answer many questions in one go.]

I have the following JSON structure in my Firebase database:

{  
  \"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
    }
  }
}

I am reading this with the following code:

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) { }
});

But I get this error:

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

How can I read my users into Java objects?

回答1:

Firebase uses Jackson to allow serialization of Java objects to JSON and deserialization of JSON back into Java objects. You can find more about Jackson on the Jackson website and this page about Jackson annotations.

In the rest of this answer, we’ll show a few common ways of using Jackson with Firebase.

Loading complete users

The simplest way of loading the users from Firebase into Android is if we create a Java class that completely mimics the properties in the JSON:

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+\"\\’}”; }
}

We can use this class in a listener:

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) { }
});

You may note that the User class follow the JavaBean property pattern. Every JSON property maps by a field in the User class and we have a public getter method for each field. By ensuring that all properties are mapped with the exact same name, we ensure that Jackson can automatically map them.

You can also manually control the mapping by putting Jackson annotations on your Java class, and its fields and methods. We’ll cover the two most common annotations (@JsonIgnore and @JsonIgnoreProperties) below.

Partially loading users

Say that you only care about the user’s name and handle in your Java code. Let’s remove the stackId and see what happens:

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 + \"\\’}”; 
  }
}

If we now attach the same listener as before and run the program, it will throw an exception:

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)

The “failed to debounce type” indicates that Jackson was unable to deserialize the JSON into a User object. In the nested exception it tells us why:

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 found a property stackId in the JSON and doesn’t know what to do with it, so it throws an exception. Luckily there is an annotation that we can use to tell it to ignore specific properties from the JSON when mapping it to our User class:

@JsonIgnoreProperties({ “stackId\" })
private static class User {
  ...
}

If we not run the code with our listener again, Jackson will know that it can ignore stackId in the JSON and it will be able to deserialize the JSON into a User object again.

Since adding properties to the JSON is such a common practice in Firebase applications, you may find it more convenient to simply tell Jackson to ignore all properties that don’t have a mapping in the Java class:

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

Now if we add properties to the JSON later, the Java code will still be able to load the Users. Just keep in mind that the User objects won’t contain all information that was present in the JSON, so be careful when writing them back to Firebase again.

Partially saving users

One reason why it is nice to have a custom Java class, is that we can add convenience methods to it. Say that we add a convenience method that gets the name to display for a user:

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() + \"\'}\"; 
  }
}

Now let\'s read the users from Firebase and write them back into a new location:

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) { }
});

The JSON in the copiedusers node looks like this:

\"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\"
    }
}

That’s not the same as the source JSON, because Jackson recognizes the new getDisplayName() method as a JavaBean getter and thus added a displayName property to the JSON it outputs. We solve this problem by adding a JsonIgnore annotation to getDisplayName().

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

When serializing a User object, Jackson will now ignore the getDisplayName() method and the JSON we write out will be the same as what we got it.



回答2:

The 9.x (and higher) versions of the Firebase SDK for Android/Java stopped including Jackson for serializing/deserializing Java<->JSON. The newer SDK instead provides a minimal set of custom annotations to allow control over the most common customization needs, while having a minimal impact on the resulting JAR/APK size.


My original answer is still valid if you\'re:

  • Using the Firebase 2.x SDKs
  • Using the Firebase 9.0 or higher SDKs, but use Jackson for serializing/deserializing Java<->JSON.

The rest of this answer covers how to handle serialization/deserialization scenarios in Firebase SDK 9.0 or higher.


Data structure

We\'ll start with this JSON structure in our Firebase Database:

{
  \"-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
  }
}

Loading complete users

At its most basic, we can load each user from this JSON into the following 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+ \"\'}\"; }
}

If we declare the fields to be public, we don\'t even need the getters:

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

Partially loading users

We can also partially load a user, for example with:

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 + \"\'\'}\";
    }
}

When we use this class to load the users from the same JSON, the code runs (unlike the Jackson variant mentioned in my other answer). But you\'ll see a warning in your logging output:

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

So get rid of that, we can annotate the class with @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 + \"\'\'}\";
    }
}

Partially saving users

As before, you might want to add a calculated property to the user. You\'d want to ignore such a property when saving the data back to the database. To do this, you can annotate the property/getter/setter/field with @Exclude:

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+ \"\'}\"; }
}

Now when writing a user to the database, the value of getTag() will be ignored.

Using a different property name in the JSON than in the Java code

You can also specify what name a field/getter/setter from the Java code should get in the JSON in the database. To do this: annotate the field/getter/setter with @PropertyName().

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+ \"\'}\"; }
}

In general it\'s best to use the default mapping between Java<->JSON that the Firebase SDK uses. But @PropertyName may be needed when you have a pre-existing JSON structure that you can\'t otherwise map to Java classes.



回答3:

Because your wrong query path folder in root.this is example \"enter

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\"