用 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 ...
随机推荐
- 【ACM算法竞赛日常训练】DAY16【奇♂妙拆分】【区区区间间间】【小AA的数列】数学 | 位运算 | 前缀和
DAY16共3题: 奇♂妙拆分(简单数学) 区区区间间间(单调栈) 小AA的数列(位运算dp) 作者:Eriktse 简介:19岁,211计算机在读,现役ACM银牌选手力争以通俗易懂的方式讲解算法!️ ...
- (一)pyahocorasick和marisa_trie,字符串快速查找的python包,自然语言处理,命名实体识别可用的高效包
Pyahocorasick Pyahocorasick是一个基于AC自动机算法的字符串匹配工具.它可以用于快速查找多个短字符串在一个长字符串中的所有出现位置.Pyahocorasick可以在构建状态机 ...
- input输入框输入组合的限制
1 输入大小写字母.数字.下划线: 2 <input type="text" οnkeyup="this.value=this.value.replace(/[^\ ...
- Python表达式及运算符
表达式 由一个或者几个数字或者变量或者运算符合成的一行代码 通常返回一个结果 运算符 由一个以上的值经过一系列的运算得到新值的过程叫运算 用来操作运算的符号叫运算符 运算符分类 算数运算符 比较或者关 ...
- I-o-C 一篇概览
一.ioC 容器和 Bean介绍 IoC(Inversion of Control )也被称之为 DI(dependency injection),名称侧重点略有不同. 所谓控制翻转即对象通过构造函数 ...
- 【Redis】持久化实现(RDB、AOF)
一.Redis RDB 持久化 1.RDB 工作原理 RDB(Redis Database)基于时间进行生成数据快照,默认只保留当前最新数据状态,优点时执行速度较快,但上次数据保存点到当前时间点之间的 ...
- 【python爬虫】对于微博用户发表文章内容和评论的爬取
此博客仅作为交流学习 对于喜爱的微博用户文章内容进行爬取 (此部分在于app页面进行爬取,比较方便) 分析页面 在这里进行json方法进行,点击Network进行抓包 发现数据加载是由这个页面发出的, ...
- 2022-06-02:一开始在0位置,每一次都可以向左或者向右跳, 第i次能向左或者向右跳严格的i步。 请问从0到x位置,至少跳几次可以到达。 来自字节。 力扣754. 到达终点数字。
2022-06-02:一开始在0位置,每一次都可以向左或者向右跳, 第i次能向左或者向右跳严格的i步. 请问从0到x位置,至少跳几次可以到达. 来自字节. 力扣754. 到达终点数字. 答案2022- ...
- 2022-05-23:给定一个数组arr,你可以随意挑选其中的数字, 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1。 返回你最多能挑选几个数。 来自美团。
2022-05-23:给定一个数组arr,你可以随意挑选其中的数字, 但是你挑选的数中,任何两个数a和b,必须Math.abs(a - b) > 1. 返回你最多能挑选几个数. 来自美团. 答案 ...
- F对象和Q对象
F对象 批量计算 Q对象,与或非