static StaticDataTask fromJson(JsonNode jsonNode) {
    Preconditions.checkArgument(jsonNode != null, "Invalid JSON node for data task: null");
    Preconditions.checkArgument(
        jsonNode.isObject(), "Invalid JSON node for data task: non-object (%s)", jsonNode);

    Schema schema = SchemaParser.fromJson(JsonUtil.get(SCHEMA, jsonNode));
    Schema projectedSchema = SchemaParser.fromJson(JsonUtil.get(PROJECTED_SCHEMA, jsonNode));
    DataFile metadataFile =
        (DataFile)
            ContentFileParser.fromJson(
                JsonUtil.get(METADATA_FILE, jsonNode), PartitionSpec.unpartitioned());

    JsonNode rowsArray = JsonUtil.get(ROWS, jsonNode);
    Preconditions.checkArgument(
        rowsArray.isArray(), "Invalid JSON node for rows: non-array (%s)", rowsArray);

    StructLike[] rows = new StructLike[rowsArray.size()];
    for (int i = 0; i < rowsArray.size(); ++i) {
      JsonNode rowNode = rowsArray.get(i);
      rows[i] = (StructLike) SingleValueParser.fromJson(schema.asStruct(), rowNode);
    }

    return new StaticDataTask(metadataFile, schema, projectedSchema, rows);
  }
@Test
  public void missingFields() throws Exception {
    ObjectMapper mapper = new ObjectMapper();

    String missingSchemaStr = "{}";
    JsonNode missingSchemaNode = mapper.reader().readTree(missingSchemaStr);
    assertThatThrownBy(() -> DataTaskParser.fromJson(missingSchemaNode))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("Cannot parse missing field: schema");

    String missingProjectionStr =
        "{"
            + "\"schema\":{\"type\":\"struct\",\"schema-id\":0,"
            + "\"fields\":[{\"id\":1,\"name\":\"committed_at\",\"required\":true,\"type\":\"timestamptz\"}]}"
            + "}";
    JsonNode missingProjectionNode = mapper.reader().readTree(missingProjectionStr);
    assertThatThrownBy(() -> DataTaskParser.fromJson(missingProjectionNode))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("Cannot parse missing field: projection");

    String missingMetadataFileStr =
        "{"
            + "\"schema\":{\"type\":\"struct\",\"schema-id\":0,"
            + "\"fields\":[{\"id\":1,\"name\":\"committed_at\",\"required\":true,\"type\":\"timestamptz\"}]},"
            + "\"projection\":{\"type\":\"struct\",\"schema-id\":0,"
            + "\"fields\":[{\"id\":1,\"name\":\"committed_at\",\"required\":true,\"type\":\"timestamptz\"}]}"
            + "}";
    JsonNode missingMetadataFileNode = mapper.reader().readTree(missingMetadataFileStr);
    assertThatThrownBy(() -> DataTaskParser.fromJson(missingMetadataFileNode))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("Cannot parse missing field: metadata-file");

    String missingTableRowsStr =
        "{\"task-type\":\"data-task\","
            + "\"schema\":{\"type\":\"struct\",\"schema-id\":0,"
            + "\"fields\":[{\"id\":1,\"name\":\"committed_at\",\"required\":true,\"type\":\"timestamptz\"},"
            + "{\"id\":2,\"name\":\"snapshot_id\",\"required\":true,\"type\":\"long\"},"
            + "{\"id\":3,\"name\":\"parent_id\",\"required\":false,\"type\":\"long\"},"
            + "{\"id\":4,\"name\":\"operation\",\"required\":false,\"type\":\"string\"},"
            + "{\"id\":5,\"name\":\"manifest_list\",\"required\":false,\"type\":\"string\"},"
            + "{\"id\":6,\"name\":\"summary\",\"required\":false,\"type\":{\"type\":\"map\","
            + "\"key-id\":7,\"key\":\"string\",\"value-id\":8,"
            + "\"value\":\"string\",\"value-required\":true}}]},"
            + "\"projection\":{\"type\":\"struct\",\"schema-id\":0,"
            + "\"fields\":[{\"id\":1,\"name\":\"committed_at\",\"required\":true,\"type\":\"timestamptz\"},"
            + "{\"id\":2,\"name\":\"snapshot_id\",\"required\":true,\"type\":\"long\"},"
            + "{\"id\":3,\"name\":\"parent_id\",\"required\":false,\"type\":\"long\"},"
            + "{\"id\":4,\"name\":\"operation\",\"required\":false,\"type\":\"string\"},"
            + "{\"id\":5,\"name\":\"manifest_list\",\"required\":false,\"type\":\"string\"},"
            + "{\"id\":6,\"name\":\"summary\",\"required\":false,\"type\":{\"type\":\"map\","
            + "\"key-id\":7,\"key\":\"string\",\"value-id\":8,"
            + "\"value\":\"string\",\"value-required\":true}}]},"
            + "\"metadata-file\":{\"spec-id\":0,\"content\":\"DATA\","
            + "\"file-path\":\"/tmp/metadata2.json\","
            + "\"file-format\":\"METADATA\",\"partition\":{},"
            + "\"file-size-in-bytes\":0,\"record-count\":2,\"sort-order-id\":0}"
            + "}";
    JsonNode missingTableRowsNode = mapper.reader().readTree(missingTableRowsStr);
    assertThatThrownBy(() -> DataTaskParser.fromJson(missingTableRowsNode))
        .isInstanceOf(IllegalArgumentException.class)
        .hasMessageContaining("Cannot parse missing field: rows");
  }