¿Por qué obtengo "Falló al rebotar al escribir" cuando convertí JSON de Firebase en objetos Java?

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

Tengo la siguiente estructura de 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 tengo este error:

Excepción en el subproceso "FirebaseEventTarget" com.firebase.client.FirebaseException: Error al saltar al tipo

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

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

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

Cargando usuarios completos

La forma más sencilla de cargar los usuarios de Firebase a Android es si creamos una clase Java que imita completamente 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 de JavaBean . Cada propiedad JSON se mapea por un campo en la clase User y tenemos un método getter público para cada campo. Al garantizar que todas las propiedades se asignan con el mismo nombre, nos aseguramos de que Jackson puede asignarlas automáticamente.

También puede controlar manualmente la asignación poniendo anotaciones de 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 sólo se preocupan por el nombre del usuario y manejarlo en su código Java. stackId el stackId y veamos lo que sucede:

 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 listener como antes y ejecutamos el programa, 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 "no devolver" 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 lanza una excepción. Por suerte hay una anotación que podemos usar para decirle que ignore las propiedades específicas de JSON cuando se asigna a nuestra clase de User :

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

Si no ejecutamos el código con nuestro oyente de nuevo, Jackson sabrá que puede ignorar stackId en el JSON y que será capaz de deserializar el JSON en un objeto de usuario de nuevo.

Dado que la adición de propiedades al JSON es una práctica común en las aplicaciones de Firebase, puede resultar más conveniente simplemente decirle a Jackson que ignore todas las propiedades que no tienen una asignación en la clase Java:

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

Ahora si agregamos propiedades al JSON más tarde, el código Java seguirá siendo capaz de cargar el User . Sólo recuerde 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.

Guardar parcialmente los usuarios

Una de las razones por las que es bueno tener una clase de Java personalizada, es que podemos agregar 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 vamos a leer a los usuarios de Firebase y volver a escribirlos 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" } } 

Eso no es lo mismo que el JSON fuente, ya que Jackson reconoce el nuevo método getDisplayName() como un getter de JavaBean y, por tanto, agrega una propiedad displayName al JSON que genera. JsonIgnore este problema agregando una anotación de JsonIgnore a getDisplayName() .

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

Al serializar un objeto de usuario, Jackson ahora ignorará el método getDisplayName() y el JSON que escribiremos será el mismo que lo 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 reciente proporciona un conjunto mínimo de anotaciones personalizadas para permitir el control sobre las necesidades de personalización más comunes, al tiempo que tiene un impacto mínimo en el tamaño JAR / APK resultante.

La respuesta que le di anteriormente es todavía válida si usted está:

  • Utilizar los SDK de Firebase 2.x
  • Utilizar los SDK de Firebase 9.0 o superior, pero [utilizar Jackson para serializar / deserializar Java <-> JSON] (TODO: link to answer).

Estructura de datos

Comenzaremos con esta estructura JSON en nuestra base de datos 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 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 utilizamos esta clase para cargar a los usuarios del mismo JSON, el código se ejecuta (a diferencia de la variante de Jackson mencionada en mi otra respuesta). Pero verás una advertencia en tu salida de registro:

ADVERTENCIA: No setter / field para stackId encontrado en la clase Anotaciones $ PartialUser

Así que deshacerse 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 + "''}"; } } 

Guardar parcialmente los usuarios

Como antes, es posible que desee agregar una propiedad calculada al usuario. Desea ignorar dicha propiedad al guardar los datos 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, el valor de getTag() será ignorado.

Utilizar un nombre de propiedad diferente en JSON que en el código 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, lo mejor es utilizar el mapeo predeterminado entre Java <-> JSON que utiliza el SDK de Firebase. Pero @PropertyName puede ser necesario cuando tiene una estructura JSON preexistente que no puede asignar a clases Java.

Debido a que su carpeta de ruta de consulta equivocada en root.this es ejemplo Introduzca aquí la descripción de la imagen

 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" 
  • ¿Cómo usar la nueva API de acceso a tarjetas SD de Lollipop en dispositivos que ejecutan KITKAT?
  • ¿Cómo AOT hace la compilación Andorid mejor?
  • ActionBar.setNavigationMode (ActionBar.NAVIGATION_MODE_TABS) obsoleto
  • Android: Constructor es ambiguo cuando paso Null, pero no cuando paso una variable asignada a Null
  • Java comprobar si la fecha es el primer domingo del mes
  • Uso de android.jar en el proyecto Java - RuntimeException Stub?
  • Seguridad de los recursos de la cadena
  • ¿Cómo obtienes MCC y MNC del teléfono en Android?
  • Cómo crear gradiente de fondo animado como Instagram app?
  • HTTPS con resultado de autenticación básico en Unauthorized
  • Agregar nueva columna en el proveedor de contenido de contactos predeterminado de android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.