I recently ran into the problem of needing to store a JSON object in a key, value store. I pasted a little utility class that does the job below. Here’s an example run against some nasty JSON,
ORIGINAL: { string: 'aString', integer: -1, nested: { x: 1, y: 2, z: 3}, moreNested: { a: [1,2,3], b: [4,5,6], c: [7,8,9]}, arrayFromHell: [{ innerArray: [{ innerInnerArray: [1,2,3]}]}]} ENCODED: {"arrayFromHell.0.innerArray.0.innerInnerArray.0":1,"arrayFromHell.0.innerArray.0.innerInnerArray.1":2,"arrayFromHell.0.innerArray.0.innerInnerArray.2":3,"nested.z":3,"nested.y":2,"nested.x":1,"integer":-1,"string":"aString","moreNested.b.0":4,"moreNested.b.1":5,"moreNested.b.2":6,"moreNested.c.0":7,"moreNested.c.1":8,"moreNested.c.2":9,"moreNested.a.0":1,"moreNested.a.1":2,"moreNested.a.2":3} DECODED: {"nested":{"z":3,"y":2,"x":1},"arrayFromHell":[{"innerArray":[{"innerInnerArray":[1,2,3]}]}],"integer":-1,"string":"aString","moreNested":{"b":[4,5,6],"c":[7,8,9],"a":[1,2,3]}}
As you can see, it simply looks at every leaf value (string, int) and derives the key path and stores the value there. For example,
{ x: { y: { z: 1 } } }
becomes,
{ x.y.z: 1 }
Arrays are a special case. Each value in an array becomes the path so far to the array appended with the array index. For example,
{ anArray: [1,2,3] }
becomes,
{ anArray.0: 1, anArray.1: 2, anArray.2: 3 }
package org.test.flatjson; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class JsonFlattener { public static String encode(JSONObject jo) throws JSONException { String s = "{" + encode(null, jo) + "}"; return s; } public static String encode(String json) throws JSONException { JSONObject jo = new JSONObject(json); return encode(jo); } private static String encode(String parent, Object val) throws JSONException { StringBuilder sb = new StringBuilder(); if (val instanceof JSONObject) { JSONObject jo = (JSONObject) val; for (Iterator<String> i = jo.keys(); i.hasNext();) { String key = i.next(); String hkey = (parent == null) ? key : parent + "." + key; Object jval = jo.get(key); String json = encode(hkey, jval); sb.append(json); if (i.hasNext()) { sb.append(","); } } } else if (val instanceof JSONArray) { JSONArray ja = (JSONArray) val; for (int i = 0; i < ja.length(); i++) { String hkey = (parent == null) ? "" + i : parent + "." + i; Object aval = ja.get(i); String json = encode(hkey, aval); sb.append(json); if (i < ja.length() - 1) { sb.append(","); } } } else if (val instanceof String) { sb.append("\"").append(parent).append("\"").append(":"); String s = (String) val; sb.append(JSONObject.quote(s)); } else if (val instanceof Integer) { sb.append("\"").append(parent).append("\"").append(":"); Integer integer = (Integer) val; sb.append(integer); } return sb.toString(); } public static String decode(String flatJson) throws JSONException { JSONObject encoded = new JSONObject(flatJson); return decodeToString(encoded); } public static String decodeToString(JSONObject encoded) throws JSONException { return decodeToObject(encoded).toString(); } public static JSONObject decodeToObject(JSONObject encoded) throws JSONException { JSONObject decoded = new JSONObject(); for (Iterator<String> i = encoded.keys(); i.hasNext();) { String hkey = i.next(); String[] keys = hkey.split("\\."); Object json = decoded; for (int j = 0; j < keys.length; j++) { if (j == keys.length - 1) { Object val = encoded.get(hkey); if (json instanceof JSONObject) { JSONObject jo = (JSONObject)json; jo.put(keys[j], val); } else if (json instanceof JSONArray) { JSONArray ja = (JSONArray)json; int index = Integer.parseInt(keys[j]); ja.put(index, val); } } else { // we're NOT at a leaf key if (!isNumber(keys[j + 1])) { // next index is an object JSONObject joChild; if (json instanceof JSONObject) { // last index was an object // we're creating an object in an object JSONObject jo = (JSONObject)json; if (jo.has(keys[j])) { joChild = jo.getJSONObject(keys[j]); } else { joChild = new JSONObject(); jo.put(keys[j], joChild); } } else if (json instanceof JSONArray) { // last index was an array // we're creating an object in an array JSONArray ja = (JSONArray)json; int index = Integer.parseInt(keys[j]); if (!ja.isNull(index)) { joChild = ja.getJSONObject(index); } else { joChild = new JSONObject(); ja.put(index, joChild); } } else { throw new AssertionError("unhandled object type"); } json = joChild; } else { // next index is an array element JSONArray jaChild; if (json instanceof JSONObject) { // last index was an object, // we're creating an array in an object JSONObject jo = (JSONObject)json; if (jo.has(keys[j])) { jaChild = jo.getJSONArray(keys[j]); } else { jaChild = new JSONArray(); jo.put(keys[j], jaChild); } } else if (json instanceof JSONArray) { // last index was an array // we're creating an array in an array JSONArray ja = (JSONArray)json; int index = Integer.parseInt(keys[j + 1]); if (!ja.isNull(index)) { jaChild = ja.getJSONArray(index); } else { jaChild = new JSONArray(); ja.put(index, jaChild); } } else { throw new AssertionError("unhandled object type"); } json = jaChild; } } } } return decoded; } private static boolean isNumber(String s) { try { Integer.parseInt(s); return true; } catch (NumberFormatException e) { return false; } } }