JSON解析拍平工具
背景:
问题:在做数据清洗的时候,拿到的数据是爬虫爬取的数据,存在数据结构不一致且同
时包含JSON对象和JSON数组,以及JSON嵌套,数据结构复杂,没法统一处理,所以
希望有个工具能把获取到的数据做个自动的拍平和数组的拆分,给后面做数据转换和清
洗准备好数据
解决:整体思路是走递归处理,在拿到json的时候,需要判断每个字段的内容类型
(数组、对象、字段),对应不同的操作。在操作过程中需要把父节点的json串信息
往下传,因为会涉及到json数组拆分,这样一条json会被拆分成
(数组大小的倍数N*当前条M)数条,如果有数组嵌套,一直往下乘。拆分好json对往list里面输出,判断输出的依据就是,处理到当前json的字段都没有json对象和json数组的时候,说明json已经到底了
代码:
class JsonFlattener1 (val node: AnyRef) {
val father_json=new JSONObject()
var json_list=new util.ArrayList[JSONObject]
val column_set=new util.HashSet[String]()
// val column_set:mutable.HashSet[String]=new mutable.HashSet()
def flatten1(): util.ArrayList[JSONObject] = {
process(node, father_json,"",column_set)
return json_list
}
def process(node: AnyRef, common: JSONObject,prefix:String,common_column:util.HashSet[String]){
node match {
case x: JSONObject => {
// var obj_list=new util.ArrayList[JSONObject]
var obj_map=new mutable.HashMap[String,JSONObject]()
var obj_common=common//这里不做深拷贝,这样在多json循环处理的时候父json会一直变
var self_set=new util.HashSet[String]()
common_column.forEach(aa=>{
self_set.add(aa)
})
var obj_count=0
var count = 0//用count来标记当前json的各个字段是否包含json对象或者json数组,如果处理完count还是0那么说明这个json已经处理完了,后面不会有嵌套的json,所以当作输出的标志
//在这里处理解决的是,在提前知道下一级目录的情况下,把字段装进json,这里按顺序处理字段,json对象,json数组
x.entrySet().asScala.foreach(ele => {
val (k, v) = (ele.getKey, ele.getValue)
if (!v.isInstanceOf[JSONObject] && !v.isInstanceOf[JSONArray]) //字段不包含json
{
// 处理同名的列
if (self_set.contains(k)){
obj_common.put((prefix+"/"+k), v)
self_set.add(prefix+"/"+k)
}
else{
obj_common.put(k, v)
self_set.add(k)
}
//这里处理数组里面是字符串或者数字的(非json)
}else if(ele.getValue.isInstanceOf[JSONArray] && ele.getValue.asInstanceOf[JSONArray].size()>0){
if(!ele.getValue.asInstanceOf[JSONArray].get(0).isInstanceOf[JSONObject]){
// 处理同名的列
if (self_set.contains(k) ){
obj_common.put((prefix+"/"+k), v)
self_set.add(prefix+"/"+k)
}
else{
obj_common.put(k, v)
self_set.add(k)
}
}
}
})
//字段包含json对象
x.entrySet().asScala.foreach(ele => {
val (k, v) = (ele.getKey, ele.getValue)
if (v.isInstanceOf[JSONObject])
{
obj_count+=1
count += 1
obj_map.put(k,parseCopyObj(v.asInstanceOf[JSONObject]))
// obj_list.add(parseCopyObj(v.asInstanceOf[JSONObject]))
}
})
//处理同一级json多个字段都是json的情况,
if (obj_count>0){
process(extendObj(obj_map,prefix), obj_common,prefix,self_set)
}
x.entrySet().asScala.foreach(ele => {
val (k, v) = (ele.getKey, ele.getValue)
if (v.isInstanceOf[JSONArray] && v.asInstanceOf[JSONArray].size()>0 ) //字段包含json数组
{
if (v.asInstanceOf[JSONArray].get(0).isInstanceOf[JSONObject]){
count += 1
process(v, obj_common,prefix+"/"+k,self_set)
}
}
})
if (count == 0) {
json_list.add(parseCopyObj(obj_common))
}
}
case x: JSONArray => {
if(x.size()>0 ){//只处理非空的json数组
var arr_map=new mutable.HashMap[String,JSONObject]()
// var arr_list=new util.ArrayList[JSONObject]
var ar_count = 0
var obj_count = 0
for (i <- 0 until x.size()) {
println(i+"——————————"+common_column)
process(x.get(i),common, prefix,common_column)
}
}else{
json_list.add(parseCopyObj(common))
}
}
}
}
//json深拷贝,在json传递的时候如果不做深拷贝,那么赋值的是变量地址
def parseCopyObj(obj:JSONObject):JSONObject={
val outObj=new JSONObject()
obj.entrySet().asScala.foreach(ee=>{
outObj.put(ee.getKey,ee.getValue)
})
outObj
}
//obj2复制到obj1
//这里的prefix不能做返回,因为json是多个json拼接的,所以prefix是不一样的,不能做统一返回,只能用上一层级的prefix,这会导致一个问题
//在多层json对象嵌套的时候,当前层的json只能获取到父节点的字段名,获取不到爷爷节点的字段名,这里需要注意
def extendObj(obj_map:mutable.HashMap[String,JSONObject],prefix:String):JSONObject={
var self_set=new util.HashSet[String]()
column_set.forEach(aa=>{
self_set.add(aa)
})
var trace=""
if(prefix.isEmpty)trace else trace=prefix+"/"
val outObj=new JSONObject()
obj_map.iterator.foreach(el=>{
el._2.entrySet().asScala.foreach(ee=>{
if (self_set.contains(ee.getKey)){
outObj.put((trace+el._1+"/"+ee.getKey),ee.getValue)
self_set.add(trace+el._1+"/"+ee.getKey)
}else{
outObj.put(ee.getKey,ee.getValue)
self_set.add(ee.getKey)
}
})
})
outObj
}
}
难点:
1、判断何时递归停止做数据输出:
这个逻辑需要考虑在处理字段的时候,其他字段的值可能含有json对象或者json数组,所以对一个json的处理的要遍历三次字段,判断字段包含的值(顺序分别是正常值、json对象、json数组),保证处理完当前json的字段后再处理嵌套的json。
2、处理同名字段:
在json嵌套的过程中,会出现同名字段,这这时候需要把重名的字段全路径标出来》
总结
代码可以直接用,参数传的是json