blob: 196a7b1f3993bc7d7ae2cfa9139ca872a040c9e2 [file] [log] [blame]
package org.wordpress.android.util;
import android.text.TextUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.wordpress.android.util.AppLog.T;
import java.util.ArrayList;
public class JSONUtils {
private static String QUERY_SEPERATOR = ".";
private static String QUERY_ARRAY_INDEX_START = "[";
private static String QUERY_ARRAY_INDEX_END = "]";
private static String QUERY_ARRAY_FIRST = "first";
private static String QUERY_ARRAY_LAST = "last";
private static final String JSON_NULL_STR = "null";
private static final String TAG = "JSONUtils";
/**
* Given a JSONObject and a key path (e.g property.child) and a default it will
* traverse the object graph and pull out the desired property
*/
public static <U> U queryJSON(JSONObject source, String query, U defaultObject) {
if (source == null) {
AppLog.e(T.UTILS, "Parameter source is null, can't query a null object");
return defaultObject;
}
if (query == null) {
AppLog.e(T.UTILS, "Parameter query is null");
return defaultObject;
}
int nextSeperator = query.indexOf(QUERY_SEPERATOR);
int nextIndexStart = query.indexOf(QUERY_ARRAY_INDEX_START);
if (nextSeperator == -1 && nextIndexStart == -1) {
// last item let's get it
try {
if (!source.has(query)) {
return defaultObject;
}
Object result = source.get(query);
if (result.getClass().isAssignableFrom(defaultObject.getClass())) {
return (U) result;
} else {
AppLog.w(T.UTILS, String.format("The returned object type %s is not assignable to the type %s. Using default!",
result.getClass(),defaultObject.getClass()));
return defaultObject;
}
} catch (java.lang.ClassCastException e) {
AppLog.e(T.UTILS, "Unable to cast the object to " + defaultObject.getClass().getName(), e);
return defaultObject;
} catch (JSONException e) {
AppLog.e(T.UTILS, "Unable to get the Key from the input object. Key:" + query, e);
return defaultObject;
}
}
int endQuery;
if (nextSeperator == -1 || nextIndexStart == -1) {
endQuery = Math.max(nextSeperator, nextIndexStart);
} else {
endQuery = Math.min(nextSeperator, nextIndexStart);
}
String nextQuery = query.substring(endQuery);
String key = query.substring(0, endQuery);
try {
if (nextQuery.indexOf(QUERY_SEPERATOR) == 0) {
return queryJSON(source.getJSONObject(key), nextQuery.substring(1), defaultObject);
} else if (nextQuery.indexOf(QUERY_ARRAY_INDEX_START) == 0) {
return queryJSON(source.getJSONArray(key), nextQuery, defaultObject);
} else if (!nextQuery.equals("")) {
return defaultObject;
}
Object result = source.get(key);
if (result.getClass().isAssignableFrom(defaultObject.getClass())) {
return (U) result;
} else {
AppLog.w(T.UTILS, String.format("The returned object type %s is not assignable to the type %s. Using default!",
result.getClass(),defaultObject.getClass()));
return defaultObject;
}
} catch (java.lang.ClassCastException e) {
AppLog.e(T.UTILS, "Unable to cast the object to " + defaultObject.getClass().getName(), e);
return defaultObject;
} catch (JSONException e) {
return defaultObject;
}
}
/**
* Given a JSONArray and a query (e.g. [0].property) it will traverse the array and
* pull out the requested property.
*
* Acceptable indexes include negative numbers to reference items from the end of
* the list as well as "last" and "first" as more explicit references to "0" and "-1"
*/
public static <U> U queryJSON(JSONArray source, String query, U defaultObject) {
if (source == null) {
AppLog.e(T.UTILS, "Parameter source is null, can't query a null object");
return defaultObject;
}
if (query == null) {
AppLog.e(T.UTILS, "Parameter query is null");
return defaultObject;
}
// query must start with [ have an index and then have ]
int indexStart = query.indexOf(QUERY_ARRAY_INDEX_START);
int indexEnd = query.indexOf(QUERY_ARRAY_INDEX_END);
if (indexStart == -1 || indexEnd == -1 || indexStart > indexEnd) {
return defaultObject;
}
// get "index" from "[index]"
String indexStr = query.substring(indexStart + 1, indexEnd);
int index;
if (indexStr.equals(QUERY_ARRAY_FIRST)) {
index = 0;
} else if (indexStr.equals(QUERY_ARRAY_LAST)) {
index = -1;
} else {
index = Integer.parseInt(indexStr);
}
if (index < 0) {
index = source.length() + index;
}
// copy remaining query
String remainingQuery = query.substring(indexEnd + 1);
try {
if (remainingQuery.indexOf(QUERY_ARRAY_INDEX_START) == 0) {
return queryJSON(source.getJSONArray(index), remainingQuery, defaultObject);
} else if (remainingQuery.indexOf(QUERY_SEPERATOR) == 0) {
return queryJSON(source.getJSONObject(index), remainingQuery.substring(1), defaultObject);
} else if (!remainingQuery.equals("")) {
// TODO throw an exception since the query isn't valid?
AppLog.w(T.UTILS, String.format("Incorrect query for next object %s", remainingQuery));
return defaultObject;
}
Object result = source.get(index);
if (result.getClass().isAssignableFrom(defaultObject.getClass())) {
return (U) result;
} else {
AppLog.w(T.UTILS, String.format("The returned object type %s is not assignable to the type %s. Using default!",
result.getClass(),defaultObject.getClass()));
return defaultObject;
}
} catch (java.lang.ClassCastException e) {
AppLog.e(T.UTILS, "Unable to cast the object to "+defaultObject.getClass().getName(), e);
return defaultObject;
} catch (JSONException e) {
return defaultObject;
}
}
/**
* Convert a JSONArray (expected to contain strings) in a string list
*/
public static ArrayList<String> fromJSONArrayToStringList(JSONArray jsonArray) {
ArrayList<String> stringList = new ArrayList<String>();
for (int i = 0; i < jsonArray.length(); i++) {
try {
stringList.add(jsonArray.getString(i));
} catch (JSONException e) {
AppLog.e(T.UTILS, e);
}
}
return stringList;
}
/**
* Convert a string list in a JSONArray
*/
public static JSONArray fromStringListToJSONArray(ArrayList<String> stringList) {
JSONArray jsonArray = new JSONArray();
if (stringList != null) {
for (int i = 0; i < stringList.size(); i++) {
jsonArray.put(stringList.get(i));
}
}
return jsonArray;
}
/*
* wrapper for JSONObject.optString() which handles "null" values
*/
public static String getString(JSONObject json, String name) {
String value = json.optString(name);
// return empty string for "null"
if (JSON_NULL_STR.equals(value))
return "";
return value;
}
/*
* use with strings that contain HTML entities
*/
public static String getStringDecoded(JSONObject json, String name) {
String value = getString(json, name);
return HtmlUtils.fastUnescapeHtml(value);
}
/*
* replacement for JSONObject.optBoolean() - optBoolean() only checks for "true" and "false",
* but our API sometimes uses "0" to denote false
*/
public static boolean getBool(JSONObject json, String name) {
String value = getString(json, name);
if (TextUtils.isEmpty(value))
return false;
if (value.equals("0"))
return false;
if (value.equalsIgnoreCase("false"))
return false;
if (value.equalsIgnoreCase("no"))
return false;
return true;
}
/*
* returns the JSONObject child of the passed parent that matches the passed query
* this is basically an "optJSONObject" that supports nested queries, for example:
*
* getJSONChild("meta/data/site")
*
* would find this:
*
* "meta": {
* "data": {
* "site": {
* "ID": 3584907,
* "name": "WordPress.com News",
* }
* }
* }
*/
public static JSONObject getJSONChild(final JSONObject jsonParent, final String query) {
if (jsonParent == null || TextUtils.isEmpty(query))
return null;
String[] names = query.split("/");
JSONObject jsonChild = null;
for (int i = 0; i < names.length; i++) {
if (jsonChild == null) {
jsonChild = jsonParent.optJSONObject(names[i]);
} else {
jsonChild = jsonChild.optJSONObject(names[i]);
}
if (jsonChild == null)
return null;
}
return jsonChild;
}
}