场景:假设我们有个文本文件内容如下,可能数据非常非常多(300M),我们要将json结构内的res数组中的数据导入数据库之中,如何解析出来
重点是每个json文件内的res数据都不一样,key值也不一样,如何写一个公用的解析方法,我们只需要指定key就能自动匹配。
{
"status":"200",
"res":[
{
"province":"山东省",
"city":"潍坊市",
"name":"兰州拉面馆",
"prop":{
"type":"餐饮单位",
"level":1
},
"month_data":[
{"month":"1","amount":18518.5},
{"month":"2","amount":18519.5},
{"month":"3","amount":18517.5},
{"month":"4","amount":18516.5}
]
},
{
"province":"山东省",
"city":"淄博市",
"name":"电瓶批发",
"prop":{
"type":"其它",
"level":2
},
"month_data":[
{"month":"1","amount":28518.5},
{"month":"2","amount":28519.5},
{"month":"3","amount":28517.5},
{"month":"4","amount":28516.5}
]
}
]
}
如果read出来 然后 JsonUtils 工具类转换 list<Bean> 内容一旦很多,就容易java heap space
我的办法
我采用的是com.google.gson.stream.JsonReader这个工具类是按照符号顺序读取的,不是一下加载到内存中的。
测试入口
public static void main(String[] args) throws Exception {
// 创建jsonReader
JsonReader jsonReader = new JsonReader(new FileReader(new File("C:\\Users\\Wsong\\Desktop\\test.json")));
// 定位到指定的数组节点 如果本身就是个数组 传入$,如果本身是个对象可以传入null
findRootArray(jsonReader, "$.res");
// 传入jsonReader,传入key值
ArrayList<List<String>> lists = jsonArr2list(jsonReader, Arrays.asList("$.province", "$.city", "$.name", "$.prop.type", "$.prop.level"));
// 得到每一行的数组结果,我这里为了讲明白,没有设置数据类型 都用的string
System.out.println(lists);
}
结果
[[山东省, 潍坊市, 兰州拉面馆, 餐饮单位, 1, 18516.5], [山东省, 淄博市, 电瓶批发, 其它, 2, 28516.5]]
代码
package com.ws.tools;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import com.ws.tools.dto.ColumnEntry;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.Reader;
import java.util.*;
public class JsonReaderUtils {
private static Gson gson = new Gson();
public static void main(String[] args) throws Exception {
// 创建jsonReader
JsonReader jsonReader = new JsonReader(new FileReader(new File("C:\\Users\\Wsong\\Desktop\\test.json")));
// 定位到指定的数组节点
findRootArray(jsonReader, "$.res");
// 传入jsonReader,传入key值
ArrayList<List<String>> lists = jsonArr2list(jsonReader, Arrays.asList("$.province", "$.city", "$.name", "$.prop.type","$.month_data[3].amount"));
// 得到每一行的数组结果,我这里为了讲明白,没有设置数据类型 都用的string
System.out.println(lists);
}
public static ArrayList<List<String>> jsonArr2list(JsonReader jsonReader, List<String> jsonKeys) throws Exception {
JsonToken peek;
int startObj = 0;
int endObj = 0;
// 组装最后的结果
ArrayList<List<String>> res = new ArrayList<>();
StringBuilder sb = new StringBuilder();
// 防止循环无法跳出
int i = 0;
while (true) {
// 这个方法是返回json文件中下一个标签
peek = jsonReader.peek();
// 如果下一个标签是9 代表json文档结束 就退出循环了返回了
if (peek.ordinal() == 9 || i > 10000000) {
break;
}
// 遇到一个"{" startObj++
if (peek.ordinal() == 2) {
startObj++;
}
// 遇到一个"}" endObj++
if (peek.ordinal() == 3) {
endObj++;
}
// 读取到每个标签
String val = peekAndRead(peek, jsonReader);
// 对数据进行处理,看不懂可以注掉这段代码
if (StringUtils.endsWith(sb, "{")) {
val = StringUtils.removeStart(val, ",");
}
// 拼接 json
sb.append(val);
// 如果 遇到的 "{" 和遇到的 "}" 数量相同 说明一个对象已经读取完成,该读下一个对象了
if (startObj == endObj && startObj != 0) {
// 解析到的数据
List<String> parser = parser(sb.toString(),jsonKeys);
res.add(parser);
sb = new StringBuilder();
startObj = 0;
endObj = 0;
i = 0;
}
i++;
}
return res;
}
private static List<String> parser(String json,List<String> columns)
{
List<String> splitLine = new ArrayList<>();
// jsonpath 解析支持 $.key $key1.$key2 $key[0].$key1
DocumentContext document = JsonPath.parse(json);
String tempValue;
for (String columnIndex : columns) {
try {
tempValue = document.read(columnIndex,String.class);
}
catch (Exception ignore) {
ignore.printStackTrace();
tempValue = null;
}
splitLine.add(tempValue);
}
return splitLine;
}
private static void findRootArray(JsonReader jsonReader, String arryRootKey) throws Exception {
// arryRootKey==$ 表明,数据根节点就是个数组,直接开启数组
if ("$".equalsIgnoreCase(arryRootKey)) {
jsonReader.beginArray();
} else if (StringUtils.isBlank(arryRootKey)) {
// 如果 arryRootKey==$ 说明是个对象,不用管
} else {
// 按层级查询 数组节点
String[] keys = StringUtils.removeStart(arryRootKey, "$.").split("\\.");
for (String s : keys) {
JsonToken peek;
int i = 10000;
while (jsonReader.hasNext()) {
peek = jsonReader.peek();
if (peek.ordinal() == 4) {
if (jsonReader.nextName().equalsIgnoreCase(s)) {
jsonReader.beginArray();
break;
}
} else {
peekAndRead(peek, jsonReader);
}
i--;
if (i == 0) {
throw new Exception("json key 解析异常");
}
}
}
}
}
/**
* 将peek,转成字符 用来拼接每个对象的json
*/
private static String peekAndRead(JsonToken p, JsonReader jsonReader) throws Exception {
switch (p) {
case BEGIN_OBJECT:
jsonReader.beginObject();
return "{";
case END_OBJECT:
jsonReader.endObject();
return "}";
case BEGIN_ARRAY:
jsonReader.beginArray();
return "[";
case END_ARRAY:
jsonReader.endArray();
return "]";
case NAME:
return ",\"" + jsonReader.nextName() + "\":";
case BOOLEAN:
return jsonReader.nextBoolean() + "";
case NULL:
jsonReader.nextNull();
return "null";
case STRING:
case NUMBER:
return "\"" + jsonReader.nextString() + "\"";
case END_DOCUMENT:
throw new Exception("json文件解析已经完成~");
default:
throw new Exception("不认识的json符号 [" + p.ordinal() + "]");
}
}
}