JSON剥空心菜(用JS遍历JSON所有节点,在控制台打印用户友好的JSON结构树)

项目需要,要遍历以下JSON数据,以可读性好的方式展示给用户。

var request = {
    "sheet": {
        "一般体格检查及生命体征": [
            {
                "清点器械": true,
                "自我介绍": true,
                "观察一般状态": {},
                "洗手消毒": true
            },
            {
                "测体温": {
                    "status": true,
                    "duration": {
                        "value": 10,
                        "unit": "min"
                    }
                },
                "测血压": true,
                
                // 至少30秒,注意两侧对比
                "测脉搏": true,

                "呼吸": true
            }
        ],
        "头颈部": [
            {
                "观察头部": {
                    "头部外形": "正常",
                    "毛发分布": "正常",
                    "异常运动": {}
                },
                "触诊头颅": {}
            },
            {
                "视诊": {
                    "双眼": {},
                    "眉毛": {}
                },
                "检查": {
                    "下睑结膜": {},
                    "球结膜": {},
                    "巩膜": {}
                },
                "翻转上睑": {
                    "检查": {
                        "上睑结膜": {},
                        "球结膜": {},
                        "巩膜": {}
                    }
                },

                // 6 个方向
                "眼球运动": ["左","左上","左下","右","右上","右下"],
                "眼球震颤": false,
                "对光反射": ["直接","间接"],
                "集合反射": true
            },
            {
                "视诊": ["外耳","外耳道"],
                "触诊": ["耳廓","乳突"]
            },
            {
                "视诊": ["外鼻","鼻前庭","鼻中隔"],
                "触诊": ["外鼻"],
                "压痛": ["上颌窦","额窦","筛窦"]
            },
            {
                "视诊": ["口","唇","牙齿","上颚","舌质","舌苔"],
                "借助压舌板观察": ["颊粘膜","牙齿","牙龈","口底"],
                "口咽部": true,
                "扁桃体": true
            },
            {
                "视诊": {
                    "颈部": ["外形","皮肤","颈静脉充盈","颈动脉搏动"]
                },
                "颈部活动": true,
                "触诊": {
                    "颈部淋巴结": ["耳前","耳后","枕后","颌下","颏下","颈前","颈后","锁骨上"],
                    "甲状腺": {},
                    "颈动脉": {},
                    "气管位置": {}
                }
            }
        ],
        "前侧胸部": {
            "肺部": {
                "视诊": {
                    "胸廓": ["外形","对称性","皮肤"],
                    "呼吸": ["运动","频率","节律","深度"]
                }
            },
            "心脏": {}
        },
        "背部(病人坐起)": {},
        "腹部(病人躺下)": {},
        "上肢": {},
        "下肢": {},
        "神经反射": {},
        "步态与腰椎运动": {}
    }
}


最终效果应该要类似下面这样子:

|    1 一般体格检查及生命体征
|    |    1.1
|    |    |    1.1.1 清点器械
|    |    |    |    true
|    |    |    1.1.2 自我介绍
|    |    |    |    true
|    |    |    1.1.3 观察一般状态
|    |    |    1.1.4 洗手消毒
|    |    |    |    true
|    |    1.2
|    |    |    1.2.1 测体温
|    |    |    |    1.2.1.1 status
|    |    |    |    |    true
|    |    |    |    1.2.1.2 duration
|    |    |    |    |    1.2.1.2.1 value
|    |    |    |    |    |    10
|    |    |    |    |    1.2.1.2.2 unit
|    |    |    |    |    |    min
|    |    |    1.2.2 测血压
|    |    |    |    true
|    |    |    1.2.3 测脉搏
|    |    |    |    true
|    |    |    1.2.4 呼吸
|    |    |    |    true
|    2 头颈部
|    |    2.1
|    |    |    2.1.1 观察头部
|    |    |    |    2.1.1.1 头部外形
|    |    |    |    |    正常
|    |    |    |    2.1.1.2 毛发分布
|    |    |    |    |    正常
|    |    |    |    2.1.1.3 异常运动
|    |    |    2.1.2 触诊头颅
|    |    2.2
|    |    |    2.2.1 视诊
|    |    |    |    2.2.1.1 双眼
|    |    |    |    2.2.1.2 眉毛
|    |    |    2.2.2 检查
|    |    |    |    2.2.2.1 下睑结膜
|    |    |    |    2.2.2.2 球结膜
|    |    |    |    2.2.2.3 巩膜
|    |    |    2.2.3 翻转上睑
|    |    |    |    2.2.3.1 检查
|    |    |    |    |    2.2.3.1.1 上睑结膜
|    |    |    |    |    2.2.3.1.2 球结膜
|    |    |    |    |    2.2.3.1.3 巩膜
|    |    |    2.2.4 眼球运动
|    |    |    |    2.2.4.1
|    |    |    |    |    左
|    |    |    |    2.2.4.2
|    |    |    |    |    左上
|    |    |    |    2.2.4.3
|    |    |    |    |    左下
|    |    |    |    2.2.4.4
|    |    |    |    |    右
|    |    |    |    2.2.4.5
|    |    |    |    |    右上
|    |    |    |    2.2.4.6
|    |    |    |    |    右下
|    |    |    2.2.5 眼球震颤
|    |    |    |    false
|    |    |    2.2.6 对光反射
|    |    |    |    2.2.6.1
|    |    |    |    |    直接
|    |    |    |    2.2.6.2
|    |    |    |    |    间接
|    |    |    2.2.7 集合反射
|    |    |    |    true
|    |    2.3
|    |    |    2.3.1 视诊
|    |    |    |    2.3.1.1
|    |    |    |    |    外耳
|    |    |    |    2.3.1.2
|    |    |    |    |    外耳道
|    |    |    2.3.2 触诊
|    |    |    |    2.3.2.1
|    |    |    |    |    耳廓
|    |    |    |    2.3.2.2
|    |    |    |    |    乳突
|    |    2.4
|    |    |    2.4.1 视诊
|    |    |    |    2.4.1.1
|    |    |    |    |    外鼻
|    |    |    |    2.4.1.2
|    |    |    |    |    鼻前庭
|    |    |    |    2.4.1.3
|    |    |    |    |    鼻中隔
|    |    |    2.4.2 触诊
|    |    |    |    2.4.2.1
|    |    |    |    |    外鼻
|    |    |    2.4.3 压痛
|    |    |    |    2.4.3.1
|    |    |    |    |    上颌窦
|    |    |    |    2.4.3.2
|    |    |    |    |    额窦
|    |    |    |    2.4.3.3
|    |    |    |    |    筛窦
|    |    2.5
|    |    |    2.5.1 视诊
|    |    |    |    2.5.1.1
|    |    |    |    |    口
|    |    |    |    2.5.1.2
|    |    |    |    |    唇
|    |    |    |    2.5.1.3
|    |    |    |    |    牙齿
|    |    |    |    2.5.1.4
|    |    |    |    |    上颚
|    |    |    |    2.5.1.5
|    |    |    |    |    舌质
|    |    |    |    2.5.1.6
|    |    |    |    |    舌苔
|    |    |    2.5.2 借助压舌板观察
|    |    |    |    2.5.2.1
|    |    |    |    |    颊粘膜
|    |    |    |    2.5.2.2
|    |    |    |    |    牙齿
|    |    |    |    2.5.2.3
|    |    |    |    |    牙龈
|    |    |    |    2.5.2.4
|    |    |    |    |    口底
|    |    |    2.5.3 口咽部
|    |    |    |    true
|    |    |    2.5.4 扁桃体
|    |    |    |    true
|    |    2.6
|    |    |    2.6.1 视诊
|    |    |    |    2.6.1.1 颈部
|    |    |    |    |    2.6.1.1.1
|    |    |    |    |    |    外形
|    |    |    |    |    2.6.1.1.2
|    |    |    |    |    |    皮肤
|    |    |    |    |    2.6.1.1.3
|    |    |    |    |    |    颈静脉充盈
|    |    |    |    |    2.6.1.1.4
|    |    |    |    |    |    颈动脉搏动
|    |    |    2.6.2 颈部活动
|    |    |    |    true
|    |    |    2.6.3 触诊
|    |    |    |    2.6.3.1 颈部淋巴结
|    |    |    |    |    2.6.3.1.1
|    |    |    |    |    |    耳前
|    |    |    |    |    2.6.3.1.2
|    |    |    |    |    |    耳后
|    |    |    |    |    2.6.3.1.3
|    |    |    |    |    |    枕后
|    |    |    |    |    2.6.3.1.4
|    |    |    |    |    |    颌下
|    |    |    |    |    2.6.3.1.5
|    |    |    |    |    |    颏下
|    |    |    |    |    2.6.3.1.6
|    |    |    |    |    |    颈前
|    |    |    |    |    2.6.3.1.7
|    |    |    |    |    |    颈后
|    |    |    |    |    2.6.3.1.8
|    |    |    |    |    |    锁骨上
|    |    |    |    2.6.3.2 甲状腺
|    |    |    |    2.6.3.3 颈动脉
|    |    |    |    2.6.3.4 气管位置
|    3 前侧胸部
|    |    3.1 肺部
|    |    |    3.1.1 视诊
|    |    |    |    3.1.1.1 胸廓
|    |    |    |    |    3.1.1.1.1
|    |    |    |    |    |    外形
|    |    |    |    |    3.1.1.1.2
|    |    |    |    |    |    对称性
|    |    |    |    |    3.1.1.1.3
|    |    |    |    |    |    皮肤
|    |    |    |    3.1.1.2 呼吸
|    |    |    |    |    3.1.1.2.1
|    |    |    |    |    |    运动
|    |    |    |    |    3.1.1.2.2
|    |    |    |    |    |    频率
|    |    |    |    |    3.1.1.2.3
|    |    |    |    |    |    节律
|    |    |    |    |    3.1.1.2.4
|    |    |    |    |    |    深度
|    |    3.2 心脏
|    4 背部(病人坐起)
|    5 腹部(病人躺下)
|    6 上肢
|    7 下肢
|    8 神经反射
|    9 步态与腰椎运动

可以看到,这种JSON对象极不规则,代码如何写得简洁在我脑中困难地凝练着。想起以前也有类似的痛苦经历,如何递归一层又一层的对象花费了我好几个小时。第二次接触这种项目当然也费了不少时间,好在最后理清了思路。嗯,这是目前看起来最优雅的代码了。

function exploit(JSONObject){
    var layer = 1;
    var layer_arr = new Array();
    var pad = "|    ";
    reveal(JSONObject);

    function reveal(JSONObject) {
        var key_count = 0;
    
        for ( key in JSONObject ) {
            key_count ++
            layer_arr.push(key_count);

            var serial = pad.repeat(layer) + layer_arr.join('.') + " ";
            if ( ! isNaN(parseInt(key)) ) {
                console.log( serial );
            } else {
                console.log( serial + key );
            }
            
            layer ++
    
            if ( typeof(JSONObject[key]) == 'object' & JSONObject[key] != {} ) {
                var previous_count = key_count;
                reveal(JSONObject[key]);
                key_count = previous_count;
            } else {
                console.log( pad.repeat( layer ) + JSONObject[key] );
            }

            layer --
            layer_arr.pop();
        }
    }
}

exploit(request['sheet']);


输出已经在上面的最终效果那里给出,就不赘述了。


总结一下这类问题的关键点:

1. 函数调用自身。如上面的reveal函数在合适的时候调用了自己,每次只剥开一层,但能递归地剥开下一层,因此能遍历整个JSON。

2. 灵活运用局部变量和全局变量。经过反复思考,我把key_count作为局部变量,在每次调用reveal函数,也就是进入下一层遍历的时候都能归零,也就是重新进行编号;在进入下一层之前,使用一个最局部的变量previous_count保存遍历的进度信息;layer和layer_arr是遍历过程中遍历深度的记录信息,放在函数外作为全局变量;pad是缩进所用的字符串,不需要随着函数的调用而变化,因此也作为全局变量。这些变量在代码中的位置稍有偏差就不能达到想要的效果,可谓是“失之毫厘,谬以千里”。


呼~终于能用更短的代码实现这一功能了。个人感觉这个函数的扩展性很大,在我搜索的文章中似乎还没有人实现过这样的功能(不然我为什么要忍痛自己写呢?),想跟大家分享一下。如果大家还有什么更好的办法,或者已经有函数实现了上述功能,欢迎在评论区留言!


最后,我给我的实现方法起了个名字——“剥空心菜”,是因为启示我这样做的是初中世界历史课上做题遇到的一句话:

他摆弄这个国家就像家庭主妇摆弄卷心菜一样 他以为只要把外面的烂叶子剥掉就会有里边的好芯子 他不停地剥下去 一直到剥光为止
—— 某苏联杂志主编评价戈尔巴乔夫



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值