I wonder to know how Firebase serialize/deserialize POJO object to/from json, does it use Jackson or Gson or any similar library else.
I have trouble about naming convention with Firebase. My model some like this:
class Data {
private String someFieldName;
private String anotherFieldName;
public Data() {}
public void setSomeFieldName(String) {...}
public String getSomeFieldName(String) {...}
public void setAnotherFieldName(String) {...}
public String getAnotherFieldName() {...}
}
And the expected result in Firebase should be:
{
"some_field_name" : "...",
"another_field_name" : "..."
}
with Gson I can use FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES for my purpose, as in Gson doc:
Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":
someFieldName ---> some_field_name
_someFieldName ---> _some_field_name
aStringField ---> a_string_field
aURL ---> a_u_r_l
How can I convert my POJO object to "Firebase value" with specific naming convention and vice versa, or there are any way to customize the serialize/deserialize process?
Thanks!
When reading the data back from the Firebase database you can use the #PropertyName annotation to mark a field to be renamed when being serialized/deserialized, like so:
#IgnoreExtraProperties
class Data {
#PropertyName("some_field_name")
public String someFieldName
#PropertyName("another_field_name")
private String anotherFieldName;
public Data() {}
}
Make sure that your field is public and not private or else the annotation will not work (I also believe that Firebase uses Jackson to handle the object mapping under the hood, but don't think you can actually customize HOW it uses it).
Personally I prefer keeping explicit control over the serialization/deserialization process, and not relying on specific framework and/or annotations.
Your Data class can be simply modified like this :
class Data {
private String someFieldName;
private String anotherFieldName;
public Data() {}
public Data(Map<String, Object> map) {
someFieldName = (String) map.get("some_field_name") ;
anotherFieldName = (String) map.get("another_field_name") ;
}
public Map<String, Object> toMap() {
Map<String, Object> map = new HashMap<>();
map.put("some_field_name", someFieldName);
map.put("another_field_name", anotherFieldName);
return map ;
}
}
For writing value to firebase, simply do :
dbref.setValue(data.toMap());
For reading :
Map<String, Object> map = (Map<String, Object>) dataSnapshot.getValue();
data = new Data(map);
They are some advantages with this solution :
No assumption is made on underlying json framework
No need to use annotations
You can even further decouple you Object model from your Json model by externalizing the methods toMap() and constructor to a DataMapper (snippet hereunder)
public static Data fromMap(Map<String, Object> map) {
String someFieldName = (String) map.get("some_field_name") ;
String anotherFieldName = (String) map.get("another_field_name") ;
return new Data(someFieldName, anotherFieldName);
}
public static Map<String, Object> toMap(Data data) {
Map<String, Object> map = new HashMap<>();
map.put("some_field_name", data.getSomeFieldName());
map.put("another_field_name", data.getAnotherFieldName());
return map ;
}
Related
Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?
public class Foo {
public String bar;
public String baz;
public Foo( String bar, String baz ) {
this.bar = bar;
this.baz = baz;
}
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));
The string jsonRequest can be:
{ "bar":"bar", "baz":"baz" } (correct)
{ "baz":"baz", "bar":"bar" } (wrong sequence)
You'd need to create a custom JSON serializer.
E.g.
public class FooJsonSerializer implements JsonSerializer<Foo> {
#Override
public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("bar", context.serialize(foo.getBar());
object.add("baz", context.serialize(foo.getBaz());
// ...
return object;
}
}
and use it as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...
This maintains the order as you've specified in the serializer.
See also:
Gson User Guide - Custom serializers and deserializers
If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with #JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.
And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.
Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.
I had the same problem and it was solved by literal order of properties' names in the class.
The example in the question will always return the following jsonRequest:
{ "bar":"bar", "baz":"baz" }
In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:
public class Foo {
public String f1_baz;
public String f2_bar;
public Foo ( String f1_baz, String f2_bar ) {
this.f1_baz = f1_baz;
this.f2_bar = f2_bar;
}
}
jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }
Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:
private void standardizeFormat(File dir) throws IOException {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
String path = child.getPath();
JsonReader jsonReader = new JsonReader(new FileReader(path));
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
Object data = gson.fromJson(jsonReader, Object.class);
JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
jsonWriter.setIndent(" ");
gson.toJson(data, Object.class, jsonWriter);
jsonWriter.close();
}
}
}
private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
#Override
public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
TreeSet sorted = Sets.newTreeSet(foo.keySet());
for (Object key : sorted) {
object.add((String) key, context.serialize(foo.get(key)));
}
return object;
}
}
It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...
Seems like Gson.toJson(Object object) generates JSON code with randomly spread fields of the object. Is there way to fix fields order somehow?
public class Foo {
public String bar;
public String baz;
public Foo( String bar, String baz ) {
this.bar = bar;
this.baz = baz;
}
}
Gson gson = new Gson();
String jsonRequest = gson.toJson(new Foo("bar","baz"));
The string jsonRequest can be:
{ "bar":"bar", "baz":"baz" } (correct)
{ "baz":"baz", "bar":"bar" } (wrong sequence)
You'd need to create a custom JSON serializer.
E.g.
public class FooJsonSerializer implements JsonSerializer<Foo> {
#Override
public JsonElement serialize(Foo foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.add("bar", context.serialize(foo.getBar());
object.add("baz", context.serialize(foo.getBaz());
// ...
return object;
}
}
and use it as follows:
Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class, new FooJsonSerializer()).create();
String json = gson.toJson(foo);
// ...
This maintains the order as you've specified in the serializer.
See also:
Gson User Guide - Custom serializers and deserializers
If GSON doesn't support definition of field order, there are other libraries that do. Jackson allows definining this with #JsonPropertyOrder, for example. Having to specify one's own custom serializer seems like awful lot of work to me.
And yes, I agree in that as per JSON specification, application should not expect specific ordering of fields.
Actually Gson.toJson(Object object) doesn't generate fields in random order. The order of resulted json depends on literal sequence of the fields' names.
I had the same problem and it was solved by literal order of properties' names in the class.
The example in the question will always return the following jsonRequest:
{ "bar":"bar", "baz":"baz" }
In order to have a specific order you should modify fields' names, ex: if you want baz to be first in order then comes bar:
public class Foo {
public String f1_baz;
public String f2_bar;
public Foo ( String f1_baz, String f2_bar ) {
this.f1_baz = f1_baz;
this.f2_bar = f2_bar;
}
}
jsonRequest will be { "f1_baz ":"baz", "f2_bar":"bar" }
Here's my solution for looping over json text files in a given directory and writing over the top of them with sorted versions:
private void standardizeFormat(File dir) throws IOException {
File[] directoryListing = dir.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
String path = child.getPath();
JsonReader jsonReader = new JsonReader(new FileReader(path));
Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(LinkedTreeMap.class, new SortedJsonSerializer()).create();
Object data = gson.fromJson(jsonReader, Object.class);
JsonWriter jsonWriter = new JsonWriter(new FileWriter(path));
jsonWriter.setIndent(" ");
gson.toJson(data, Object.class, jsonWriter);
jsonWriter.close();
}
}
}
private class SortedJsonSerializer implements JsonSerializer<LinkedTreeMap> {
#Override
public JsonElement serialize(LinkedTreeMap foo, Type type, JsonSerializationContext context) {
JsonObject object = new JsonObject();
TreeSet sorted = Sets.newTreeSet(foo.keySet());
for (Object key : sorted) {
object.add((String) key, context.serialize(foo.get(key)));
}
return object;
}
}
It's pretty hacky because it depends on the fact that Gson uses LinkedTreeMap when the Type is simply Object. This is an implementation details that is probably not guaranteed. Anyway, it's good enough for my short-lived purposes...
My android application is crashing on start up, because of a small error in my back-end side which is in firebase. A document in my firestore database has a field in which an Image object (id and url) is expected but for some reason it's stored as a long in the database.
I get this error:
java.lang.RuntimeException: Could not deserialize object. Can't convert object of type java.lang.Long to type com..data.entity.gps.tracker.ImageEntity
My ImageEntity class:
public class ImageEntity {
private int id;
private String image_id;
public String getImage_id() {
return image_id;
}
public void setImage_id(String image_id) {
this.image_id = image_id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
I can't just exclude the cover ImageEntity object, it's an integral part of my app, how can I add a validation step to check if the cover object is a valid object and not a long?
This is where the crash happens:
public List<Model> mapList(QuerySnapshot querySnapshot) {
List<Model> list = new ArrayList<>();
for (QueryDocumentSnapshot queryDocumentSnapshot : querySnapshot) {
Model model = map(queryDocumentSnapshot);
long id = ((Release) model).getId();
if (!addedReleasesIDs.contains(id)) {
list.add(model);
addedReleasesIDs.add(id);
}
}
return list;
}
public Model map(QueryDocumentSnapshot queryDocumentSnapshot) {
// Auto serialization
Entity entity = queryDocumentSnapshot.toObject(getEntityClass()); // CRASH HERE
return map(entity);
}
If you need to take control over the deserializaition of a document snapshot, you should access its fields as a Map<String, Object> using getData() instead of using the Firebase SDKs internal mapper.
Map<String, Object> data = (Map<String, Object>) queryDocumentSnapshot.getData();
With this, you can now get() the individual fields by name out of the data map, check their types, and convert them to whatever other type you want for your model object.
This is also faster in terms of performance than using toObject because it doesn't have to use Java reflection.
I'm new to Firebase, and I've been really enjoying it so far. I'm running into a problem; I'm using the FirebaseListAdapter similar to the tutorial outline here: https://github.com/firebase/AndroidChat
To use the FirebaseListAdapter, I need to use data model objects (to get the automatic binding to work nicely). The problem is I also want to keep a timestamp value with that model object, and I want to get the timestamp from the Firebase server.
What I have currently that is NOT working is a class DataModelObject (similar to com.firebase.androidchat.Chat in the demo example) with a constructor like :
DataModelObject(String data1, String data2, Map enQTimeStamp)
which I then try to use like this:
DataModelObject dmo = new DataModelObject ("foo", "bar", ServerValue.TIMESTAMP);
myFirebaseRef.push().setValue(dmo);
This causes a JsonMappingException when I try to run that code. I found a code snippet here :
https://www.firebase.com/blog/2015-02-11-firebase-unique-identifiers.html
But it's worthwhile to note that on line 4 of the Android code example, that will cause a compile time error (as he is trying to put ServerValue.TIMESTAMP into a Map, and TIMESTAMP is a Map itself)
What is the right way to do this and maintain compatibility with FirebaseListAdapter?
This sounds similar to this question: When making a POJO in Firebase, can you use ServerValue.TIMESTAMP?
When creating POJOs used to store/retrieve data apart from the default empty constructor I usually use a constructor similar to this:
Param param1;
Param param2;
HashMap<String, Object> timestampCreated;
//required empty constructor
public DataObject(){}
public DataObject(Param param1, Param param2) {
this.param1 = param1;
this.param2 = param2;
HashMap<String, Object> timestampNow = new HashMap<>();
timestampNow.put("timestamp", ServerValue.TIMESTAMP);
this.timestampCreated = timestampNow;
}
Be sure to include a getter for the HashMap<> used to store the Timestamp:
public HashMap<String, Object> getTimestampCreated(){
return timestampCreated;
}
Then use the #Exclude annotation to create a getter that you can use in your code to get the value of the timestamp if you need it. The #Exclude annotation will cause Firebase to ignore this getter and not look for a corresponding property
#Exclude
public long getTimestampCreatedLong(){
return (long)timestampCreated.get("timestamp");
}
Here's how I do it
//member variable
Object createdTimestamp;
public YourConstructor(){
createdTimestamp = ServerValue.TIMESTAMP
}
#Exclude
public long getCreatedTimestampLong(){
return (long)createdTimestamp;
}
Your db object should include these:
public class FirebaseDbObject {
private final Object timestamp = ServerValue.TIMESTAMP;
//........
//........
Object getTimestamp() {
return timestamp;
}
#Exclude
public long timestamp() {
return (long) timestamp;
}
}
This will add an extra field called "timestamp" to your object.
Edit: The answer posted by MobileMon is not fully correct as it does not have getter method. This is the complete and correct answer.
Kotlin provides an easy way to achieve this by data classes. You can create it like
data class FirebaseRequestModel(
var start_time: Any = ServerValue.TIMESTAMP,
var stop_time: Long = 0,
var total_time: Long = 0,
)
and use it directly by
val firebaseModel = FirebaseRequestModel()
firebaseRef.setValue(firebaseModel)
This will get default values from data class.
Or even you can initiate your own values by
val firebaseModel = FirebaseRequestModel(ServerValue.TIMESTAMP, 2134, 0)
firebaseRef.setValue(firebaseModel)
Similar to Urgurcan's answer, but a bit cleaner so the caller doesn't have trouble guessing between getTimestamp vs timestamp.
public class FirebaseDbObject {
private Object timestamp = ServerValue.TIMESTAMP;
//........
//........
#PropertyName("timestamp")
Object getRawTimestamp() {
return timestamp;
}
#Exclude
public long getTimestamp() {
return (long) timestamp;
}
}
You can do it:
public class MyTimeStamp {
private Object timestamp;
public MyTimeStamp() {
}
public Object getTimestamp() {
return timestamp;
}
public void setTimestamp(Object timestamp) {
this.timestamp = timestamp;
}
}
And so:
public static void start(Context context) {
MyTimeStamp timeStamp = new MyTimeStamp();
timeStamp.setTimestamp(ServerValue.TIMESTAMP);
Log.d(TAG, "start: ", timeStamp.getTimestamp().toString());
}
I have been using GSON library to parse all the json string and get a JSON object.
But now I need to parse is like this:
{
"status":1,
"info":[
{
"\u5a31\u4e50":"\u51b7\u76d8,\u9ad8\u811a\u676f,\u6211\u7684\u7cd6\u679c\u5c4b,\u670d\u52a1\u4e1a\u6d88\u8d39\u52b5"
},
{
"\u7f8e\u5986":"\u4e2a\u62a4|\u5316\u5986#\u9762\u90e8\u62a4\u7406,\u4e2a\u4eba\u536b\u751f,\u8eab\u4f53\u62a4\u7406,\u9999\u6c34\u9999\u6c1b,\u6c90\u6d74|\u7f8e\u53d1\u7528\u54c1,\u5f69\u5986,\u7cbe\u6cb9SPA,\u773c\u90e8\u62a4\u7406,\u78e8\u7802\u53bb"
},
{
"\u8863\u670d":"\u670d|\u9970|\u978b|\u5e3d#\u670d\u88c5,\u978b\u9774,\u5185\u8863,\u914d\u9970,\u536b\u8863,\u4f11\u95f2\u88e4,T\u6064,\u88d9\u5b50,\u886c\u886b,\u9488\u7ec7\u886b,\u5a74\u5e7c\u513f\u670d\u9970"
}
],
"total":3
}
The key fields are dynamic, so I don't know how to write a model class to read this.
How would you like your model class to look?
status and total would probably be int, so that only leaves info.
As an experiment, just add a field Object info and see how Gson would set it to an ArrayList<LinkedHashMap<String, String>> -- ugly and hard to access by key, but all the data is there. Given that information, the fastest way to model a class would be:
class Something {
int status;
List<Map<String, String> info;
int total;
}
If you have control over how that JSON is generated, I suggest changing the structure of info from an array of objects [{a:b},{c:d},{e:f}] to just an object {a:b,c:d,e:f}. With this, you could just map it to a Map<String, String> with all the benefits like access by key, keys() and values():
class Something {
int status;
Map<String, String> info;
int total;
}
If you want the latter model class without changing the JSON format, you'll have to write a TypeAdapter (or JsonDeserializer if you're only interested in parsing JSON, not generating it from your model class).
Here's a JsonDeserializer hat would map your original info JSON property to a plain Map<String, String>.
class ArrayOfObjectsToMapDeserializer
implements JsonDeserializer<Map<String, String>> {
public Map<String, String> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
Map<String, String> result = new HashMap<String, String>();
JsonArray array = json.getAsJsonArray();
for (JsonElement element : array) {
JsonObject object = element.getAsJsonObject();
// This does not check if the objects only have one property, so JSON
// like [{a:b,c:d}{e:f}] will become a Map like {a:b,c:d,e:f} as well.
for (Entry<String, JsonElement> entry : object.entrySet()) {
String key = entry.getKey();
String value = entry.getValue().getAsString();
result.put(key, value);
}
}
return result;
}
}
You need to register this custom JsonDeserializer similar to this:
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(
new TypeToken<Map<String, String>>() {}.getType(),
new ArrayOfObjectsToMapDeserializer());
Gson gson = builder.create();
Note that this registers the custom deserializer for any Map<String, String> regardless in what class it is encountered. If you don't want this, you'll need to create a custom TypeAdapterFactory as well and check the declaring class before returning and instance of the deserializer.
Here goes a solution, which does not requires to make a JsonDeserializer.
All you can create is a JsonElement in a map Map.Entry<String, JsonElement>and use a for loop to iterate over the entries
//parsing string response to json object
JsonObject jsonObject = (JsonObject) new JsonParser().parse(jsonString);
//getting root object
JsonObject dateWiseContent = jsonObject.get("rootObject").getAsJsonObject();
for (Map.Entry<String, JsonElement> entry : dateWiseContent.entrySet()) {
//this gets the dynamic keys
String dateKey = entry.getKey();
//you can get any thing now json element,array,object according to json.
JsonArray jsonArrayDates = entry.getValue().getAsJsonArray();
}
read more here