用 Vue.js 实现一个 JSON Viewer
演示地址: http://json.imlht.com/vue-json-viewer-demo.html
常用的 JSON 格式化工具
JSON 是一种轻量级的数据交换格式, 相信大家用得比较多, 平时也用了很多格式化工具, 例如我最常用的 Json.cn, 还有这个 BeJson, 前者清爽无广告, 后者性能给力(有广告), 可以复制下面的 JSON 体验一下:
JSON 实例
{
"name": "小明",
"age": 24,
"gender": true,
"height": 1.85,
"weight": null,
"skills": [
{
"PHP": [
"Laravel",
"Composer"
]
},
{
"JavaScript": [
"jQuery",
"Vue",
"React"
]
},
"Golang",
"Python",
"Lua"
]
}
窥探 Json.cn 的实现
想自己实现一个 JSON 工具, 偷师是必不可少滴. 翻下 Json.cn 的源码, 发现是用 jQuery 写的, 代码量不多, 比较有用的就是缩进填充函数 indent_tab 还有类型判断函数 _typeof:
function indent_tab(indent_count) {
return (new Array(indent_count + 1)).join(' ');
}
function _typeof(object) {
var tf = typeof object,
ts = _toString.call(object);
return null === object ? 'Null' :
'undefined' == tf ? 'Undefined' :
'boolean' == tf ? 'Boolean' :
'number' == tf ? 'Number' :
'string' == tf ? 'String' :
'[object Function]' == ts ? 'Function' :
'[object Array]' == ts ? 'Array' :
'[object Date]' == ts ? 'Date' : 'Object';
};
当然, 样式我也抄了, 折叠看数组长度这个酷炫的想法也抄了哈哈! 折叠展开的实现可以看下函数 show 和 hide, 原理比较简单: 折叠的时候把 innerHTML 存进 data-inner, 展开的时候再写回去:
function hide(obj) {
var data_type = obj.parentNode.getAttribute('data-type');
var data_size = obj.parentNode.getAttribute('data-size');
obj.parentNode.setAttribute('data-inner',obj.parentNode.innerHTML);
if (data_type === 'array') {
obj.parentNode.innerHTML = '<i style="cursor:pointer;" class="fa fa-plus-square-o" onclick="show(this)"></i>Array[<span class="json_number">' + data_size + '</span>]';
} else {
obj.parentNode.innerHTML = '<i style="cursor:pointer;" class="fa fa-plus-square-o" onclick="show(this)"></i>Object{...}';
}
}
function show(obj) {
var innerHtml = obj.parentNode.getAttribute('data-inner');
obj.parentNode.innerHTML = innerHtml;
}
再看看函数 format: 根据值的类型和缩进层级返回字符串, 如果是 Array 或 Object, 将会递归调用: format -> _format_array -> format -> _format_object -> ...
function format(object, indent_count) {
var html_fragment = '';
switch (_typeof(object)) {
case 'Null':
0 html_fragment = _format_null(object);
break;
case 'Boolean':
html_fragment = _format_boolean(object);
break;
case 'Number':
html_fragment = _format_number(object);
break;
case 'String':
html_fragment = _format_string(object);
break;
case 'Array':
html_fragment = _format_array(object, indent_count);
break;
case 'Object':
html_fragment = _format_object(object, indent_count);
break;
}
return html_fragment;
};
function _format_null(object) {
return '<span class="json_null">null</span>';
}
function _format_boolean(object) {
return '<span class="json_boolean">' + object + '</span>';
}
function _format_number(object) {
return '<span class="json_number">' + object + '</span>';
}
function _format_string(object) {
object = object.replace(/\</g, "<");
object = object.replace(/\>/g, ">");
if (0 <= object.search(/^http/)) {
object = '<a href="' + object + '" target="_blank" class="json_link">' + object + '</a>'
}
return '<span class="json_string">"' + object + '"</span>';
}
function _format_array(object, indent_count) {
var tmp_array = [];
for (var i = 0,
size = object.length; i < size; ++i) {
tmp_array.push(indent_tab(indent_count) + format(object[i], indent_count + 1));
}
return '<span data-type="array" data-size="' + tmp_array.length + '"><i style="cursor:pointer;" class="fa fa-minus-square-o" onclick="hide(this)"></i>[<br/>' + tmp_array.join(',<br/>') + '<br/>' + indent_tab(indent_count - 1) + ']</span>';
}
function _format_object(object, indent_count) {
var tmp_array = [];
for (var key in object) {
tmp_array.push(indent_tab(indent_count) + '<span class="json_key">"' + key + '"</span>:' + format(object[key], indent_count + 1));
}
return '<span data-type="object"><i style="cursor:pointer;" class="fa fa-minus-square-o" onclick="hide(this)"></i>{<br/>' + tmp_array.join(',<br/>') + '<br/>' + indent_tab(indent_count - 1) + '}</span>';
}
递归返回组件
了解原理之后, 再回头想想该如何用 Vue.js 实现? 熟悉 Vue 官方文档的人应该会想到官方实例: 树形视图 Example, 它演示了组件的递归使用, 这次派上用场了! 因为格式化的原理是根据值的类型返回特定的字符串, 结合组件化的思想, 我们递归返回组件就可以了.
如何实现组件化
- JSON由key和val组成, 不妨将它们各自拆分为一个组件;
- JSON的每一行由key:val组成, key:val合并为item组件.
也就是说, item 父组件包含了 key 和 val 子组件, val 有多种类型, 如果是 Array 或 Object, 递归展开为 item 组件. 至于为什么叫 val 不叫 value 组件, 因为我强迫症啊哈哈哈! 都是3个字母看起来很顺眼.
OK, 瞎哔哔了这么多, 是时候看代码了. 定义(呸)抄一下缩进字符串和类型判断函数:
// 缩进字符串
var padstr = ' ';
// 返回给定value的类型
function valueType(value) {
var tf = typeof value;
var ts = Object.prototype.toString.call(value);
return value === null ? 'Null' :
'boolean' === tf ? 'Boolean' :
'number' === tf ? 'Number' :
'string' === tf ? 'String' :
'[object Array]' === ts ? 'Array' : 'Object';
}
什么鬼?! 第一个单词 var, 用 const 啊! 好吧我只是为了说明原理, 所以没有用 ES6/7 等高级特性, 没有 webpack 也没有 npm, 全部被我撸在一个 html 里了哈哈哈!
key 组件
组件 key 逻辑比较简单, key 用双引号 " 包起来, 如果是数组的 key, 那就不渲染. 另外再根据层级填充缩进字符即可:
<!-- key template -->
<script type="text/x-template" id="key-template">
<span class="key">
<span v-html="pad"></span><strong class="json_key" v-if="render">"{{jsonKey}}"</strong><template v-if="render">:</template>
</span>
</script>
<script>
Vue.component('key', {
template: '#key-template',
props: ['json-key', 'current-depth'],
computed: {
pad: function () {
return new Array(this.currentDepth+1).join(padstr);
},
render: function () {
return isNaN(this.jsonKey);
}
},
});
</script>
val 组件
组件 val 模板复杂了些. 如果是 Array 或 Object, 判断当前组件的 open 打开状态, 如果为 true, 渲染折叠 - 图标并递归渲染 item 组件, 否则渲染展开 + 图标, 并根据类型生成折叠后的字符串; 如果是 Null, String, Number 或 Boolean, 渲染带有样式的 span 标签, 如果不是最后一个元素渲染 , 逗号, 最后再渲染 <br> 标签:
<!-- val template -->
<script type="text/x-template" id="val-template">
<span class="val">
<template v-if="canToggle">
<template v-if="open">
<!-- Array -->
<template v-if="type === 'Array'"><i class="fa fa-minus-square-o" @click="toggle"></i>[<br>
<item class="item" :json-val="jsonVal" :current-depth="currentDepth+1" :max-depth="maxDepth"></item><span v-html="pad"></span>]<template v-if="!last">,</template><br>
</template>
<!-- Object -->
<template v-else-if="type === 'Object'"><i class="fa fa-minus-square-o" @click="toggle"></i>{<br>
<item class="item" :json-val="jsonVal" :current-depth="currentDepth+1" :max-depth="maxDepth"></item><span v-html="pad"></span>}<template v-if="!last">,</template><br>
</template>
</template>
<template v-else>
<!-- Array -->
<template v-if="type === 'Array'">
<i class="fa fa-plus-square-o" @click="toggle"></i><span class="json_hide">Array[<span class="json_number">{{jsonVal.length}}</span>]</span><template v-if="!last">,</template><br>
</template>
<!-- Object -->
<template v-else-if="type === 'Object'">
<i class="fa fa-plus-square-o" @click="toggle"></i><span class="json_hide">Object{<span class="json_string">...</span>}</span><template v-if="!last">,</template><br>
</template>
</template>
</template>
<template v-else>
<!-- Null -->
<template v-if="type === 'Null'">
<span class="json_null">null</span><template v-if="!last">,</template><br>
</template>
<!-- String -->
<template v-else-if="type === 'String'">
<span class="json_string">"{{jsonVal}}"</span><template v-if="!last">,</template><br>
</template>
<!-- Number -->
<template v-else-if="type === 'Number'">
<span class="json_number">{{jsonVal}}</span><template v-if="!last">,</template><br>
</template>
<!-- Boolean -->
<template v-else-if="type === 'Boolean'">
<span class="json_boolean">{{jsonVal ? 'true' : 'false'}}</span><template v-if="!last">,</template><br>
</template>
</template>
</span>
</script>
<script>
Vue.component('val', {
template: '#val-template',
props: ['json-val', 'current-depth', 'max-depth', 'last'],
data: function () {
return { open: this.currentDepth < this.maxDepth };
},
computed: {
pad: function () {
return new Array(this.currentDepth+1).join(padstr);
},
type: function () {
return valueType(this.jsonVal);
},
canToggle: function () {
return this.type === 'Array' || this.type === 'Object';
}
},
methods: {
toggle: function () {
this.open = !this.open;
}
}
});
</script>
item 组件
item 组件把 key 和 val 组件合起来就OK了:
<!-- item template -->
<script type="text/x-template" id="item-template">
<span>
<template v-for="(key, i) in keys">
<key :json-key="key" :current-depth="currentDepth"></key>
<val :last="i === keys.length-1"
:json-val="jsonVal[key]"
:current-depth="currentDepth"
:max-depth="maxDepth">
</val>
</template>
</span>
</script>
<script>
Vue.component('item', {
template: '#item-template',
props: ['json-key', 'json-val', 'current-depth', 'max-depth'],
computed: {
pad: function () {
return new Array(this.currentDepth).join(padstr);
},
type: function () {
return valueType(this.jsonVal);
},
keys: function () {
return Object.keys(this.jsonVal);
}
}
});
</script>
vm 实例
根组件没有 key, 所以 #vm 里面只有一个 val 组件. current-depth 为 0, 表示根节点, 无缩进层级, max-depth 表示初始化之后展示到第几层, 这里设为 3:
<!-- vm -->
<div id="vm">
<val :json-val="json" :current-depth="currentDepth" :max-depth="maxDepth" :last="true"></val>
</div>
<script>
var vm = new Vue({
el: '#vm',
data: {
currentDepth: 0,
maxDepth: 3,
json: {
"name": "小明",
"age": 24,
"gender": true,
"height": 1.85,
"weight": null,
"skills": [
{
"PHP": [
"Laravel",
"Composer"
]
},
{
"JavaScript": [
"jQuery",
"Vue",
"React"
]
},
"Golang",
"Python",
"Lua"
]
}
},
methods: {
getJson: function () {
return this.json;
},
setJson: function (json) {
this.json = json;
}
}
});
</script>
#vm 提供了 getJson 和 setJson 接口, getJson 返回当前实例的 JSON 对象, 看起来没什么卵用, 但它治好了我的强迫症; setJson 可以动态改变实例的 JSON 对象, 妈妈我再也不用 F5 刷新了, 按下键盘 F12 进入开发者工具的控制台, 然后 vm.setJson(...) 就可以看效果了.
存在的问题
目前没发现有 bug, 如果有的话麻烦告知, 谢谢! 性能上, 解析比较简单的 JSON 倒是可以, 层级多的或者体积大的 JSON 会特别慢, 可能消耗在递归上. 有兴趣的可以动手测试一下, 欢迎交流.
最后
分享一下自用的 JSON 解析工具, 绿色无广告, 解析速度飞快: http://json.imlht.com/index.html. 看了下时间, 凌晨1点! 睡觉睡觉! 晚安世界!
文章来源于本人博客,发布于 2017-12-21,原文链接:https://imlht.com/archives/88/
用 Vue.js 实现一个 JSON Viewer的更多相关文章
- vue.js 作一个用户表添加页面----初级
使用vue.js 制作一个用户表添加页面,实际上是把原来需要使用js写的部分,改写成vue.js的格式 首先,想象一下,先做思考,我们要添加用户表,设涉及到哪些数据,一个是用户id,一个是用户名,一个 ...
- Vue.js实现一个SPA登录页面的过程
技术栈 vue.js 主框架 vuex 状态管理 vue-router 路由管理 一般过程 在一般的登录过程中,一种前端方案是: 检查状态:进入页面时或者路由变化时检查是否有登录状态(保存在cooki ...
- 用Vue.js搭建一个小说阅读网站
目录 1.简介 2.如何使用vue.js 3.部署api服务器 4.vue.js路由配置 5.实现页面加载数据 6.测试vue项目 7.在正式环境部署 8.Vue前端代码下载 1.简介 这是一个使用v ...
- Vue.js写一个SPA登录页面的过程
技术栈 vue.js 主框架 vuex 状态管理 vue-router 路由管理 一般过程 在一般的登录过程中,一种前端方案是: 检查状态:进入页面时或者路由变化时检查是否有登录状态(保存在cooki ...
- 用Vue.js开发一个电影App的前端界面
我们要构建一个什么样的App? 我们大多数人使用在线流媒体服务(如Netflix)观看我们最喜欢的电影或者节目.这篇文章将重点介绍如何通过使用vue.js 2 建立一个类似风格的电影流媒体WEB交互界 ...
- vue.js定义一个一级的路由 ----由浅入深
#### 定义一个路由- 实例化一个路由并设置路由映射表 - 实例化里面第一个参数 routes 路由映射表 - routes 里面参数 - path 路由的路径 - component 路由对应的组 ...
- vue.js的package.json相关问题解惑
使用vue-cli创建vue.webpack项目,在项目中用到了iSlider移动端滑动插件,只在本地命令工具中npm install islider.js:提交之后,partner下载代码后一直运行 ...
- vue.js建立一个简单的表格
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...
- 使用vue.js封装一个包含图片的跑马灯组件
初衷: 学习完Vuejs后,来准备练习仿写一下老东家的门户页面,主要是为了熟悉一下常用插件的使用,比如video.js,wow.js,swiper等等:而其中涉及到一个包含图片跑马灯组件,大概长这样( ...
- js 从一个json拼接成另一个json,并做json数据分页table展示
先给数据: //原始json数据json = [{"id":"1","aid":"013","performa ...
随机推荐
- React中图片的相对路径引入和绝对路径引入
React中当在JSX中的img标签中引入时使用相对路径引入,地址是基于index.html的而不是当前jsx文件的,如 <img src="./src/assets/images/g ...
- SpringBoot应用集成微服务组件Nacos
目录 springboot与微服务组件nacos Nacos服务快速启动 STS4 开发工具 Maven 环境配置 STS4开发工具引入Maven配置 Maven Repo配置阿里云镜像源 Sprin ...
- ARM Cortex-M4|非常好用的一种串口收发方式
在这里分享项目中我经常使用的一种串口收发方式:阻塞发送 + 接收中断 +空闲中断 + 环形队列 项目代码地址:www.baidu.com 一.简介 串口发送使用最简单的阻塞发送方式,一般来说都是接收的 ...
- Node.js躬行记(27)——接口管理
在页面发生线上问题时,你要做的事情就是去查接口,响应数据是否正确,查接口的方法有两种: 第一种是在浏览器中打开地址,但是你必须得知道详细的 URL,并且有些页面还需要附带参数. 第二种是打开编辑器,启 ...
- python 快速替换csv数据集字符串列表中的表情符号为空,asyncio,re,pandas
传统的字符串列表替换字符串使用遍历非常慢 比如下面这段代码,如果处理几十万或上百万的数据集时,会非常的慢,几小时几天都可能 import re p = re.compile(u'['u'\U0001F ...
- Python分支结构之if语句
程序结构 程序三种结构 顺序 循环 分支 分支结构 分支结构基本语法 if 条件表达式: 语句1 语句2 语句3 ...... 条件表达式就是计算结果必须为布尔值的表达式 表达式后面的冒号不能少 注意 ...
- [Pytorch框架] 1.3、张量
文章目录 PyTorch是什么? Tensors(张量) NumPy 转换 CUDA 张量 PyTorch是什么? 基于Python的科学计算包,服务于以下两种场景: 作为NumPy的替代品,可以使用 ...
- #Python 利用pivot_table,数据透视表进行数据分析
前面我们分享了,利用python进行数据合并和连接,但是工作中,我们往往需要对数据进一步的聚合或者运算,以求最后的数据结果. 今天我们就来学习一下利用pandas模块,对数据集进行数据透视分析. pi ...
- 2022-12-11:行程和用户。以下为输出结果,请问sql语句如何写? +------------+-------------------+ | Day | Cancellation
2022-12-11:行程和用户.以下为输出结果,请问sql语句如何写? ±-----------±------------------+ | Day | Cancellation Rate | ±- ...
- drf——序列化之source(了解)、定制字段的两种方式(重要)、多表关联反序列化保存、反序列化字段校验、ModelSerializer使用
1 序列化高级用法之source(了解) # 1.创建了5个表(图书管理的5个) # 2.对book进行序列化 # 总结:source的用法 1.修改前端看到的字段key值--->source指 ...