Использование Gson с путем

Используя простой файл Json, например:

{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}}

Я хочу иметь возможность получить JsonArray с именем menuitem, используя путь:

String path =  "menu.popup.menuitem"

Я попытался сделать это, используя:

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
}

с:

JsonElement jsonElement = fromString(json, path);

Но когда я пытаюсь isJsonArray(), возвращаемое значение равно false. При выполнении дополнительной проверки работоспособности с использованием Gson.toJson(jsonElement) вывод представляет собой полную строку json (см. выше), которая была введена изначально. Что происходит не так?


person Eduardo    schedule 28.09.2014    source источник
comment
возможный дубликат Java-строки, разделенной на . (точка)   -  person Devrim    schedule 28.09.2014
comment
Используйте, например, ; вместо ..   -  person Hot Licks    schedule 28.09.2014


Ответы (2)


split использует регулярное выражение для поиска мест, в которых строка должна быть разделена, но . в регулярном выражении - это специальный символ, который представляет «любой символ рядом с разделителями строк», что означает, что вы фактически разделяете каждый символ. Итак, для строки типа

"foo"

"foo".split(".") разделится на f, o, o

"foo"
 ^^^

что означает, что вы получите в качестве результата массив с четырьмя пустыми строками (3 разделения дают 4 элемента).

["", "", "", ""]

На самом деле я солгал, потому что split(regex) делает еще одну вещь: он удаляет конечные пустые строки из массива результатов, но ваш массив содержит только пустые строки, что означает, что все они будут удалены, поэтому split(".") вернет только пустой массив [], поэтому ваш цикл не будет выполнить итерацию даже один раз (поэтому ваш метод возвращает неизмененный obj).

Чтобы избавиться от этой проблемы, вам нужно сделать . литералом (вам нужно его экранировать). Для этого вы можете использовать, например, split("\\.") или split("[.]") или split(Pattern.quote("."), которые работают так же, как split("\\Q.\\E") — они добавляют область цитаты.

Также внутри цикла вы должны сначала проверить тип Json, с которым вы работаете, потому что getAsJsonObject завершится ошибкой, если Json является массивом. Таким образом, ваш код, вероятно, должен выглядеть так

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;
}
person Pshemo    schedule 28.09.2014
comment
В качестве альтернативы path.split([.]) также можно использовать, так как включение в [ ] изображает буквальный символ - person prash; 21.09.2017
comment
@prash Да, что уже упоминалось в ответе, а также несколько других способов :) - person Pshemo; 21.09.2017
comment
вау, такой подробный! .. Извините, я не заметил, что только что проверил раздел кода .. спасибо за указание :-) - person prash; 21.09.2017

Я не уверен, почему это не встроено в Gson, но вот метод, который я написал, который возвращает JsonElement с учетом ввода JsonElement и пути JSON:

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

Чтобы вызвать его, используйте что-то вроде:

String jsonString = ...;

Gson gson = new Gson();
JsonObject  jsonObject = gson.fromJson(jsonString, JsonObject.class);
JsonElement subElement = getJsonElement(jsonObject, "a.b.c[2].d";
person isapir    schedule 10.12.2017
comment
Это не встроено в Gson, потому что есть отдельный, более формальный способ сделать это, который называется JSON Path. Вот одна из популярных реализаций github.com/json-path/JsonPath, а вот введение в это baeldung.com/guide-to-jayway-jsonpath - person Slawomir; 07.04.2019