将 JSON 文件中的所有元素收集到一个列表中

Collect all elements in a JSON file into a single list

我正在使用 Gson 2.8.1+(如果需要我可以升级)。

如果我有 JsonObject:

"config" : {
        "option_one" : {
            "option_two" : "result_one"
        }
    }
}

...我怎样才能有效地将其转换为以下形式:

"config.option_one.option_two" : "result_one"

算法

你能想到的最简单的算法是递归折叠。您首先递归地潜入结构的底部,然后询问地图中是否只有一个元素(您必须使用某些框架解析 json 以获得 Map 结构)。如果存在,则将父字段的字符串与 属性 连接起来,并将父字段的值设置为 属性 的值。然后你向上移动并重复这个过程,直到你在根部。当然,如果map有多个字段,你就移到parent去试试egan。

简单示例:

public static void main(String[] args) {
    String str = """
    {
        "config" : {
            "option_one" : {
                "option_two" : "result_one"
            }
        }
    }""";
    var obj = JsonParser.parseString(str).getAsJsonObject();
    System.out.println(flatten(obj)); // {"config.option_one.option_two":"result_one"}
}

public static JsonObject flatten(JsonObject toFlatten) {
    var flattened = new JsonObject();
    flatten0("", toFlatten, flattened);
    return flattened;
}

private static void flatten0(String prefix, JsonObject toFlatten, JsonObject toMutate) {
    for (var entry : toFlatten.entrySet()) {
        var keyWithPrefix = prefix + entry.getKey();
        if (entry.getValue() instanceof JsonObject child) {
            flatten0(keyWithPrefix + ".", child, toMutate);
        } else {
            toMutate.add(keyWithPrefix, entry.getValue());
        }
    }
}

Gson 没有类似的东西,但它提供了足够的能力来构建它:你可以遍历 JSON 流(JsonReader)和树(JsonElement,但是没有包装成 JsonReader) 基于堆栈和 stack-based/recursively 相应地(流可能会节省很多)。

我会创建一个通用的树行走方法来适应它以用于进一步的目的。

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);
    }
}

注意上面的方法也支持数组。 walk 方法是推送语义驱动的:它使用回调来提供步行进度。通过返回迭代器或流使其变得惰性可能会更便宜并应用拉动语义。此外,CharSequence 视图元素可能会节省创建许多字符串的时间。

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();
}

测试:

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
);