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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With