How to handle nested JSON paths

Context and Problem Statement

When trying to parse nested JSON structures in JabLS which receives the VSCode settings as (partial) JSON, we need to decide how to handle nested JSON paths.

Considered Options

  • Use org.hisp.dhis:json-tree
  • Use Unirest and Optional
  • Use GSON and chaining Optional
  • Use an own method to parse the path

Decision Outcome

Chosen option: “Use org.hisp.dhis:json-tree”, because comes out best (see below).

Pros and Cons of the Options

Use org.hisp.dhis:json-tree

this.integrityCheck = json.getBoolean("jabref.integrityCheck.enabled").booleanValue(this.integrityCheck);
  • Good, because very compact and readable
  • Good, because no Exception is thrown if path does not exist
  • Good, because it supports nested paths directly
  • Good, because it has a fallback value
  • Bad, because it introduces a new dependency

Use Unirest and Optional

this.integrityCheck = Optional.ofNullable(json.optJSONObject("jabref"))
                              .map(jabref -> jabref.optJSONObject("integrityCheck"))
                              .map(integrityCheck -> integrityCheck.optBoolean("enabled", this.integrityCheck))
                              .orElse(this.integrityCheck);
  • Good, because no Exception is thrown if path does not exist
  • Good, because it has a fallback value
  • Bad, because it is quite verbose
  • Bad, because it requires chaining Optional

Use GSON and chaining Optional

this.integrityCheck = Optional.ofNullable(json.get("jabref"))
                              .map(JsonElement::getAsJsonObject)
                              .map(jsonObject -> jsonObject.get("integrityCheck"))
                              .map(JsonElement::getAsJsonObject)
                              .map(jsonObject -> jsonObject.get("enabled"))
                              .map(JsonElement::getAsBoolean)
                              .orElse(this.integrityCheck);
  • Good, because no Exception is thrown if path does not exist
  • Good, because it has a fallback value
  • Bad, because it is verbose
  • Bad, because it requires chaining Optional

Use an own method to parse the path

private <T> T assignIfPresent(JsonObject obj, T current, Class<T> type, String... path) {
    JsonObject currentObject = obj;
    for (String key : path) {
        Optional<JsonElement> element = Optional.ofNullable(currentObject.get(key));
        if (element.isEmpty()) {
            return current;
        }
        if (element.get().isJsonObject()) {
            currentObject = element.get().getAsJsonObject();
            continue;
        }
        try {
            T value = gson.fromJson(element.get(), type);
            if (value != null) {
                return value;
            }
        } catch (JsonParseException _) {
            return current;
        }
    }
    return current;
}
  • Good, because no Exception is thrown if path does not exist
  • Good, because it has a fallback value
  • Bad, because it is very verbose
  • Bad, because it needs way more parameters
  • Bad, because it needs to be tested properly
  • Bad, because it needs to be maintained