¿Por qué me aparece el mensaje “Fallé al rebotar para escribir” cuando cambio JSON de Firebase a objetos Java?

[Divulgación: soy un ingeniero en Firebase. Esta pregunta pretende ser una pregunta de referencia para responder muchas preguntas de una vez.]

Tengo la siguiente estructura JSON en mi base de datos de Firebase:

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

Estoy leyendo esto con el siguiente código:

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

Pero me sale este error:

Excepción en el hilo “FirebaseEventTarget” com.firebase.client.FirebaseException: no se pudo revertir al tipo

¿Cómo puedo leer a mis usuarios en objetos Java?

Firebase utiliza Jackson para permitir la serialización de objetos Java en JSON y la deserialización de JSON en objetos Java. Puede encontrar más acerca de Jackson en el sitio web de Jackson y en esta página sobre las anotaciones de Jackson .

En el rest de esta respuesta, mostraremos algunas formas comunes de usar Jackson con Firebase.

Cargando usuarios completos

La forma más sencilla de cargar a los usuarios de Firebase en Android es si creamos una clase Java que imita por completo las propiedades en el 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+"\'}”; } } 

Podemos usar esta clase en un oyente:

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

Puede observar que la clase User sigue el patrón de propiedad JavaBean . Cada propiedad JSON se mapea por un campo en la clase User y tenemos un método public getter para cada campo. Al asegurarnos de que todas las propiedades estén mapeadas con el mismo nombre, nos aseguramos de que Jackson pueda mapearlas automáticamente.

También puede controlar manualmente la asignación colocando anotaciones Jackson en su clase Java y sus campos y métodos. @JsonIgnore las dos anotaciones más comunes ( @JsonIgnore y @JsonIgnoreProperties ) a continuación.

Carga parcial de usuarios

Digamos que solo le importa el nombre del usuario y maneja su código Java. Vamos a eliminar el stackId y ver qué pasa:

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

Si ahora adjuntamos el mismo oyente que antes y ejecutamos el progtwig, lanzará una excepción:

 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) 

El “tipo de error de eliminación de rebote” indica que Jackson no pudo deserializar el JSON en un objeto Usuario. En la excepción anidada, nos dice por qué:

 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 encontró una propiedad stackId en el JSON y no sabe qué hacer con ella, por lo que arroja una excepción. Afortunadamente, hay una anotación que podemos usar para indicarle que ignore las propiedades específicas de JSON al mapearlo a nuestra clase de User :

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

Si no ejecutamos el código nuevamente con nuestro oyente, Jackson sabrá que puede ignorar stackId en el JSON y podrá deserializar el JSON en un objeto User nuevamente.

Dado que agregar propiedades al JSON es una práctica tan común en las aplicaciones de Firebase, quizás le resulte más conveniente simplemente decirle a Jackson que ignore todas las propiedades que no tienen un mapeo en la clase Java:

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

Ahora si agregamos propiedades a JSON más tarde, el código de Java aún podrá cargar los User ‘s. Solo tenga en cuenta que los objetos de Usuario no contendrán toda la información que estaba presente en el JSON, así que tenga cuidado al volver a escribirlos en Firebase.

Parcialmente ahorrando usuarios

Una de las razones por las que es bueno tener una clase Java personalizada es que podemos agregarle métodos de conveniencia. Digamos que agregamos un método de conveniencia que obtiene el nombre para mostrar para un usuario:

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

Ahora leamos a los usuarios de Firebase y escríbalos de nuevo en una nueva ubicación:

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

El JSON en el nodo copiedusers ve así:

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

No es lo mismo que el JSON de origen, porque Jackson reconoce el nuevo método getDisplayName() como un get de JavaBean y, por lo tanto, agregó una propiedad displayName al JSON que genera. JsonIgnore este problema agregando una anotación JsonIgnore a getDisplayName() .

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

Al serializar un objeto User, Jackson ahora ignorará el método getDisplayName() y el JSON que escribamos será el mismo que el que obtuvimos.

Las versiones 9.x (y superiores) del SDK de Firebase para Android / Java dejaron de incluir a Jackson para serializar / deserializar Java <-> JSON. El SDK más nuevo en su lugar proporciona un conjunto mínimo de anotaciones personalizadas para permitir el control de las necesidades de personalización más comunes, a la vez que tiene un impacto mínimo en el tamaño de JAR / APK resultante.


Mi respuesta original sigue siendo válida si eres:

  • Uso de los SDK de Firebase 2.x
  • Uso de Firebase 9.0 o superior SDK, pero use Jackson para serializar / deserializar Java <-> JSON .

El rest de esta respuesta cubre cómo manejar escenarios de serialización / deserialización en Firebase SDK 9.0 o superior.


Estructura de datos

Comenzaremos con esta estructura JSON en nuestra base de datos de Firebase:

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

Cargando usuarios completos

En su forma más básica, podemos cargar cada usuario de este JSON en la siguiente clase de 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+ "'}"; } } 

Si declaramos que los campos son públicos, ni siquiera necesitamos los getters:

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

Carga parcial de usuarios

También podemos cargar parcialmente a un usuario, por ejemplo con:

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

Cuando usamos esta clase para cargar a los usuarios desde el mismo JSON, el código se ejecuta (a diferencia de la variante de Jackson mencionada en mi otra respuesta). Pero verá una advertencia en su salida de registro:

ADVERTENCIA: Ningún setter / field para stackId encontrado en la clase Anotaciones $ PartialUser

Así que deshazte de eso, podemos anotar la clase con @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 + "''}"; } } 

Parcialmente ahorrando usuarios

Como antes, es posible que desee agregar una propiedad calculada para el usuario. Desearía ignorar tal propiedad al guardar los datos nuevamente en la base de datos. Para hacer esto, puede anotar la propiedad / getter / setter / field con @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+ "'}"; } } 

Ahora, al escribir un usuario en la base de datos, se getTag() el valor de getTag() .

Usar un nombre de propiedad diferente en el JSON que en el código de Java

También puede especificar qué nombre debe obtener un campo / getter / setter del código Java en el JSON de la base de datos. Para hacer esto: anote el campo / getter / setter con @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+ "'}"; } } 

En general, es mejor usar la asignación predeterminada entre Java <-> JSON que usa Firebase SDK. Pero @PropertyName puede ser necesario cuando tienes una estructura JSON preexistente que no puedes asignar a las clases de Java.

Porque la carpeta de ruta de consulta incorrecta en root.this es un ejemplo enter image description here

 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"