使用GSON转换嵌套JSON流

cgfeq70w  于 2022-11-06  发布在  其他
关注(0)|答案(1)|浏览(176)

用途:使用GSON获取一个大型JSON文件的输入流,并将其作为迭代器向下游函数公开;但是我不能在内存中存储整个JSON文件。目前我使用一些基本的Java代码来实现这一点,这些代码可以完成以下操作:

  • 知道何时跳过大括号
  • 读取流,直到找到下一个有效的JSON对象
  • 使用GSON将其解析为POJO
    期望的结果查看GSON是否具有内置功能来替换我的自定义Java代码。
    输入文档示例
{
    "header":
    {
        "header1":"value1",
        "header2":"value2",
        "header3":"value3"
    },
    "body":
    {
        "obj-1":
        {
            "id":"obj-1",
            "name":"obj-1-name",
            "description":"obj-1-description"
        },
        "obj-2":
        {
            "id":"obj-2",
            "name":"obj-2-name",
            "description":"obj-2-description"
        },
        "obj-3":
        {
            "id":"obj-3",
            "name":"obj-3-name",
            "description":"obj-3-description"
        },
        "obj-4":
        {
            "id":"obj-4",
            "name":"obj-4-name",
            "description":"obj-4-description"
        }
    }
}

输出文件范例

{
    "header":
    {
        "header1":"value1",
        "header2":"value2",
        "header3":"value3"
    },  
    "object":
    {
        "id":"obj-1",
        "name":"obj-1-name",
        "description":"obj-1-description"
    }
}

已经为“header”对象、“body”JSON对象中的各个元素和输出文档创建了POJO。
使用以下内容作为初始解决问题的垫脚石https://howtodoinjava.com/gson/jsonreader-streaming-json-parser/,我的理解是,由于存在JSON结构的转换,因此我需要执行基本的3步过程;只是将其转换为GSON特定函数?

uyto3xhc

uyto3xhc1#

正如在链接的教程中提到的,当你想以流的方式处理JSON数据时,你应该使用JsonReader,然后你可以使用从Gson获得的相应的TypeAdapter示例作为你的POJO类,并使用它们来解析头部和单个主体对象。
您也可以使用Gson.fromJson(JsonReader, ...)方法,而不是直接使用TypeAdapter示例;然而Gson不幸地不尊重宽大处理设置/总是让读者宽大处理。除非你明确需要这样做,否则我建议你不要这样做,而是直接使用TypeAdapter示例,因为这样就尊重了JsonReader的宽大处理设置。
假设您有以下POJO类:

public class Header {
  public String header1;
  public String header2;
  public String header3;
}

public class BodyObject {
  public String id;
  public String name;
  public String description;
}

public class OutputDocument {
  public Header header;
  public BodyObject object;
}

然后你可以创建一个方法来创建一个Stream<OutputDocument>,如下所示。在这里使用Stream的好处是它的close方法可以用来关闭提供JSON数据的Reader。然而,它也可以用类似的方法使用Iterator来实现。

/**
 * Creates a {@link Stream} which transforms the data to {@link OutputDocument} elements.
 * 
 * <p><b>Important:</b> The provided reader will be closed by this method, or by the created
 * stream. It is therefore necessary to call {@link Stream#close()} (for example by using a
 * try-with-resources statement).
 * 
 * @param inputDocumentReader JSON data stream
 * @param gson Gson object used for looking up adapters
 * @return Stream of transformed elements
 */
public static Stream<OutputDocument> transform(Reader inputDocumentReader, Gson gson) throws IOException {
  JsonReader jsonReader = new JsonReader(inputDocumentReader);
  try {
    jsonReader.beginObject();
    String headerProperty = jsonReader.nextName();
    if (!headerProperty.equals("header")) {
      throw new IllegalArgumentException("Expected 'header' property at " + jsonReader.getPath());
    }

    // Read the Header
    TypeAdapter<Header> headerAdapter = gson.getAdapter(Header.class);
    Header header = headerAdapter.read(jsonReader);

    String bodyProperty = jsonReader.nextName();
    if (!bodyProperty.equals("body")) {
      throw new IllegalArgumentException("Expected 'body' property at " + jsonReader.getPath());
    }

    // Start reading body
    jsonReader.beginObject();
    TypeAdapter<BodyObject> bodyObjectAdapter = gson.getAdapter(BodyObject.class);

    long estimatedSize = Long.MAX_VALUE; // unknown size
    // Could also add `| NONNULL`, if there are no null body objects
    int characteristics = Spliterator.Spliterator.ORDERED;
    Spliterator<OutputDocument> spliterator = new AbstractSpliterator<>(estimatedSize, characteristics) {
      @Override
      public boolean tryAdvance(Consumer<? super OutputDocument> action) {
        try {
          // Check if end of 'body' object has been reached
          if (!jsonReader.hasNext()) {
            // End 'body'
            jsonReader.endObject();
            // End top level object
            jsonReader.endObject();

            if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
              throw new IllegalStateException("Expected end of JSON document at " + jsonReader.getPath());
            }
            // Reached end
            return false;
          } else {
            // Skip entry name
            jsonReader.skipValue();

            BodyObject object = bodyObjectAdapter.read(jsonReader);

            // Create combined OutputDocument
            OutputDocument result = new OutputDocument();
            result.header = header;
            result.object = object;

            action.accept(result);
            return true;
          }
        } catch (IOException e) {
          throw new UncheckedIOException("Failed reading next element", e);
        }
      }
    };
    return StreamSupport.stream(spliterator, false) // false, don't create parallel stream
        .onClose(() -> {
          try {
            jsonReader.close();
          } catch (IOException e) {
            throw new UncheckedIOException("Failed closing JsonReader", e);
          }
        });
  }
  catch (Exception e) {
    try {
      jsonReader.close();
    } catch (IOException suppressed) {
      e.addSuppressed(suppressed);
    }
    throw e;
  }
}

然后可以像这样调用此方法:

try (Stream<OutputDocument> stream = transform(inputDocumentReader, new Gson())) {
    ...
}

inputDocumentReader是您为InputDocument文件创建的ReaderGson示例可以是新示例(如上面的示例所示),也可以是您使用GsonBuilder创建的示例(如果您注册了自定义适配器以自定义POJO类或其字段的反序列化方式)。

相关问题