Get JSON Object from Google Cloud Firestore - android

This was the closest question that I found but still not what I'm looking for.
I'm using google Firestore to save user information (number, sex, etc). I use a JSON custom object to save, but when I'm trying to get the information I'm not able to transform in JSON object again.
private void getUserData() {
ffDatabase.collection(Objects.requireNonNull(mAuth.getCurrentUser()).getUid()).get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
JSONObject user = new JSONObject(task.getResult()
.toObjects(JSONObject.class).get(0));
DataStorageTemporary.getInstance().setJsonUser(user);
} else {
Log.w("ERROR load data", "Error getting documents.", task.getException());
}
}
});
}
I tried to get from Task(result) the JSON object, but it won't work:
task.getResult().toObjects(JSONObject.class).get(0)
I know that I can change QuerySnapshot to DocumentSnapshot, but I still no able to get the JSON Object from.

You would need to create a custom User Class and then write to firestore using that POJO as #Doug Stevenson says. see the following from the docs:
"Custom objects Using Java Map objects to represent your documents is often not very convenient, so Cloud Firestore also supports writing your own Java objects with custom classes. Cloud Firestore will internally convert the objects to supported data types."

Firestore doesn't store JSON. It stores key/value pairs with strongly typed values. When you read a document on Android, you have two choices to get a hold of those fields:
Automatically map the key/value pairs to a POJO that conforms to JavaBeans standards. JSONObject is not a valid JavaBean type object.
Access each key/value pair out of the document individually.

ApiFuture<QuerySnapshot> future = db.collection(collection).get();
List<QueryDocumentSnapshot> documents = future.get().getDocuments();
Iterate through the DocumentSnapshots and use recursion to create a JSONObject.
You can use getData() on DocumentSnapshot object to get a Map of your Firestore Document and then you can parse the object and create your own JSONObject.
for (DocumentSnapshot document : documents) {
JSONObject obj = mapToJSON(document.getData());
// Other stuff here ...
}
private JSONObject mapToJSON(Map<String, Object> map) {
JSONObject obj = new JSONObject();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
Map<String, Object> subMap = (Map<String, Object>) value;
obj.put(key, mapToJSON(subMap));
} else if (value instanceof List) {
obj.put(key, listToJSONArray((List) value));
}
else {
obj.put(key, value);
}
}
return obj;
}
private JSONArray listToJSONArray(List<Object> list) {
JSONArray arr = new JSONArray();
for(Object obj: list) {
if (obj instanceof Map) {
arr.add(mapToJSON((Map) obj));
}
else if(obj instanceof List) {
arr.add(listToJSONArray((List) obj));
}
else {
arr.add(obj);
}
}
return arr;
}

I took #atmandhol's solution as a starting point but used the JsonObjectBuilder. I also converted the chars-string-valueType structure of Firestore back to a single JsonStructure.
Use it as inspiration (not a full-proof solution). E.g. more valueTypes than "STRING" need to be added.
private JsonObject mapToJSON(Map<String, Object> map) {
JsonObjectBuilder builder = Json.createObjectBuilder();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if ( isSingleElement(value) ) {
builder.add(key, getSingleValueAsString(((Map) value)));
} else if (value instanceof Map) {
Map<String, Object> subMap = (Map<String, Object>) value;
builder.add(key, mapToJSON(subMap));
} else if (value instanceof List) {
builder.add(key, listToJSONArray((List<Object>) value));
}
else {
builder.add(key, value.toString());
}
}
return builder.build();
}
private String getSingleValueAsString(Map value) {
if( value.get("valueType").equals("STRING") ) {
return value.get("string").toString();
}
return "";
}
private boolean isSingleElement(Object value) {
return ( value instanceof Map
&& ((Map) value).containsKey("valueType"));
}
private JsonArray listToJSONArray(List<Object> list) {
JsonArrayBuilder builder = Json.createArrayBuilder();
for(Object value: list) {
if ( isSingleElement(value) ) {
builder.add(getSingleValueAsString(((Map) value)));
} else if (value instanceof Map) {
builder.add(mapToJSON((Map<String, Object>) value));
}
else if(value instanceof List) {
builder.add(listToJSONArray((List<Object>) value));
}
else {
builder.add(value.toString());
}
}
return builder.build();
}

Related

How do I write a json field to retrieve this?

I am currently using json for my code to retrieve an array of boolean values. However, i am adding a subfield to achieve another array within, but I am not good with json and kind of stuck how to go about it.
Here's my code so far:
field values :
public enum Field {
/**
* Runtime Config Fields
**/
FIELD_CAN_CHANGE_PASSWORD("canChangePassword", true, canUpdate),
FIELD_MAX_AUTO_DOWNLOAD_SIZE("maxAutoDownloadSize", 5000000L),
FIELD_ALWAYS_REAUTHENTICATE("alwaysReauthenticate", false, canUpdate),
FIELD_CAN_START_CALL("canStartCall", false),
FIELD_ROOMS_ENABLED("roomsEnabled", !Core.isMessenger()),
FIELD_CAN_CREATE_ROOM("canCreateRoom", !Core.isMessenger(), canUpdate),
FIELD_MAX_ENVELOPE_TTL("maxTTL", Core.isMessenger() ? 518400L : 31536000L, canUpdate),
FIELD_MAX_BURN_ON_READ_TTL("maxBOR", 0L, canUpdate),
FIELD_MAX_UPLOAD_SIZE("maxUploadSize", -1L, true),
FIELD_FRIEND_FINDER("friendFinder", !Core.isEnterprise(), canUpdate),
FIELD_ONLY_SHOW_IN_NETWORK_CONTACTS("onlyShowInNetwork", false),
FIELD_CAN_ADD_CONTACT("canAddContact", true, canUpdate),
FIELD_FORCE_DEVICE_LOCKOUT("forceDeviceLockout", 5L, canUpdate),
FIELD_VERIFICATION_MODE("verificationMode", VerificationMode.OPTIONAL.getValue(), true),
FIELD_ENABLE_NOTIFICATION_PREVIEW("enableNotificationPreview", true, true),
FIELD_DIRECTORY_ENABLED("directoryEnabled", true, true);
public String fieldName;
public Object defaultValue;
public boolean updateFromServer;
Field(String key, Object defaultValue) {
this(key, defaultValue, true);
}
Field(String key, Object defaultValue, boolean updateFromServer) {
this.fieldName = key;
this.defaultValue = defaultValue;
this.updateFromServer = updateFromServer;
}
}
putting values in field:
private void putValueForField(JSONObject configuration, Field field) {
try {
if (configuration.isNull(field.fieldName)) {
Object value = field.defaultValue;
if (value instanceof long[]) {
JSONArray array = new JSONArray();
for (long obj : (long[]) field.defaultValue) {
array.put(obj);
}
value = array;
}
runtimeConfiguration.put(field.fieldName, value);
} else {
runtimeConfiguration.put(field.fieldName, configuration.get(field.fieldName));
}
} catch (JSONException e) {
}
}
getting values :
private Object getValueForField(Field field) {
if (runtimeConfiguration.has(field.fieldName) && field.updateFromServer) {
try {
Object value = runtimeConfiguration.get(field.fieldName);
if (value instanceof JSONArray) {
JSONArray values = (JSONArray) value;
if (values.get(0) instanceof Number) {
long[] retVals = new long[values.length()];
for (int i = 0; i < values.length(); i++) {
retVals[i] = ((Number) values.get(i)).longValue();
}
return retVals;
}
} else if (value instanceof Number) {
return ((Number) value).longValue();
} else {
return value;
}
} catch (Exception e) {
e.printStackTrace();
}
}
return field.defaultValue;
}
one of the methods using the fields above:
public boolean canChangePassword() {
return (boolean) getValueForField(Field.FIELD_CAN_CHANGE_PASSWORD);
}
My new json is :
{"enableNotificationPreview":true,"destructOnRead":[30,60,300],"alwaysReauthenticate":false,"forceDeviceLockout":0,"permmod":1516894585,"maxBOR":0,"roomsEnabled":true,"directoryEnabled":true,"canStartCall":true,"canAddContact":true,"legacyDownload":false,"verificationMode":1,"restrictedAdmin":false,"canChangePassword":true,"friendFinder":true,"NEWVALUE":{"canStartNewValue1":true,"canStartroupValue":true,"canVideoCall":true,"canStartRoomValue":true,"canAddtoValue":true,"canStartValueshare":true},"canCreateRoom":true,"maxTTL":2592000,"onlyShowInNetwork":false,"maxUploadSize":null,"availableEnvelopeTTL":[0,600,3600,86400,604800,2592000],"maxAutoDownloadSize":7340032}
where I am plugging in :
"NEWVALUE":{"canStartNewValue1":true,"canStartroupValue":true,"canVideoCall":true,"canStartRoomValue":true,"canAddtoValue":true,"canStartValueshare":true}
Not sure how to update my putValueForField to reflect this new json and the corresponding fields. Any idea?
To check and retrieve JSONObject from the json object you can modify your method to check for instance of JSONObject and assign value to it.
private void putValueForField(JSONObject configuration, Field field) {
try {
if (configuration.isNull(field.fieldName)) {
Object value = field.defaultValue;
if (value instanceof long[]) {
JSONArray array = new JSONArray();
for (long obj : (long[]) field.defaultValue) {
array.put(obj);
}
value = array;
}
// this is how you check if fieldName is of type jsonobject
if (value instanceof JSONObject) {
JSONObject valueObject = configuration.getJSONObject(field.fieldName);
value = valueObject;
}
runtimeConfiguration.put(field.fieldName, value);
} else {
runtimeConfiguration.put(field.fieldName, configuration.get(field.fieldName));
}
} catch (JSONException e) {
}
}
Form what I can understand you are just getting the values from JSON and saving it in field in that case:
I think in this method ultimately you are just storing all values as object only so while retrieving you have to do all this check again.
If you are getting the response from server with fixed Object fields and object values you should be creating a class from that configuration where you will know what exactly it will return. That would really make life easier.
Or maybe if you just need the configuration to get/update or put values you can maintain it in json object itself Android JSON doc reference
For example lets say you have this jsonobject itself, I have added the working example below, hope this helps.
import org.json.*;
class Main {
public static void main(String[] args) throws JSONException {
String jsn = "{\"enableNotificationPreview\":true,\"destructOnRead\":[30,60,300],\"" +
"alwaysReauthenticate\":false,\"forceDeviceLockout\":0,\"NEWVALUE\":{\"canStartNewValue1\":true," +
"\"canStartroupValue\":true,\"canVideoCall\":true,\"canStartRoomValue\":true},\"canCreateRoom\":true," +
"\"maxTTL\":2592000,\"onlyShowInNetwork\":false,\"maxUploadSize\":null,\"availableEnvelopeTTL\"" +
":[0,600,3600,86400,604800,2592000],\"maxAutoDownloadSize\":7340032}";
JSONObject jsnObj = new JSONObject(jsn);
Object enableNotificationPreview = jsnObj.get("enableNotificationPreview");
// or if you know it will be boolean
boolean enableNotificationPreviewBool = jsnObj.getBoolean("enableNotificationPreview");
System.out.println(enableNotificationPreview);
// output : true
System.out.println(enableNotificationPreviewBool);
// output : true
JSONObject NEWVALUEObj = jsnObj.getJSONObject("NEWVALUE");
// now again you can extract values from it
boolean canStartNewValue1 = NEWVALUEObj.getBoolean("canStartNewValue1");
System.out.println(canStartNewValue1);
NEWVALUEObj.put("canStartNewValue1", false);
System.out.println(NEWVALUEObj.toString());
jsnObj.put("forceDeviceLockout", 23456);
// now canStartNewValue1 value is changed to false in the object
// and forceDeviceLockout is also 23456
System.out.println(jsnObj.toString());
}
}

com.firebase.client.FirebaseException: Failed to parse node with class class CLASS_NAME android

I am getting following exception while updating an existing value in the Firebase using updateChildren method.
com.firebase.client.FirebaseException: Failed to parse node with class class com.shajeelafzal.quicktasks_app.models.HashTagModel
at com.firebase.client.snapshot.NodeUtilities.NodeFromJSON(NodeUtilities.java:84)
at com.firebase.client.snapshot.NodeUtilities.NodeFromJSON(NodeUtilities.java:12)
at com.firebase.client.utilities.Validation.parseAndValidateUpdate(Validation.java:127)
at com.firebase.client.Firebase.updateChildren(Firebase.java:438)
at com.shajeelafzal.quicktasks_app.fragments.AddEditTaskFragment$4.onDataChange(AddEditTaskFragment.java:408)
My model looks like this:
public class HashTagModel implements Parcelable {
private HashMap<String, Object> timeStampLastUsed;
#Expose
private String name;
#Expose
private String createByUserEmail;
private List<String> tasksKeys;
public HashTagModel() {
}
public HashTagModel(HashMap<String, Object> timeStampLastUsed, String name,
String createByUserEmail, ArrayList<String> tasksKeys) {
this.timeStampLastUsed = timeStampLastUsed;
this.name = name;
this.createByUserEmail = createByUserEmail;
this.tasksKeys = tasksKeys;
}
}
JSON Object that I want to update looks like this on Firebase:
"hashTags" : {
"USER_EMAIL" : {
"USA" : {
"createByUserEmail" : "USER_EMAIL",
"name" : "#USA",
"tasksKeys" : [ "-K6mS36uhKthKf1-1pF1" ],
"timeStampLastUsed" : {
"timestamp" : 1451514461234
}
}
}
}
And my onDateChange method looks like this:
public void onDataChange(DataSnapshot dataSnapshot) {
/** if the hash tag does not exists already then create new one. */
if (dataSnapshot.getValue() == null) {
HashMap<String, Object> timestampJoined = new HashMap<>();
timestampJoined.put(Constants.FIREBASE_PROPERTY_TIMESTAMP, ServerValue.TIMESTAMP);
ArrayList<String> keysList = new ArrayList<String>();
keysList.add(key);
HashTagModel hashTag = new HashTagModel(timestampJoined, "#" + mHashTags.get(finalI),
Utils.decodeEmail(mEncodedEmail), keysList);
finalHashTagLocation.setValue(hashTag);
} else {
HashTagModel hashtaghModel = dataSnapshot.getValue(HashTagModel.class);
hashtaghModel.getTasksKeys().add(key);
/* HashMap for data to update */
HashMap<String, Object> updateUserTaskData = new HashMap<>();
Utils.updateMapForAllWithValue(null, mHashTags.get(finalI), mEncodedEmail,
updateUserTaskData, "", hashtaghModel, Constants.FIREBASE_LOCATION_USER_HASH_TAGS);
/** update the Hash Tag */
finalHashTagLocation.updateChildren(updateUserTaskData, new Firebase.CompletionListener() {
#Override
public void onComplete(FirebaseError firebaseError, Firebase firebase) {
Log.i("", "");
}
});
}
getActivity().finish();
}
Unlike the setValue() method, updateChildren() does not perform a Java-to-JSON conversion on the value(s) you pass in.
You're passing a HashMap<String, Object> into updateChildren(), which fits the contract of that API. But I'm pretty sure in some of the values you have a Java object (likely a HashTagModel) that is not directly mapped to a JSON type.
But what you can do is convert the POJO into a Map with:
Map<String, Object> hashtaghMap = new ObjectMapper().convertValue(hashtaghModel, Map.class);
Then you can put the map of values into the values that you're passing into updateChildren().

Parse - Android - Will an exception be caused if no data is returned on a Parse query?

A quick question - would an exception be caused on a Parse query if no matches were found and no data was returned by the query? For example, I'm looking to query my username table to find out if a user/username already exists or not. So I'm wondering if no matches were found on a username then would that return an unsuccesful query with an exception or a successful query with no data in the list of objects?
ParseQuery<ParseUser> userQuery = ParseUser.getQuery();
query.whereEqualTo("username", usernameInput);
query.findInBackground(new FindCallback<ParseUser>() {
#Override
public void done(List<ParseUser> objects, ParseException e) {
//query was successful
if (e == null) {
}
//query was unsuccessful
else {
}
}
If you follow the code from ParseQuery:
public Task<List<T>> findInBackground() {
return findAsync(builder.build());
}
you’ll eventually stumble upon this:
// Converts the JSONArray that represents the results of a find command to an
// ArrayList<ParseObject>.
/* package */ <T extends ParseObject> List<T> convertFindResponse(ParseQuery.State<T> state,
JSONObject response) throws JSONException {
ArrayList<T> answer = new ArrayList<>();
JSONArray results = response.getJSONArray("results");
if (results == null) {
PLog.d(TAG, "null results in find response");
} else {
String resultClassName = response.optString("className", null);
if (resultClassName == null) {
resultClassName = state.className();
}
for (int i = 0; i < results.length(); ++i) {
JSONObject data = results.getJSONObject(i);
T object = ParseObject.fromJSON(data, resultClassName, state.selectedKeys() == null);
answer.add(object);
/*
* If there was a $relatedTo constraint on the query, then add any results to the list of
* known objects in the relation for offline caching
*/
ParseQuery.RelationConstraint relation =
(ParseQuery.RelationConstraint) state.constraints().get("$relatedTo");
if (relation != null) {
relation.getRelation().addKnownObject(object);
}
}
}
return answer;
}
Inspect the above - you can see that a successful query, even though it might contain no data, will still return a non-null ArrayList instance.

Flatten Nested Object into target object with GSON

Dearest Stackoverflowers,
I was wondering if anyone knows how to solve this the best way;
I'm talking to an api which returns a json object like this:
{
"field1": "value1",
"field2": "value2",
"details": {
"nested1": 1,
"nested2": 1
}
In java I have an object (entity) which for example, would have all these fields, but with the details as loose fields, so:
field1, field2, nested1, nested2.
This because It's an android project and I can't just go saving a class with info into my entity since I'm bound to ormlite.
Is there any way to convert the fields flat into my object using GSON? note that I'm using a generic class to convert these right now straight from the API. And I want to store these fields (which contain information as an int). In the same entity.
You can write a custom type adapter to map json value to your pojo.
Define a pojo:
public class DataHolder {
public List<String> fieldList;
public List<Integer> detailList;
}
Write a custom typeAdapter:
public class CustomTypeAdapter extends TypeAdapter<DataHolder> {
public DataHolder read(JsonReader in) throws IOException {
final DataHolder dataHolder = new DataHolder();
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (name.startsWith("field")) {
if (dataHolder.fieldList == null) {
dataHolder.fieldList = new ArrayList<String>();
}
dataHolder.fieldList.add(in.nextString());
} else if (name.equals("details")) {
in.beginObject();
dataHolder.detailList = new ArrayList<Integer>();
} else if (name.startsWith("nested")) {
dataHolder.detailList.add(in.nextInt());
}
}
if(dataHolder.detailList != null) {
in.endObject();
}
in.endObject();
return dataHolder;
}
public void write(JsonWriter writer, DataHolder value) throws IOException {
throw new RuntimeException("CustomTypeAdapter's write method not implemented!");
}
}
Test:
String json = "{\"field1\":\"value1\",\"field2\":\"value2\",\"details\":{\"nested1\":1,\"nested2\":1}}";
GsonBuilder builder = new GsonBuilder();
builder.registerTypeAdapter(DataHolder.class, new CustomTypeAdapter());
Gson gson = builder.create();
DataHolder dataHolder = gson.fromJson(json, DataHolder.class);
Output:
About TypeAdapter:
https://google-gson.googlecode.com/svn/trunk/gson/docs/javadocs/com/google/gson/TypeAdapter.html
http://www.javacreed.com/gson-typeadapter-example/

How to create a complex JSON using HashMap in android?

I am facing a problem creating a Json which is actually a complex one and i need to create it through HashMap only..I was actually looking for some recursive function that could be a best solution to my problem.
JSON i need to create looks like..
{"pkt":{
"data2":{"z":"3", "y":"2", "x":"1"},
"data3":{"n":"3", "l":"1", "m":"2"},
"mid":"1328779096525",
"data1":{"b":"2", "c":"3", "a":"1"},
"msg":"10012"
}
}
any ideas??
You'd do something like this:
public void toJSON(Map<?, ?> map, JSONStringer stringer) throws JSONException {
stringer.object();
for (Map.Entry<?, ?> entry : map.entrySet()) {
stringer.key(String.valueOf(entry.getKey()));
toJSONValue(entry.getValue(), stringer);
}
stringer.endObject();
}
public void toJSONValue(Object value, JSONStringer stringer) throws JSONException {
if (value == null) {
stringer.value(null);
} else if (value instanceof Collection) {
toJSON((Collection<?>) value, stringer);
} else if (value instanceof Map) {
toJSON((Map<?, ?>) value, stringer);
} else if (value.getClass().isArray()) {
if (value.getClass().getComponentType().isPrimitive()) {
stringer.array();
if (value instanceof byte[]) {
for (byte b : (byte[]) value) {
stringer.value(b);
}
} else if (value instanceof short[]) {
for (short s : (short[]) value) {
stringer.value(s);
}
} else if (value instanceof int[]) {
for (int i : (int[]) value) {
stringer.value(i);
}
} else if (value instanceof float[]) {
for (float f : (float[]) value) {
stringer.value(f);
}
} else if (value instanceof double[]) {
for (double d : (double[]) value) {
stringer.value(d);
}
} else if (value instanceof char[]) {
for (char c : (char[]) value) {
stringer.value(c);
}
} else if (value instanceof boolean[]) {
for (boolean b : (boolean[]) value) {
stringer.value(b);
}
}
stringer.endArray();
} else {
toJSON((Object[]) value, stringer);
}
} else {
stringer.value(value);
}
}
public void toJSON(Object[] array, JSONStringer stringer) throws JSONException {
stringer.array();
for (Object value : array) {
toJSONValue(value, stringer);
}
stringer.endArray();
}
public void toJSON(Collection<?> collection, JSONStringer stringer) throws JSONException {
stringer.array();
for (Object value : collection) {
toJSONValue(value, stringer);
}
stringer.endArray();
}
To construct the example you gave:
// Using a variety of maps since all should work..
HashMap<String, Object> pkt = new HashMap<String, Object>();
LinkedHashMap<String, String> data1 = new LinkedHashMap<String, String>();
data1.put("b", "2");
data1.put("c", "3");
data1.put("a", "1");
LinkedHashMap<String, String> data2 = new LinkedHashMap<String, String>();
data2.put("z", "3");
data2.put("y", "2");
data2.put("x", "1");
TreeMap<String, Object> data3 = new TreeMap<String, Object>();
data3.put("z", "3");
data3.put("y", "2");
data3.put("x", "1");
pkt.put("data2", data2);
pkt.put("data3", data3);
pkt.put("mid", "1328779096525");
pkt.put("data1", data1);
pkt.put("msg", "10012");
try {
JSONStringer stringer = new JSONStringer();
stringer.object();
stringer.key("pkt");
toJSON(pkt, stringer);
stringer.endObject();
System.out.println(stringer.toString());
} catch (JSONException e) {
// Time for some error-handling
}
Which would result in (formatted for viewing):
{
"pkt":{
"data2":{
"z":"3",
"y":"2",
"x":"1"
},
"mid":"1328779096525",
"data3":{
"x":"1",
"y":"2",
"z":"3"
},
"msg":"10012",
"data1":{
"b":"2",
"c":"3",
"a":"1"
}
}
}
We're using GSON for our object/JSON conversions. Here's a link for more info: GSON

Categories

Resources