How to deserialise a subclass in Firebase using getValue(Subclass.class)
Firebaser here
This is a known bug in some versions of the Firebase Database SDK for Android: our serializer/deserializer only considers properties/fields on the declared class.
Serialization of inherited properties from the base class, is missing in the in releases 9.0 to 9.6 (iirc) of the Firebase Database SDK for Android. It was added back in versions since then.
Workaround
In the meantime you can use Jackson (which the Firebase 2.x SDKs used under the hood) to make the inheritance model work.
Update: here's a snippet of how you can read from JSON into your TestChild
:
public class TestParent {
protected String parentAttribute;
public String getParentAttribute() {
return parentAttribute;
}
}
public class TestChild extends TestParent {
private String childAttribute;
public String getChildAttribute() {
return childAttribute;
}
}
You'll note that I made getParentAttribute()
public, because only public fields/getters are considered. With that change, this JSON:
{
"childAttribute" : "child",
"parentAttribute" : "parent"
}
Becomes readable with:
ObjectMapper mapper = new ObjectMapper();
GenericTypeIndicator<Map<String,Object>> indicator = new GenericTypeIndicator<Map<String, Object>>() {};
TestChild value = mapper.convertValue(dataSnapshot.getValue(indicator), TestChild.class);
The GenericTypeIndicator
is a bit weird, but luckily it's a magic incantation that can be copy/pasted.
Firebase database android getter for property
firebaser here
The JSON serializer/deserializer in the Firebase Android SDK builds a list of candidate properties for a class based on its public fields and its JavaBean-style pseudo-properties that have a getter and a setter.
We've discussed whether the latter should be based solely on a getter for serializing to JSON and a setter for deserializing from JSON. But at this moment that would be a breaking change to the behavior, which we're not willing to do.
If you'd like broader support over the serialization/deserialization you can always use Jackson to do so. See my answer here: How to deserialise a subclass in Firebase using getValue(Subclass.class)
Why do I get Failed to bounce to type when I turn JSON from Firebase into Java objects?
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 User
s. 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.
How to use auto-value with firebase 9.2 in Android
I'm not sure this is possible with the default Firebase data mapper, but there is a possible workaround. First let's explain the errors you're seeing:
com.google.firebase.database.DatabaseException: No properties to serialize found on class com.example.app.model.Office
The Firebase mapper looks for either public
fields or fields named with the getFoo
/setFoo
pattern. So on your class the mapper does not see any properties.
java.lang.InstantiationException: Can't instantiate abstract class com.example.app.model.Office
This is the one I think you'll have trouble getting around. In order for the deserialization to work your class needs to have a public, no-argument constructor that the mapper can call via reflection (newInstance()
). As far as I know this is not how AutoValue works.
But don't lose hope!. According to this github issue there is a way to make Jackson and AutoValue compatible using the @JsonCreator
annotation. So you'll need to use both Jackson and Firebase to get the job done here.
Serializing:
// Convert to a Map<String,Object> using Jackson and then pass that to Firebase
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> map = mapper.convertValue(office, Map.class);
databaseReference.setValue(map);
Deserializing:
// Use Firebase to convert to a Map<String,Object>
GenericTypeIndicator<Map<String,Object>> t = new GenericTypeIndicator<Map<String,Object>>() {};
Map<String,Object> map = dataSnap.getValue(t);
// Use Jackson to convert from a Map to an Office object
ObjectMapper mapper = new ObjectMapper();
Office pojo = mapper.convertValue(map, Office.class);
Android, Kotlin: Creating a GenericTypeIndicator from reflection
I got the detection of the Map variable working by doing an isAssignableFrom, and creating an object for GenericTypeIndicator.
So the only thing that needed to be changed is the when
part in the code to this:
when {
field.type == Translation::class.java ->
field.set(obj, Translation(it.getValue(String::class.java)!!))
Map::class.java.isAssignableFrom(field.type) ->
field.set(obj, it.getValue(object: GenericTypeIndicator<HashMap<String, Any>>(){}))
else -> field.set(obj, it.getValue(field.type))
}
Firebase only accepts String as keys, so we can just make that one fixed, It isn't fond of using * notation for the value though, but we can just make it Any and it seems to work fine.
I hope this helps other people who might ever come across this problem x)
Firebase serialization names
The Firebase SDK uses the annotation it finds for the property whenever it gets or sets its value. That means you need to consider how Firebase gets/sets the value, and annotate each place it looks.
Since you're declaring a getter
method, Firebase will use that to get the value of the property. It will use the field for setting the value. So the annotation needs to be on both:
public class Pojo {
@PropertyName("Guid")
public String guid;
@PropertyName("Name")
public String name;
@PropertyName("Guid")
public String getPojoGuid() {
return guid;
}
@PropertyName("Guid")
public void setPojoGuid(String guid) {
this.guid = guid;
}
}
If you'd have getters and setters, the annotation would need to be on those, but not on the fields anymore:
public class Pojo {
private String guid;
private String name;
@PropertyName("Guid")
public String getPojoGuid() {
return guid;
}
@PropertyName("Guid")
public void setPojoGuid(String value) {
guid = value;
}
@PropertyName("Name")
public void setPojoGuid(String guid) {
this.guid = guid;
}
@PropertyName("Name")
public void setPojoGuid(String value) {
name = value;
}
}
Firebase simple chat client not retrieving values
Just to formalize an answer so it can be useful to others searching this:
Using DataSnapshot.getValue(YourModel.Class)
you can get a parsed object with the same structure that is defined in Firebase. Just make sure YourModel.class has that structure.
When serializing the returned object to a POJO, it must contain exactly the same properties as the Firebase object.
How to represent nested data in Firebase class
The JavaBean class to represent this structure is:
public static class DinosaurFacts {
String name;
String work;
Dimensions dimensions;
public String getName() {
return name;
}
public String getWork() {
return work;
}
public Dimensions getDimensions() {
return dimensions;
}
public class Dimensions {
double height;
long weight;
double length;
public double getHeight() {
return height;
}
public long getWeight() {
return weight;
}
public double getLength() {
return length;
}
}
}
Then you can read a dino with:
DinosaurFacts dino = dinoSnapshot.getValue(DinosaurFacts.class);
And access the dimensions with for example:
dino.getDimensions().getWeight();
Found conflicting getters for name
Kotlin compiler generates getters for read-only variables (getters and setters for read-write variables) defined in a class. Because you have a variable named fullname
and custom getfullname
and setfullname
you will have two getters and two setters for fullname
variable.
Here are all get
methods you will see from Java code:
And looks like snapshot.getValue(Users::class.java)
does not know which getters
and setters
to use.
Simples solution should be removing custom methods:
class Users {
var username:String = " "
var Fullname:String = " "
var image:String = " "
var id:String = " "
constructor()
constructor(username: String, fullname: String, image: String, id: String) {
this.username = username
this.Fullname = fullname
this.image = image
this.id = id
}
}
Otherwise, update variables declaration with private
access modifier:
class Users {
private var username:String = " "
private var Fullname:String = " "
private var image:String = " "
private var id:String = " "
constructor()
constructor(username: String, fullname: String, image: String, id: String) {
this.username = username
this.Fullname = fullname
this.image = image
this.id = id
}
// Below are your custom getters and setters
}
Related Topics
Maximum Java Heap Size of a 32-Bit Jvm on a 64-Bit Os
Deserialize a List≪T≫ Object With Gson
Android: How to Enable/Disable Wifi or Internet Connection Programmatically
Any Good Orm Tools for Android Development
Foreign Key Constraints in Android Using SQLite? on Delete Cascade
How to Get the Ip of the Computer on Linux Through Java
Practical Limitations of Jvm Memory and CPU Usage
Create Jfreechart-1.5.3 Jar from Source Code
Converting a String to Hexadecimal in Java
Match Multiline Text Using Regular Expression
How to Retrieve a File from a Server Via Sftp
How to Parse a Dynamic JSON Key in a Nested JSON Result
How to Check If an App Running on Android
Android Download Binary File Problems
Noclassdeffounderror: Android.Support.V7.Internal.View.Menu.Menubuilder
Obtaining the Thread Id for Java Threads in Linux
Why Does Killing Jvm Also Terminates Its Child Process If Waitfor Has Been Used