Gson does not have anything like that, but it provides enough capabilities to build it on top: you can walk JSON streams (JsonReader) and trees (JsonElement, but not wrapped into JsonReader) stack-based and stack-based/recursively accordingly (streams may save much).
I would create a generic tree-walking method to adapt it for further purposes.
public static void walk(final JsonElement jsonElement, final BiConsumer<? super Collection<?>, ? super JsonElement> consumer) {
final Deque<Object> parents = new ArrayDeque<>();
parents.push("$");
walk(jsonElement, consumer, parents);
}
private static void walk(final JsonElement jsonElement, final BiConsumer<? super Collection<?>, ? super JsonElement> consumer, final Deque<Object> path) {
if ( jsonElement.isJsonNull() ) {
consumer.accept(path, jsonElement);
} else if ( jsonElement.isJsonPrimitive() ) {
consumer.accept(path, jsonElement);
} else if ( jsonElement.isJsonObject() ) {
for ( final Map.Entry<String, JsonElement> e : jsonElement.getAsJsonObject().entrySet() ) {
path.addLast(e.getKey());
walk(e.getValue(), consumer, path);
path.removeLast();
}
} else if ( jsonElement.isJsonArray() ) {
int i = 0;
for ( final JsonElement e : jsonElement.getAsJsonArray() ) {
path.addLast(i++);
walk(e, consumer, path);
path.removeLast();
}
} else {
throw new AssertionError(jsonElement);
}
}
Note that the method above also supports arrays. The walk method is push-semantics-driven: it uses callbacks to provide the walk progress. Making it lazy by returning an iterator or a stream would probably be cheaper and get the pull semantics applied. Also, CharSequence view elements would probably save on creating many strings.
public static String toJsonPath(final Iterable<?> path) {
final StringBuilder stringBuilder = new StringBuilder();
final Iterator<?> iterator = path.iterator();
if ( iterator.hasNext() ) {
final Object next = iterator.next();
stringBuilder.append(next);
}
while ( iterator.hasNext() ) {
final Object next = iterator.next();
if ( next instanceof Number ) {
stringBuilder.append('[').append(next).append(']');
} else if ( next instanceof CharSequence ) {
stringBuilder.append('.').append(next);
} else {
throw new UnsupportedOperationException("Unsupported: " + next);
}
}
return stringBuilder.toString();
}
Test:
final JsonElement jsonElement = Streams.parse(jsonReader);
final Collection<String> paths = new ArrayList<>();
JsonPaths.walk(jsonElement, (path, element) -> paths.add(JsonPaths.toJsonPath(path)));
for ( final String path : paths ) {
System.out.println(path);
}
Assertions.assertIterableEquals(
ImmutableList.of(
"$.nothing",
"$.number",
"$.object.subobject.number",
"$.array[0].string",
"$.array[1].string",
"$.array[2][0][0][0]"
),
paths
);