Почему я получаю "Не удалось перейти к типу", когда я превращаю JSON из Firebase в объекты Java?
[Раскрытие информации: я инженер Firebase. Этот вопрос предназначен для справки, чтобы ответить на множество вопросов за один раз.]
У меня есть следующая структура JSON в моей базе данных 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
}
}
}
Я читаю это со следующим кодом:
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) { }
});
Но я получаю эту ошибку:
Исключение в потоке "FirebaseEventTarget" com.firebase.client.Исключение FirebaseException: не удалось перейти к типу
Как я могу преобразовать моих пользователей в объекты Java?
Переведено автоматически
Ответ 1
Firebase использует Jackson, чтобы разрешить сериализацию объектов Java в JSON и десериализацию JSON обратно в объекты Java. Вы можете найти больше о Jackson на веб-сайте Jackson и на этой странице об аннотациях Jackson.
В остальной части этого ответа мы покажем несколько распространенных способов использования Jackson с Firebase.
Загрузка полных пользователей
Самый простой способ загрузить пользователей из Firebase в Android - это создать класс Java, который полностью имитирует свойства в 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+"\’}”; }
}
Мы можем использовать этот класс в прослушивателе:
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) { }
});
Вы можете заметить, что класс User следует шаблону свойств JavaBean. Каждое свойство JSON сопоставляется полю в классе User, и у нас есть общедоступный метод получения для каждого поля. Гарантируя, что все свойства сопоставлены с точно таким же именем, мы гарантируем, что 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 + "\’}”;
}
}
Если мы теперь подключим тот же прослушиватель, что и раньше, и запустим программу, она выдаст исключение:
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)
“Не удалось отменить тип” указывает, что Джексону не удалось десериализовать 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)
Джексон нашел свойство stackId
в JSON и не знает, что с ним делать, поэтому выдает исключение. К счастью, есть аннотация, которую мы можем использовать, чтобы указать ему игнорировать определенные свойства JSON при сопоставлении его с нашим User
классом:
@JsonIgnoreProperties({ "stackId" })
private static class User {
...
}
Если мы снова не запустим код с нашим слушателем, Jackson узнает, что он может игнорировать stackId
в JSON, и он сможет снова десериализовать JSON в пользовательский объект.
Поскольку добавление свойств в JSON является такой распространенной практикой в приложениях Firebase, вам может показаться более удобным просто сказать Джексону игнорировать все свойства, которые не имеют сопоставления в классе Java:
@JsonIgnoreProperties(ignoreUnknown=true)
private static class User {
...
}
Теперь, если мы добавим свойства к JSON позже, код Java все равно сможет загружать User
s. Просто имейте в виду, что пользовательские объекты не будут содержать всю информацию, которая присутствовала в 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() + "'}";
}
}
Теперь давайте прочитаем пользователей из 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) { }
});
JSON в copiedusers
узле выглядит следующим образом:
"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, потому что Джексон распознает новый getDisplayName()
метод как средство получения JavaBean и, таким образом, добавил displayName
свойство к JSON, которое он выводит. Мы решаем эту проблему, добавляя JsonIgnore
аннотацию к getDisplayName()
.
@JsonIgnore
public String getDisplayName() {
return getName() + "(" + getHandle() + ")";
}
При сериализации пользовательского объекта Jackson теперь игнорирует метод getDisplayName()
, и JSON, который мы выписываем, будет таким же, как и тот, который мы получили.
Ответ 2
Версии Firebase SDK 9.x (и выше) для Android / Java перестали включать Jackson для сериализации / десериализации Java<-> JSON. Вместо этого более новый SDK предоставляет минимальный набор пользовательских аннотаций, позволяющий контролировать наиболее распространенные потребности в настройке, оказывая минимальное влияние на результирующий размер JAR / APK.
Мой первоначальный ответ все еще действителен, если вы:
- Использование Firebase 2.x SDKS
- Используя Firebase 9.0 или более поздние SDK, но используйте Jackson для сериализации / десериализации Java <-> JSON.
Остальная часть этого ответа описывает, как обрабатывать сценарии сериализации / десериализации в Firebase SDK 9.0 или выше.
Структура данных
Мы начнем с этой структуры JSON в нашей базе данных 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
}
}
Загрузка полных пользователей
По сути, мы можем загрузить каждого пользователя из этого 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 "User{handle='"+handle+"', name='"+name+"', stackId="+stackId+ "'}"; }
}
Если мы объявляем поля общедоступными, нам даже не нужны геттеры:
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 "User{handle='" + handle + "', NAME='" + name + "''}";
}
}
Когда мы используем этот класс для загрузки пользователей из того же JSON, код выполняется (в отличие от варианта Jackson, упомянутого в моем другом ответе). Но вы увидите предупреждение в выходных данных вашего журнала.:
ПРЕДУПРЕЖДЕНИЕ: в аннотациях класса $ 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 "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
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"