首页 > 解决方案 > 使用 java/GSON 解析大型 JSON,无法读取 JSON 结构

问题描述

我正在尝试使用 Java 和 GSON 解析来自 Musicbrainz.org 的 JSON 格式的大型(约 10GB)数据库转储

JSON 文件具有这种结构。没有'['']'表示这将是一个对象数组,每个对象之间没有','。不知道为什么,这个 JSON 文件就是这样。

{
    "id": "d0ab06e1-751a-414b-a976-da72670391b1",
    "name": "Arcing Wires",
    "sort-name": "Arcing Wires"
}
{
    "id": "6f0c2c16-dd7e-4268-a484-bc7b2ac78108",
    "name": "Another",
    "sort-name": "Another"
}
{
    "id": "e062b6cd-5506-47b0-afdb-72f4279ec38c",
    "name": "Agent S",
    "sort-name": "Agent S"
}

这是我正在使用的代码:

        try(JsonReader jsonReader = new JsonReader(
            new InputStreamReader(
                    new FileInputStream(jsonFilePath), StandardCharsets.UTF_8))) {
        Gson gson = new GsonBuilder().create();
        jsonReader.beginArray();
        while (jsonReader.hasNext()) {
            Artist mapped = gson.fromJson(jsonReader, Artist.class);
            //TODO do something with the object
            }
        }
        jsonReader.endArray();
    }
    catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

我映射的类是这样的:

public class Artist {

@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("sort-name")
public String sortName;

}

我得到的错误:

Exception in thread "main" java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at DBLoader.parse(DBLoader.java:39)
at DBLoader.main(DBLoader.java:23)

我相信 GSON 期望与我声明的结构不同,但我不明白我应该如何定义这种没有逗号和方括号的 JSON。有什么线索吗?谢谢

标签: javajsongson

解决方案


默认情况下,JSON 仅声明一个最高值(是的,这将是一个有效的 JSON 文档),但有JSON 流使用任意技术将多个 JSON 元素连接到单个流中,假设流使用者可以解析它(阅读更多)。Gson 支持所谓的 lenient 模式,该模式关闭“仅一个最高值”模式(并做一些与问题无关的更多事情)JsonReader:. setLenient打开 lenient 模式后,您可以一个一个地读取 JSON 元素,事实证明,此模式可用于解析/读取行分隔的 JSON连接的 JSON 值因为它们只是由 Gson 忽略的零个或多个空格分隔(因此不支持更多奇特的记录分隔符分隔的 JSON长度前缀的 JSON)。它对您不起作用的原因是您的初始代码假定流包含单个 JSON 数组(显然不是:它应该是不符合 JSON 数组语法的元素流)。

一个简单的通用 JSON 流支持可能看起来像这样(使用 Stream API 来获得比现有更丰富的 API Iterator,但展示一个想法很好,您可以轻松地将其调整为迭代器、回调、可观察流,无论您喜欢什么):

@UtilityClass
public final class JsonStreamSupport {

    public static <T> Stream<T> parse(@WillNotClose final JsonReader jsonReader, final Function<? super JsonReader, ? extends T> readElement) {
        final boolean isLenient = jsonReader.isLenient();
        jsonReader.setLenient(true);
        final Spliterator<T> spliterator = new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) {
            @Override
            public boolean tryAdvance(final Consumer<? super T> action) {
                try {
                    final JsonToken token = jsonReader.peek();
                    if ( token == JsonToken.END_DOCUMENT ) {
                        return false;
                    }
                    // TODO: read more elements in batch
                    final T element = readElement.apply(jsonReader);
                    action.accept(element);
                    return true;
                } catch ( final IOException ex ) {
                    throw new RuntimeException(ex);
                }
            }
        };
        return StreamSupport.stream(spliterator, false)
                .onClose(() -> jsonReader.setLenient(isLenient));
    }

}

进而:

JsonStreamSupport.<Artist>parse(jsonReader, jr -> gson.fromJson(jr, Artist.class))
        .forEach(System.out::println);

输出(假设Artist有 Lombok-generated toString()):

艺术家(id=d0ab06e1-751a-414b-a976-da72670391b1,name=Arcing Wires,sortName=Arcing Wires)
艺术家(id=6f0c2c16-dd7e-4268-a484-bc7b2ac78108,name=Another,sortName=Another)
艺术家(id= e062b6cd-5506-47b0-afdb-72f4279ec38c,名称=Agent S,sortName=Agent S)

这种方法(JSON 流)保存了多少字节,以便在您尝试使用的服务中使用它?我不知道。


推荐阅读