问题描述
我的目标是使用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();
}
}