Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Firebase generic parsing of maps to lists

I'm trying to set up a generic class to retrieve data from Firebase but I'm stuck on the parsing part.

override fun onDataChange(snapshot: DataSnapshot) {
    try {
        val data: T? = snapshot.getValue(dataType)
        onDataReadFromDatabase(data, d, snapshot, changeListener)
    } catch(e: Exception) {
        d.resumeWithException(e)
    }
} 

T is the type of my data and dataType is Class<T>. This is working fine for flat data structures but when there is a list as a child of T it fails with Expected a List while deserializing, but got a class java.util.HashMap.

An example of data structure that fails:

{
    "id": "xxx",
    "name": "test",
    "items": {
        "a": {"name": "itemA"},
        "b": {"name": "itemB"},
        "c": {"name": "itemC"},
        "d": {"name": "itemD"}
    }
}

With a model like this:

data class ItemList(val id: String, val name: String, val items: MutableList<Item>) {
    ...
}

I know there is a way to parse children by looping on them like it is said here but this is by knowing the class of the items.

What I would expect is a way to say to the Firebase parser: each time you need to convert a map to a list, use the function x.

like image 423
MHogge Avatar asked Apr 19 '18 07:04

MHogge


3 Answers

You Need to write a custom Deserializer and then loop it and get the values of the hasmap.

Custom Deserializer:-

public class UserDetailsDeserializer implements JsonDeserializer<AllUserDetailsKeyModel> {
  /*
    bebebejunskjd:{
      "email": "[email protected]",
          "mobileNum": "12345678",
          "password": "1234567",
          "username": "akhil"}*/
  @Override public AllUserDetailsKeyModel deserialize(JsonElement json, Type typeOfT,
      JsonDeserializationContext context) throws JsonParseException {

    final JsonObject jsonObject = json.getAsJsonObject();

    Gson gson = new Gson();

    Type AllUserDetailsResponseModel =
        new TypeToken<HashMap<String, AllUserDetailsResponseModel>>(){}.getType();

    HashMap<String, AllUserDetailsResponseModel> user =
        gson.fromJson(jsonObject, AllUserDetailsResponseModel);
    AllUserDetailsKeyModel result = new AllUserDetailsKeyModel();
    result.setResult(user);
    return result;
  }


}

The code in comments is my object model and u should replaceAllUserDetailsKeyModel with your model class and add this to the rest client like below:-

private Converter.Factory createGsonConverter() {
    GsonBuilder gsonBuilder = new GsonBuilder();
    gsonBuilder.registerTypeAdapter(AllUserDetailsKeyModel.class, new UserDetailsDeserializer());
    Gson gson = gsonBuilder.create();
    return GsonConverterFactory.create(gson);
  }

This the custom Convertor for Retrofit.

In your onResponse you just loop with hasmaps and get value by key and my model class looks like below:-

public class AllUserDetailsKeyModel {

  private Map<String, AllUserDetailsResponseModel> result;

  public Map<String, AllUserDetailsResponseModel> getResult() {
    return result;
  }

  public void setResult(Map<String, AllUserDetailsResponseModel> result) {
    this.result = result;
  }

}

probably you need to give a Type T where T is your data Type and my model consists only of a hashmap and getters and setters for that.

And finally set Custom Convertor to retrofit like below:- .addConverterFactory(createGsonConverter())

Let me know if you need more clarifications.

like image 65
bv akhil Avatar answered Oct 17 '22 17:10

bv akhil


Your Json actually doesn't contain a list, but a sub-object with fields "a", "b", etc. which in turn gets deserialized into a HashMap.

Try with a list as data:

{
    "id": "xxx",
    "name": "test",
    "items": [
        {"name": "itemA"},
        {"name": "itemB"},
        {"name": "itemC"},
        {"name": "itemD"}
    ]
}

Or else change your data class field to

val items: MutableMap<String, Item>

Where the map keys are "a", "b", etc.

like image 2
Robert Jack Will Avatar answered Oct 17 '22 18:10

Robert Jack Will


items is not a JSON array but a JSON object and for a JSON object, the map is used. Because in a map, there is a "Key - Value" pair and hence each pair is distinguished from each other by a key.

So, use:

data class ItemList(val id: String, val name: String, val items: MutableMap<String, Item>) {
    ...
}

This will definitely solve this problem. Let me know if you need more clarifications.

like image 1
Malwinder Singh Avatar answered Oct 17 '22 19:10

Malwinder Singh