使用Spring Batch读取非常复杂的JSON

问题描述

我的目标是使用Spring Batch读取非常复杂的JSON。以下是示例JSON。

{
  "order-info" : {
  "order-number" : "Test-Order-1"
  "order-items" : [
  {
   "item-id" : "4144769310"
   "categories" : [
    "ABCD","DEF"
   ],"item_imag" : "http:// "
   "attributes: {
      "color" : "red"

   },"dimensions" : {

   },"vendor" : "abcd",},{
    "item-id" : "88888","categories" : [
    "ABCD",.......

我知道我需要创建一个Custom ItemReader来解析此JSON。 请给我一些指示。我真的很笨。

我现在不使用CustomItemReader。我正在使用Java POJO。我的JsonItemReader如下:

@Bean
 public JsonItemReader<Trade> jsonItemReader() {

        ObjectMapper objectMapper = new ObjectMapper();

       JacksonjsonObjectReader<Trade> jsonObjectReader =
                new JacksonjsonObjectReader<>(Trade.class);

       jsonObjectReader.setMapper(objectMapper);

        return new JsonItemReaderBuilder<Trade>()
                .jsonObjectReader(jsonObjectReader)
                .resource(new ClassPathResource("search_data_1.json"))
                .name("TradeJsonItemReader")
                .build();
}

我现在得到的异常是:

java.lang.IllegalStateException:Json输入流必须以Json对象数组开头

从该论坛的类似帖子中,我了解到我需要使用JsonObjectReader。 “ 您可以实现它以读取单个json对象,并将其与JsonItemReader一起使用(在构造时或使用setter来使用 ”。)

如何在构建时间或使用setter来执行此操作?请分享一些相同的代码段。

MultiResourceItemReader的委托仍应为JsonItemReader。您只需要使用带有JsonItemReader的自定义JsonObjectReader而不是JacksonjsonObjectReader。从外观上看,应该是:MultiResourceItemReader---> JsonItemReader的委托-使用->您的自定义JsonObjectReader。

能否请您分享以上代码段?

解决方法

JacksonJsonItemReader 用于解析数组节点,因此它希望您的 json 以 '[' 开头。

如果你想解析一个复杂的对象,你应该编写一个阅读器。这样做真的很简单,您可以遵循 JacksonJsonObjectReader 的结构。以下是具有相应单元测试的复杂对象的读取器示例 - 将“mySubRoot”和“myDataset”替换为您的实际属性名称。

单元测试

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.springframework.core.io.ByteArrayResource;

import com.example.DatasetObjectReader;
import com.example.EmptyArrayNodeException;
import com.example.UnreachableNodeException;

@RunWith(BlockJUnit4ClassRunner.class)
public class DatasetObjectReaderTest {

@Test
public void shouldRead_ResultAsRootNode() throws Exception {
    DatasetObjectReader reader = new DatasetObjectReader();
    reader.open(new ByteArrayResource("{\"mySubRoot\":{\"myDataset\":[{\"id\":\"a\"}]}}".getBytes()) {});
    Assert.assertTrue(reader.getDatasetNode().isArray());
    Assert.assertFalse(reader.getDatasetNode().isEmpty());
}

@Test
public void shouldThrowException_OnNullNode() throws Exception {
    DatasetObjectReader reader = new DatasetObjectReader();
    boolean exceptionThrown = false;
    try {           
        reader.open(new ByteArrayResource("{}".getBytes()) {});
    } catch (UnreachableNodeException e) {
        exceptionThrown = true;
    }
    Assert.assertTrue(exceptionThrown);
}

@Test
public void shouldThrowException_OnEmptyNode() throws Exception {
    DatasetObjectReader reader = new DatasetObjectReader();
    boolean exceptionThrown = false;
    try {           
        reader.open(new ByteArrayResource("{\"mySubRoot\":{\"myDataset\":[]}}".getBytes()) {});
    } catch (EmptyArrayNodeException e) {
        exceptionThrown = true;
    }
    Assert.assertTrue(exceptionThrown);
}
}

和读者:

import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;

import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.json.JsonObjectReader;
import org.springframework.core.io.Resource;

import com.example.Dataset;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

/*
* This class follows the structure and functions similar to JacksonJsonObjectReader,with 
* the difference that it expects a object as root node,instead of an array.
*/
public class DatasetObjectReader implements JsonObjectReader<Dataset>{

public static final String SUB_ROOT_NODE = "mySubRoot";
public static final String DATASET_NODE = "myDataset";

Logger logger = Logger.getLogger(DatasetObjectReader.class.getName());

ObjectMapper mapper = new ObjectMapper();
private JsonParser jsonParser;
private InputStream inputStream;

private ArrayNode datasetNode;


public ArrayNode getDatasetNode() {
    return datasetNode;
}   

/*
 * JsonObjectReader interface has an empty default method and must be implemented in this case to set 
 * the mapper and the parser
 */
@Override
public void open(Resource resource) throws Exception {
    logger.info("Opening json object reader");
    this.inputStream = resource.getInputStream();
    JsonNode resultNode = this.mapper.readTree(this.inputStream).get(SUB_ROOT_NODE);
    if(resultNode != null) {
            this.datasetNode = (ArrayNode) resultNode.get(DATASET_NODE);
            if(datasetNode != null && !datasetNode.isEmpty()) {             
                this.jsonParser = this.mapper.getFactory().createParser(datasetNode.toString().replaceAll("^\\[|\\]$",""));
                logger.info("Reader open with parser reference: " + this.jsonParser);
            } else {
                throw new EmptyArrayNodeException();
            }
    } else {
        logger.severe("Couldn't read root node 'result'");
        throw new UnreachableNodeException();
    }
}

@Override
public Dataset read() throws Exception {
    try {
        if (this.jsonParser.nextToken() == JsonToken.START_OBJECT) {
            return this.mapper.readValue(this.jsonParser,Dataset.class);
        }
    } catch (IOException e) {
        throw new ParseException("Unable to read next JSON object",e);
    }
    return null;
}

@Override
public void close() throws Exception {
    this.inputStream.close();
    this.jsonParser.close();
}

}