【问题标题】:Use Jackson To Stream Parse an Array of Json Objects使用 Jackson 流式解析 Json 对象数组
【发布时间】:2014-07-19 00:07:44
【问题描述】:

我有一个包含 json 对象数组的文件:

[ { “测试1”:“ABC” }, { "test2": [1, 2, 3] } ]

我希望使用 Jackson 的 JsonParser 从该文件中获取输入流,并且在每次调用 .next() 时,我希望它从数组中返回一个对象,直到它用完对象或失败。

这可能吗?

用例: 我有一个带有 json 数组的大文件,其中填充了大量具有不同模式的对象。我想一次获取一个对象以避免将所有内容加载到内存中。

编辑:

我完全忘了提。我的输入是一个随时间添加的字符串。随着时间的推移,它会慢慢积累 json。我希望能够通过对象从字符串中删除已解析的对象来解析它。

但我想这并不重要!只要 jsonParser 将索引返回到字符串中,我就可以手动执行此操作。

【问题讨论】:

    标签: json jackson


    【解决方案1】:

    是的,您可以使用ObjectMapper 实现这种部分流式部分树模型处理方式:

    ObjectMapper mapper = new ObjectMapper();
    JsonParser parser = mapper.getFactory().createParser(new File(...));
    if(parser.nextToken() != JsonToken.START_ARRAY) {
      throw new IllegalStateException("Expected an array");
    }
    while(parser.nextToken() == JsonToken.START_OBJECT) {
      // read everything from this START_OBJECT to the matching END_OBJECT
      // and return it as a tree model ObjectNode
      ObjectNode node = mapper.readTree(parser);
    
      // do whatever you need to do with this object
    }
    
    parser.close();
    

    【讨论】:

    • 嗨,Ian,经过将近 2 年的时间,这段代码实际上仍然有效并且拯救了我的一天。只是想确认一下,每次映射器执行读取树直到 END_OBJECT 标记与它开始的位置匹配时,解析器的“光标”也被移动到那里对吗?因此,如果我在 while 循环之后立即执行另一个 parser.nextToken(),我应该在刚刚读取的内容之后读取下一个对象,对吗?
    • @JamesJiang 正确。给定位于START_OBJECT 上的解析器的readTree 将使用来自解析器的事件,直到它到达匹配的END_OBJECT 并将解析器留在上面。
    • 嗯,我试过这个,但我得到了一个com.fasterxml.jackson.core.JsonParseException: Unexpected character (',' (code 44)): expected a valid value (number, String, array, object, 'true', 'false' or 'null') 异常。但是 JSON 列表应该用逗号分隔,所以我不确定杰克逊为什么抱怨......有什么提示吗?
    • @SnoopDougg 在我看来就像 JSON 的错误,可能是没有值的属性 ({"foo": ,"bar":"baz"})。或者,如果您在顶层有一系列用逗号分隔但 not 被整个数组括号包围的对象 ([]) - 我知道这种技术可以处理任何一个井形成数组 ([{...},{...}]) 或对象流 ({...}{...}) 但如果逗号不带括号,则不是。
    • 我试图用逗号分隔我的流中的对象。删除逗号修复它,谢谢!现在我只是通过({...}{...}{...}
    【解决方案2】:

    您要查找的内容称为Jackson Streaming API。这是一个使用 Jackson Streaming API 的代码 sn-p,可以帮助您实现所需。

    JsonFactory factory = new JsonFactory();
    JsonParser parser = factory.createJsonParser(new File(yourPathToFile));
    
    JsonToken token = parser.nextToken();
    if (token == null) {
        // return or throw exception
    }
    
    // the first token is supposed to be the start of array '['
    if (!JsonToken.START_ARRAY.equals(token)) {
        // return or throw exception
    }
    
    // iterate through the content of the array
    while (true) {
    
        token = parser.nextToken();
        if (!JsonToken.START_OBJECT.equals(token)) {
            break;
        }
        if (token == null) {
            break;
        }
    
        // parse your objects by means of parser.getXxxValue() and/or other parser's methods
    
    }
    

    【讨论】:

    • 仅供参考,现在 createJsonParser 方法已被弃用,您可以使用 createParser 代替 createJsonParser。
    【解决方案3】:

    这是建立在 Ian Roberts 的回答基础上的较晚回答。如果嵌套到文档中,您还可以使用 JsonPointer 来查找开始位置。这避免了使用稍微繁琐的流式令牌方法进行自定义编码以到达起点。在这种情况下,basePath 是“/”,但它可以是 JsonPointer 能够理解的任何路径。

    Path sourceFile = Paths.get("/path/to/my/file.json");
    // Point the basePath to a starting point in the file
    JsonPointer basePath = JsonPointer.compile("/");
    ObjectMapper mapper = new ObjectMapper();
    try (InputStream inputSource = Files.newInputStream(sourceFile);
         JsonParser baseParser = mapper.getFactory().createParser(inputSource);
         JsonParser filteredParser = new FilteringParserDelegate(baseParser,
                        new JsonPointerBasedFilter(basePath), false, false);) {
        // Call nextToken once to initialize the filteredParser
        JsonToken basePathToken = filteredParser.nextToken();
        if (basePathToken != JsonToken.START_ARRAY) {
            throw new IllegalStateException("Base path did not point to an array: found " 
                                           + basePathToken);
        }
        while (filteredParser.nextToken() == JsonToken.START_OBJECT) {
            // Parse each object inside of the array into a separate tree model 
            // to keep a fixed memory footprint when parsing files 
            // larger than the available memory
            JsonNode nextNode = mapper.readTree(filteredParser);
            // Consume/process the node for example:
            JsonPointer fieldRelativePath = JsonPointer.compile("/test1");
            JsonNode valueNode = nextNode.at(fieldRelativePath);
            if (!valueNode.isValueNode()) {
                throw new IllegalStateException("Did not find value at "
                        + fieldRelativePath.toString() 
                        + " after setting base to " + basePath.toString());
            }
            System.out.println(valueNode.asText());
        }
    }
    

    【讨论】:

      【解决方案4】:

      此示例直接从流中读取自定义对象:

      源是一个 java.io.File

      ObjectMapper mapper = new ObjectMapper();
      JsonParser parser = mapper.getFactory().createParser( source );
      if ( parser.nextToken() != JsonToken.START_ARRAY ) {
          throw new Exception( "no array" );
      }
      while ( parser.nextToken() == JsonToken.START_OBJECT ) {
          CustomObj custom = mapper.readValue( parser, CustomObj.class );
          System.out.println( "" + custom );
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-11-08
        • 2014-12-22
        • 1970-01-01
        • 2016-12-02
        • 2019-09-11
        • 1970-01-01
        • 2020-03-14
        • 1970-01-01
        相关资源
        最近更新 更多