| 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; |
| } |
| } |