/** * * Funf: Open Sensing Framework * Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland. * Acknowledgments: Alan Gardner * Contact: nadav@media.mit.edu * * This file is part of Funf. * * Funf is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * Funf is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Funf. If not, see <http://www.gnu.org/licenses/>. * */ package edu.mit.media.funf.json; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import android.net.Uri; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; public class JsonUtils { private JsonUtils() {} private static final Comparator<Entry<String,JsonElement>> JSON_OBJECT_ENTRY_SET_COMPARATOR = new Comparator<Map.Entry<String,JsonElement>>() { @Override public int compare(Entry<String, JsonElement> lhs, Entry<String, JsonElement> rhs) { return lhs==null ? (rhs==null ? 0 : -1) : (rhs==null ? 1 : lhs.getKey().compareTo(rhs.getKey())); } }; /** * Sort all elements in Json objects, to ensure that they are identically serialized. * @param el * @return */ public static JsonElement deepSort(JsonElement el) { if (el == null) { return null; } else if (el.isJsonArray()) { JsonArray sortedArray = new JsonArray(); for (JsonElement subEl : (JsonArray)el) { sortedArray.add(deepSort(subEl)); } return sortedArray; } else if (el.isJsonObject()){ List<Entry<String,JsonElement>> entrySet = new ArrayList<Entry<String,JsonElement>>(((JsonObject)el).entrySet()); Collections.sort(entrySet, JSON_OBJECT_ENTRY_SET_COMPARATOR); JsonObject sortedObject = new JsonObject(); for (Entry<String,JsonElement> entry : entrySet) { sortedObject.add(entry.getKey(), deepSort(entry.getValue())); } return sortedObject; } else { return el; } } /** * Returns a deep copy of the JsonElement. This is not thread safe. * @param el * @return */ @SuppressWarnings("unchecked") public static <T extends JsonElement> T deepCopy(T el) { if (el == null) { return (T)JsonNull.INSTANCE; } else if (el.isJsonArray()) { JsonArray array = new JsonArray(); for (JsonElement subEl : el.getAsJsonArray()) { array.add(deepCopy(subEl)); } return (T)array; } else if (el.isJsonObject()) { JsonObject object = new JsonObject(); for (Map.Entry<String, JsonElement> entry : el.getAsJsonObject().entrySet()) { object.add(entry.getKey(), deepCopy(entry.getValue())); } return (T)object; } else { return (T)el; } } /** * Return an immutable JsonElement. Either IJsonObject, IJsonArray, or other immutable JsonPrimitive. * @param el * @return */ public static JsonElement immutable(JsonElement el) { if (el instanceof JsonObject) { return new IJsonObject(el.getAsJsonObject()); } else if (el instanceof JsonArray) { return new IJsonArray(el.getAsJsonArray()); } else if (el instanceof JsonPrimitive && el.getAsJsonPrimitive().isNumber()) { // Ensure that all LazilyParsedNumbers have been parsed into a consistent Number type. // Otherwise hash codes are not consistent, because LazilyParsedNumbers are never seen as integral. return new JsonPrimitive(el.getAsBigDecimal()); } return el; } /** * In place copy of one objects values onto another, with option to replace existing values in copy. * @param source * @param destination * @param replace */ public static void deepCopyOnto(JsonObject source, JsonObject destination, boolean replace) { if (source == null || destination == null) { throw new RuntimeException("Both source and destination must exist while copying values from one to another."); } JsonObject sourceCopy = new JsonParser().parse(source.toString()).getAsJsonObject(); // Defensive copy to ensure the source is never modified deepCopyOnto(sourceCopy, destination, replace, true); } private static void deepCopyOnto(JsonObject source, JsonObject destination, boolean replace, boolean initial) { for (Map.Entry<String, JsonElement> sourceVal : source.entrySet()) { if (replace || !destination.has(sourceVal.getKey())) { String key = sourceVal.getKey(); JsonElement value = sourceVal.getValue(); if (value.isJsonObject() && destination.has(key) && destination.get(key).isJsonObject()) { deepCopyOnto(value.getAsJsonObject(), destination.get(key).getAsJsonObject(), replace, false); } else { destination.add(key, value); } } } } public static final Gson GSON = new Gson(); public static final String JSON_SCHEME = "json"; /** * Transform json into an immutable Uri * @param el * @return */ public static Uri toUri(JsonElement el) { return new Uri.Builder() .scheme(JSON_SCHEME) .appendPath(immutable(el).toString()) .build(); } /** * Parse json data from a json:// uri * @param uri * @return */ public static JsonElement fromUri(Uri uri) { return JSON_SCHEME.equals(uri.getScheme()) ? new JsonParser().parse(uri.getPath()) : null; } }