【问题标题】:parsing large JSON with java/GSON, can't read the JSON structure使用 java/GSON 解析大型 JSON,无法读取 JSON 结构
【发布时间】:2021-09-21 21:25:42
【问题描述】:

我正在尝试使用 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。 有什么线索吗? 谢谢

【问题讨论】:

  • 这不是 JSON,它是一堆单独的 JSON 对象,它们被塞进一个文件中。要求 JSON 解析器读取它会失败。就像让会说英语和西班牙语的朋友为您翻译俄语一样。您有几个选择:预处理流并通过插入缺少的语法将其转换为合法的 JSON。或者,分别处理每个有效块({...} 之间的单个 JSON 对象)。您真的需要将整个内容加载到内存中吗?
  • 对不起,但信息非常清楚。您命令解析器读取数组,但没有数组,解析器正在给您确切的问题。

标签: java json gson


【解决方案1】:

JSON 默认只声明一个最高值(是的,这将是一个有效的 JSON 文档),但是 JSON 流 使用任意技术连接多个假设流使用者可以解析 JSON 元素到单个流中 (read more)。 Gson 支持所谓的宽松模式,该模式关闭 JsonReader:setLenient 的“仅一个最高值”模式(并做一些与问题无关的更多事情)。开启 lenient 模式后,您可以逐个读取 JSON 元素,事实证明,该模式可用于解析/读取 line-delimited JSONconcatenated JSON values 因为它们只是由 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 生成的 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,name=Agent S,sortName=Agent S)

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

【讨论】:

    【解决方案2】:

    看起来像jsonl 格式,其中每一行都是有效的 JSON 对象。 (阅读更多here
    您可以逐行读取文件并转换为对象。我认为它会起作用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-23
      • 2011-07-26
      • 1970-01-01
      • 2018-10-26
      • 1970-01-01
      相关资源
      最近更新 更多