google / gson

A Java serialization/deserialization library to convert Java Objects into JSON and back
Apache License 2.0
23.3k stars 4.28k forks source link

JSON Pointer spec (RFC 6901) support #1443

Open markkolich opened 5 years ago

markkolich commented 5 years ago

Consider the following JSON object:

{
  "foo": {
    "bar": {
       "baz": "bat"
    }
  }
}

Using GSON, without registering a custom type adapter, if I want to safely access member foo.bar.baz I end up with something like this:

final JsonObject object = new GsonBuilder().create().fromJson("{...}", JsonObject.class);

final JsonObject foo = object.getAsJsonObject("foo");
if (foo != null) {
  final JsonObject bar = foo.getAsJsonObject("bar");
  if (bar != null) {
    final JsonElement baz = bar.get("baz");
    if (baz != null && baz.isJsonPrimitive()) {
      final String bat = baz.getAsString();
      // ...
    }
  }
}

Instead, I'd love to be able to call a single method on the initial JsonObject itself, something like getStringAtPath():

final JsonObject object = new GsonBuilder().create().fromJson("{...}", JsonObject.class);

final String baz = object.getStringAtPath("foo.bar.baz"); // <<-- !!
if (baz == null) {
  // ... didn't exist.
}

In lieu of this missing API, I cooked up a simple helper method for String primitives:

import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;

@Nullable
public static final String getStringAtPath(
    final JsonObject object,
    final String path) {
  Preconditions.checkNotNull(object, "JSON object cannot be null.");
  Preconditions.checkNotNull(path, "JSON object path cannot be null.");

  JsonObject base = object;
  final Iterable<String> segments = Splitter.on(".").split(path);
  for (final String segment : segments) {
    final JsonElement element = base.get(segment);
    if (element == null) {
      return null;
    }

    if (element.isJsonPrimitive()) {
      final JsonPrimitive primitive = element.getAsJsonPrimitive();
      return primitive.getAsString();
    } else if (element.isJsonObject()) {
      base = element.getAsJsonObject();
    } else {
      return null;
    }
  }

  return null;
}

It would be tremendously helpful if similar methods were conveniently folded into the JsonObject API itself. It could easily be extended for the other types as well: int, boolean, char, double, etc. Admittedly, things get a bit hairy with arrays but I'd imagine support for paths like foo.bar[8].baz could work.

Apologies if this is a dupe, I did briefly search the existing set of issues but didn't immediately find anything related.

lyubomyr-shaydariv commented 5 years ago

I think Gson will never have something like that, but there are tools like JsonPath that seems to be exactly what you're looking for. It provides a powerful generic query language providing support for multiple libraries including Gson.

markkolich commented 5 years ago

there are tools like JsonPath that seems to be exactly what you're looking for.

That's certainly an option, but I was hoping to avoid pulling in yet another library for something that seems so common and fundamental.

In fact, Jackson 2.3.0 introduced support for the JSON Pointer spec (RFC 6901), which is closer to what I'm looking for:

final JsonNode object = new ObjectMapper().readTree("{...}");
final String baz = object.at("/foo/bar/baz").asText();

I'm heavily invested in GSON, and have no intention of switching to Jackson just for JSON Pointer support. However, I was hoping GSON would follow Jackson's lead on this.