How to Deserialise a Subclass in Firebase Using Getvalue(Subclass.Class)

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 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.

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:

all get methods in Users class

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



Leave a reply



Submit