Using a simple Json file e.g:
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
I want to be able to get the JsonArray
named menuitem
using a path:
String path = "menu.popup.menuitem"
I tried to do this using:
public static JsonElement fromString(String json, String path) throws JsonSyntaxException {
JsonObject obj = GsonBuilder.create().fromJson(json, JsonObject.class);
String[] seg = path.split(".");
for (String element : seg) {
if (obj != null) {
obj = obj.get(element).getAsJsonObject();
} else {
return null;
}
}
return obj
}
with:
JsonElement jsonElement = fromString(json, path);
But when I try isJsonArray()
the return value is false
. When doing the extra sanity check using Gson.toJson(jsonElement)
the output is the full json String (above) that was inputted originally.
What's going wrong?
split
uses regex to find places on which string should be split, but .
in regex is special character which represents "any character beside line separators", which means that you are actually splitting on each character. So for string like
"foo"
"foo".split(".")
will split on f
, o
, o
"foo"
^^^
which means you will get as result array with four empty strings (3 splits give 4 elements).
["", "", "", ""]
Actually I lied here because split(regex)
does one additional thing: it removes trailing empty strings from result array, but your array contains only empty strings, which means that they will all be removed, so split(".")
will return just empty array []
so your loop will not iterate even once (that is why your method returns unmodified obj
).
To get rid of this problem you will need to make .
literal (you need to escape it). To do so you can use for instance split("\\.")
or split("[.]")
or split(Pattern.quote(".")
which work same as split("\\Q.\\E")
- it adds quotation area.
Also inside loop you should first check type of Json you are handling, because getAsJsonObject
will fail if Json is array. So your code should probably look like
public static JsonElement fromString(String json, String path)
throws JsonSyntaxException {
JsonObject obj = new GsonBuilder().create().fromJson(json, JsonObject.class);
String[] seg = path.split("\\.");
for (String element : seg) {
if (obj != null) {
JsonElement ele = obj.get(element);
if (!ele.isJsonObject())
return ele;
else
obj = ele.getAsJsonObject();
} else {
return null;
}
}
return obj;
}
I'm not sure why this is not built-into Gson, but here is a method that I wrote, which returns a JsonElement given a JsonElement input and a JSON Path:
/**
* Returns a JSON sub-element from the given JsonElement and the given path
*
* @param json - a Gson JsonElement
* @param path - a JSON path, e.g. a.b.c[2].d
* @return - a sub-element of json according to the given path
*/
public static JsonElement getJsonElement(JsonElement json, String path){
String[] parts = path.split("\\.|\\[|\\]");
JsonElement result = json;
for (String key : parts) {
key = key.trim();
if (key.isEmpty())
continue;
if (result == null){
result = JsonNull.INSTANCE;
break;
}
if (result.isJsonObject()){
result = ((JsonObject)result).get(key);
}
else if (result.isJsonArray()){
int ix = Integer.valueOf(key) - 1;
result = ((JsonArray)result).get(ix);
}
else break;
}
return result;
}
To call it, use something like:
String jsonString = ...;
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(jsonString, JsonObject.class);
JsonElement subElement = getJsonElement(jsonObject, "a.b.c[2].d";