feling.net/json/index.html

587 lines
26 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Json 格式化json格式验证
layout: default_tool
keywords: [jsonjson在线解析,json格式化,json格式验证,json在线,jsons在线解析格式化,在线解析json,json格式,json解析,json验证,json工具,json压缩]
description: 更直观的了解接口返回值格式、结构。格式化、高亮、折叠、转发你的 json 字符串。支持一键复制、一键折叠、快捷键。转发当前 json 字符串的功能, 让沟通交流更加方便。还有 SMode 模式, 对于数组, 只解析它的第一项, json 结构更清晰。
---
<style type="text/css">
.json_key {
color: #92278f;
font-weight: bold;
}
.json_string {
color: #3ab54a;
font-weight: bold;
}
.json_link {
color: #61D2D6;
font-weight: bold;
text-decoration:none;
}
.json_link:hover {
color: #005599;
font-weight: bold;
text-decoration:none;
}
.json_number {
color: #25aae2;
font-weight: bold;
}
.json_boolean {
color: #f98280;
font-weight: bold;
}
.json_null {
color: #ccc;
font-weight: bold;
}
.fold_icon{
font-size: 12px;
color: #ccc;
}
#json_src {
word-break: break-all;
height: 100%;
resize: vertical;
padding: 10px;
font-size: 13px;
font-family: Consolas, monospace;
}
#json_result {
font-family: Consolas, monospace;
}
@media (min-width: 960px) {
#content {
display: flex;
}
#json_src_box {
width: 38%;
}
#json_result_box {
padding-left: 10px;
width: 62%;
}
}
@media (max-width: 960px) {
#json_src_box {
width: 100%;
height: 200px;
}
#json_result_box {
margin-top: 10px;
width: 100%;
}
}
</style>
<script>
if (!String.prototype.repeat) {
String.prototype.repeat = function(count) {
'use strict';
if (this == null)
throw new TypeError('can\'t convert ' + this + ' to object');
var str = '' + this;
// To convert string to integer.
count = +count;
// Check NaN
if (count != count)
count = 0;
if (count < 0)
throw new RangeError('repeat count must be non-negative');
if (count == Infinity)
throw new RangeError('repeat count must be less than infinity');
count = Math.floor(count);
if (str.length == 0 || count == 0)
return '';
// Ensuring count is a 31-bit integer allows us to heavily optimize the
// main part. But anyway, most current (August 2014) browsers can't handle
// strings 1 << 28 chars or longer, so:
if (str.length * count >= 1 << 28)
throw new RangeError('repeat count must not overflow maximum string size');
var maxCount = str.length * count;
count = Math.floor(Math.log(count) / Math.log(2));
while (count) {
str += str;
count--;
}
str += str.substring(0, maxCount - str.length);
return str;
}
}
if (!String.prototype.startsWith) {
Object.defineProperty(String.prototype, 'startsWith', {
value: function(search, pos) {
pos = !pos || pos < 0 ? 0 : +pos;
return this.substring(pos, pos + search.length) === search;
}
});
}
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(search, this_len) {
if (this_len === undefined || this_len > this.length) {
this_len = this.length;
}
return this.substring(this_len - search.length, this_len) === search;
};
}
</script>
<div id="content">
<div id="json_src_box" class="uk-width-10-10">
<textarea id="json_src" class="uk-width-1-1" autofocus spellcheck="false" v-model="jsonSrc"
v-on:click="$('#cursor-position').show();cursorPosition = $('#json_src').prop('selectionStart')"
v-on:keydown="cursorPosition = $('#json_src').prop('selectionStart')"
v-on:blur="cursorPosition = '?'"
v-on:input="cursorPosition = $('#json_src').prop('selectionStart');jsonSrcChange()">
</textarea>
</div>
<div id="json_result_box" class="uk-width-10-10" style="overflow: auto;">
<div class="uk-width-1-1" style="height:30px;">
<button class="uk-button uk-visible-small" v-bind:class="{ 'uk-button-success': defaultFold == 0 }" v-on:click="unFoldAll(1)">1级</button>
<button class="uk-button uk-visible-small" v-bind:class="{ 'uk-button-success': defaultFold == 997 }" v-on:click="unFoldAll(998)">n级</button>
{% raw %}
<span id="cursor-position" style="display:none;" data-uk-tooltip title="输入框中 光标的位置">
{{ cursorPosition }} / {{ jsonSrc.length }}
</span>
{% endraw %}
<div style="float:right;">
<input class="uk-visible-large" type="checkbox" style="display:none;"
id="array_sampling" v-model="arraySampling" v-on:change="SModeStatusChange();jsonSrcChange()">
<input class="uk-visible-large" type="checkbox" style="display:none;"
id="json_escape" v-model="jsonEscape" v-on:change="EscapeStatusChange();jsonSrcChange()">
<!-- <div class="uk-button-dropdown" data-uk-dropdown="{pos:'bottom-right'}">
<button id="alipay-red-bao-button"
class="uk-button"
style="display:none;"
v-on:mouseover="viewAlipayRedBao()">支付宝红包</button>
<div class="uk-dropdown uk-dropdown-small" style="width:240px;">
<img src="//cdn.apihub.net/img/alipay-red-bao.jpeg" alt="alipay 红包码">
</div>
</div> -->
<button v-show="!shareLink" class="uk-button" v-on:click="share">
<i id="genShareLinkSpinner" class="al-spinner" style="display:none"></i>
转发当前JSON</button>
<button id="copy-share-link" v-show="shareLink" class="uk-button uk-button-success" style="display:none;">再点一次,复制转发链接</button>
<button class="uk-button copy-button" data-clipboard-target="#json_result" data-uk-tooltip
title="复制结果框当前显示的文本<br>(源: 结果框)" @click="hideIcon()">复制</button>
<button class="uk-button copy-button uk-visible-large" data-uk-tooltip title="完全展开结果框的内容, 然后复制<br>(源: 结果框)"
data-clipboard-target="#json_result" @click="unFoldAll(998);hideIcon()">n级 &amp; 复制</button>
<button id="copy-compression-jsonSrc" class="uk-button uk-visible-large" data-uk-tooltip title="复制压缩后的 json<br>(源: js object)" >压缩 &amp; 复制</button>
</div>
</div>
<hr style="margin: 0px">
<div class="uk-width-1-1">
<pre style="margin:0;"><code v-html="jsonResult" id="json_result"></code></pre>
</div>
</div>
</div>
<script>
function hideIcon() {
$('.uk-icon-angle-right').hide();
$('.uk-icon-angle-down').hide();
}
function showIcon() {
$('.uk-icon-angle-right').show();
$('.uk-icon-angle-down').show();
}
function fold(icon) {
icon.classList.remove('uk-icon-angle-down')
icon.classList.add('uk-icon-angle-right')
icon.setAttribute('onClick', 'javascript: unFold(this)')
icon.childNodes[0].style.display = 'inline'
icon.nextSibling.style.display = 'none'
}
function unFold(icon) {
icon.classList.remove('uk-icon-angle-right')
icon.classList.add('uk-icon-angle-down')
icon.setAttribute('onClick', 'javascript: fold(this)')
icon.childNodes[0].style.display = 'none'
icon.nextSibling.style.display = 'inline'
}
var vm = new Vue({
el: "#content",
data: {
reLink: /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig,
reLastComma: /,(\s*[}\]])/g,
reEscape1: /\\/g,
reEscape2: /\\"/g,
cursorPosition: '?',
jsonSrc: `{u'python':'兼容 "单\\'引\\'号" 模式', "hello":"搜索引擎排名不太稳定,加入书签 方便下次打开。","特性列表":["多种复制模式、URL 地址可点击","支持 生成转发当前内容的链接, 方便沟通","支持 折叠后显示数组的长度 或 对象包含的属性个数","支持 对数组只解析第一项 —— SMode","支持 转义\\"","兼容 最后一项后面多出一个逗号: ,", ],"obj":{"num":-24,"null":null,"boolean":true},"objList":[{},{"key":"value"}]}`,
jsonResultTemp:[],
jsonObject: undefined,
jsonResult: '',
tab: ' ',
keyEnd: '"</span>: ',
arraySampling: false,// 遇到数组,只解析第一个元素
jsonEscape: true,
defaultFold: 997,
shareLink: ''
},
mounted: function () {
if (localStorage.json_arraySampling !== undefined) {
this.arraySampling = localStorage.json_arraySampling === 'true'
}
this.SModeStatusChange()
if (localStorage.json_jsonEscape !== undefined) {
this.jsonEscape = localStorage.json_jsonEscape === 'true'
}
this.EscapeStatusChange()
if (localStorage.json_defaultFold) {
this.defaultFold = localStorage.json_defaultFold
}
if (localStorage.json_jsonSrc) {
this.jsonSrc = localStorage.json_jsonSrc
}
let shareId = this.getQueryParamAndClearAll('shareId')
if (shareId) {
this.jsonSrc = '{"提示":"shareId: ' + shareId + ' 对应的 JSON 加载中..."}'
this.jsonSrcChange()
this.loadJsonSrcByShareId(shareId)
} else {
this.jsonSrcChange()
}
},
methods: {
isArray: function (obj) {
return obj && typeof obj === 'object' && typeof obj.length === 'number' && !(obj.propertyIsEnumerable('length'))
},
jsonLinkWarpper: function (str) {
return str.replace(this.reLink, '<a class="json_link" target="_blank" href="$1">$1</a>');
},
genKey: function (key, depth) {
this.jsonResultTemp.push("<br><span class='json_key'>")
this.jsonResultTemp.push(this.tab.repeat(depth + 1))
this.jsonResultTemp.push("\"")
this.jsonResultTemp.push($('<div/>').text(key).html())
this.jsonResultTemp.push(this.keyEnd)
},
genValue: function (obj, depth) {
if (!this.jsonResultTemp[this.jsonResultTemp.length - 1].endsWith(this.keyEnd)) { // 冒号后面不换行
this.jsonResultTemp.push('<br>')
this.jsonResultTemp.push(this.tab.repeat(depth))
}
var type = obj === null ? 'null' : typeof obj
this.jsonResultTemp.push('<span class="json_')
this.jsonResultTemp.push(type)
this.jsonResultTemp.push('">')
if (type === 'string') {
this.jsonResultTemp.push('"')
this.jsonResultTemp.push(this.jsonLinkWarpper($('<div/>').text(obj === null ? 'null' : obj).html()))
this.jsonResultTemp.push('"')
} else {
this.jsonResultTemp.push(obj === null ? 'null' : obj)
}
this.jsonResultTemp.push('</span>,')
},
genStart: function (start, depth, itemCount) {
if (this.jsonResultTemp.length > 0
&& !this.jsonResultTemp[this.jsonResultTemp.length - 1].endsWith(this.keyEnd)) { // 冒号后面不换行
this.jsonResultTemp.push('<br>')
this.jsonResultTemp.push(this.tab.repeat(depth))
}
this.jsonResultTemp.push(start)
if (depth > this.defaultFold || itemCount === 0) {
this.jsonResultTemp.push('<i class="uk-icon-angle-right" onClick="unFold(this)">'
+ '<span class="fold_icon">...' + itemCount + '</span>'
+ '</i>'
+ '<span class="foldContent" style="display:none">')
} else {
this.jsonResultTemp.push('<i class="uk-icon-angle-down" onClick="fold(this)">'
+ '<span class="fold_icon" style="display:none;">...' + itemCount + '</span>'
+ '</i>'
+ '<span class="foldContent">')
}
},
genEnd: function (end, depth) {
this.deletLastComma()
this.jsonResultTemp.push('<br>')
this.jsonResultTemp.push(this.tab.repeat(depth))
this.jsonResultTemp.push('</span>') // <span class="foldContent">
this.jsonResultTemp.push(end)
this.jsonResultTemp.push(',')
},
deletLastComma: function () {
var old = this.jsonResultTemp.pop()
var index = old.lastIndexOf(',')
var index1 = old.lastIndexOf('{')
var index2 = old.lastIndexOf('[')
if (index > index1 && index > index2) {
this.jsonResultTemp.push(old.substring(0, index))
this.jsonResultTemp.push(old.substring(index + 1))
return true
}
this.jsonResultTemp.push(old)
return false
},
genResult: function (obj, depth) {
if (this.isArray(obj)) {
this.genStart('[', depth, obj.length)
for(var i = 0; i < (this.arraySampling ? Math.min(obj.length, 1) : obj.length); i++) {
this.genResult(obj[i], depth + 1)
}
this.genEnd(']', depth)
return
}
if ((typeof obj) === 'object' && obj !== null) {
this.genStart('{', depth, Object.keys(obj).length)
for (key in obj) {
this.genKey(key, depth)
this.genResult(obj[key], depth + 1)
}
this.genEnd('}', depth)
return
}
this.genValue(obj, depth)
},
jsonSrcChange: function (compatibilityMode) {
if (this.jsonSrc === '') {
return
}
try {
var jsonSrc_ = this.jsonSrc.replace(this.reLastComma, " $1")
if (compatibilityMode == 'python') {
m = jsonSrc_.match(/u?'(.*?)'/g)
if (m) {
m.forEach(element => {
jsonSrc_ = jsonSrc_.replaceAll(element, element.replaceAll('"', '\\"'))
});
}
jsonSrc_ = jsonSrc_.replace(/u?'(.*?)'/g, '"$1"')
}
if (!this.jsonEscape) {
jsonSrc_ = jsonSrc_.replace(this.reEscape1, "\\\\").replace(this.reEscape2, '\\\\"')
}
this.jsonObject = JSON.parse(jsonSrc_)
this.jsonResultTemp = []
this.genResult(this.jsonObject, 0)
this.deletLastComma()
this.jsonResult = this.jsonResultTemp.join("")
if (this.jsonSrc.length < (1024 * 128)
&& !this.jsonSrc.startsWith('{"提示":"shareId: ')) {
localStorage.json_jsonSrc = this.jsonSrc
}
} catch (e) {
// 都有的失败提示
this.jsonResult = e.message
var position = parseInt(e.message.substring(e.message.lastIndexOf(' ')))
if (position) {
this.jsonResult += "\n\n" + jsonSrc_.substring(position - 10, position + 20)
this.jsonResult += "\n\n以上报错信息针对的是预处理后的文本输入. 可能与原输入有所出入(引号、逗号、空白符、游标等), 如需复制搜索, 请尽量复制与语法无关的部分"
}
// 兼容失败提示
if (compatibilityMode == 'python') {
this.jsonResult += "\n\n这也许是个python序列化的字符串. 尝试正则替换 /u?'(.*?)'/g 后也没有成功解析."
return
}
// 尝试兼容
if (jsonSrc_.match(/^\s*{\s*u?'.*?'/g)) {
vm_header_notify.notify('尝试 单引号 兼容', 'danger', 9 * 1000)
compatibilityMode = 'python'
this.jsonSrcChange(compatibilityMode)
}
}
},
SModeStatusChange: function () {
localStorage.json_arraySampling = this.arraySampling
if (this.arraySampling) {
$('#array-sampling-label').addClass('uk-button-success')
} else {
$('#array-sampling-label').removeClass('uk-button-success')
}
},
EscapeStatusChange: function () {
localStorage.json_jsonEscape = this.jsonEscape
if (this.jsonEscape) {
$('#json-escape-label').addClass('uk-button-success')
} else {
$('#json-escape-label').removeClass('uk-button-success')
}
},
unFoldAll: function (level) {
localStorage.json_defaultFold = level - 1
this.defaultFold = level - 1
if (vm_header_extend_item_1) {
vm_header_extend_item_1.level = level
}
this.traverseChildren(
document.getElementById("json_result"),
function(element, depth) {
if(element.tagName === 'I'){
if(depth > level){
fold(element)
} else {
unFold(element)
}
}
},
0
)
},
traverseChildren: function (element, func, depth) {
for(var i = 0; i < element.childNodes.length; i++){
this.traverseChildren(element.childNodes[i], func, depth + 1)
}
func(element, depth)
},
genShareLink: function() {
$('#genShareLinkSpinner').show()
WithFParams.postForm('/unclassified/jsonShare/create',
{
"jsonSrc": this.jsonSrc
},
function (result) {
if (result.success) {
vm.shareLink = "https://feling.net/json/?shareId=" + result.data
if (!localStorage.json_notFirstTimeGenShareLink) {
localStorage.json_notFirstTimeGenShareLink = true
}
} else {
UIkit.modal.alert("生成分享链接失败")
}
}
)
.always(function () {
$('#genShareLinkSpinner').hide()
})
},
share: function() {
if (localStorage.json_notFirstTimeGenShareLink) {
this.genShareLink()
} else {
UIkit.modal.confirm('此功能需要上传当前输入框的内容, 是否继续?', function () {
vm.genShareLink()
})
}
},
getQueryParamAndClearAll: function(key) {
var reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)");
var r = window.top.location.search.substr(1).match(reg);
// window.top.history.replaceState(null, null, "/json/")
if (r != null) {
return decodeURIComponent(r[2])
}
return null;
},
loadJsonSrcByShareId: function(shareId) {
WithFParams.postForm('/unclassified/jsonShare/get',
{
"shareId": shareId
},
function(result) {
if (result.success) {
vm.jsonSrc = result.data.jsonSrc
} else {
vm.jsonSrc = '{"提示":"shareId: ' + shareId + ' 对应的 json 加载失败"}'
}
}
).always(function () {
vm.jsonSrcChange()
})
}
}
})
</script>
<script src="//cdn.apihub.net/js/clipboard.min.js"></script>
<script>
new ClipboardJS('#copy-compression-jsonSrc', {
text: function(trigger) {
return JSON.stringify(vm.jsonObject)
}
}).on('success', function(e) {
vm_header_notify.notify('复制成功','success', 900)
})
new ClipboardJS('.copy-button').on('success', function(e) {
vm_header_notify.notify('复制成功','success', 900)
e.clearSelection()
showIcon()
})
new ClipboardJS('#copy-share-link', {
text: function(trigger) {
return vm.shareLink
}
}).on('success', function(e) {
vm.shareLink = ''
vm_header_notify.notify('复制成功','success', 900)
})
</script>
<li id="header-extend-item-1" class="header-extend-item uk-navbar-content uk-hidden-small">
<button id="button-unFoldAll-1" class="uk-button" v-bind:class="{'uk-button-success': level == 1}" onclick="vm.unFoldAll(1)">1级</button>
<button id="button-unFoldAll-2" class="uk-button" v-bind:class="{'uk-button-success': level == 2}" onclick="vm.unFoldAll(2)">2</button>
<button id="button-unFoldAll-3" class="uk-button" v-bind:class="{'uk-button-success': level == 3}" onclick="vm.unFoldAll(3)">3</button>
<button id="button-unFoldAll-4" class="uk-button" v-bind:class="{'uk-button-success': level == 4}" onclick="vm.unFoldAll(4)">4</button>
<button id="button-unFoldAll-5" class="uk-button" v-bind:class="{'uk-button-success': level == 5}" onclick="vm.unFoldAll(5)">5</button>
<button id="button-unFoldAll-n" class="uk-button" v-bind:class="{'uk-button-success': level == 998}" onclick="vm.unFoldAll(998)">n级</button>
</li>
<script>
vm_header_extend_item_1 = new Vue({
el: "#header-extend-item-1",
data: {
level: parseInt(vm.defaultFold) + 1
}
})
</script>
<li id="header-extend-item-2" class="header-extend-item uk-navbar-content uk-hidden-small">
<label class="uk-label uk-button" id="array-sampling-label" for="array_sampling"
data-uk-tooltip title="[{1},{2},{3}] -> [{1}]">SMode
</label>
<script>
if (localStorage.json_arraySampling == 'true') {
$('#array-sampling-label').addClass('uk-button-success')
} else {
$('#array-sampling-label').removeClass('uk-button-success')
}
</script>
</li>
<li id="header-extend-item-3" class="header-extend-item uk-navbar-content uk-hidden-small">
<label class="uk-label uk-button" id="json-escape-label" for="json_escape"
data-uk-tooltip title='\\ -> \ <br/> \" -> "'>转义
</label>
<script>
if (localStorage.json_jsonEscape == 'true' || localStorage.json_jsonEscape == undefined ) {
$('#json-escape-label').addClass('uk-button-success')
} else {
$('#json-escape-label').removeClass('uk-button-success')
}
</script>
</li>
<script>
$('.header-extend-item').appendTo('#header-extend')
</script>
<script>
$(function() {
$('#json_result_box').scroll(function() {
if ($('#json_result_box').scrollTop() > 400)
$('div.go-top').show();
else
$('div.go-top').hide();
});
$('div.go-top').click(function() {
$('#json_result_box').animate({scrollTop: 0}, 400);
});
});
</script>