Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
JSON html viewer
/**
* @return {string}
*/
export function jsonHtml(json) {
let indentSize = 0
function indent() {
return `<span style="font-size: 0px; position: absolute;">${' '.repeat(
indentSize,
)}</span>`
}
const html = tokenizeJson(json)
.map(({ type, value, ...extra }) => {
if (type === 'key') {
return `<div style="padding-top: 0.25rem" data-for="${
extra.for
}">${indent()}<span data-key style="color: #bfc7d5">${value}</span>`
}
if (type === 'object-open') indentSize += 2
if (type === 'object-close') indentSize -= 2
const config = {
'key-end': { suffix: '</div>' },
comma: { style: 'color: #c792ea' },
'object-open': {
suffix: '<div data-object style="padding-left: 2ch">',
style: 'color: #c792ea',
},
'object-close': {
prefix: '</div>' + indent(),
style: 'color: #c792ea',
},
string: { style: 'color: #c3e88d' },
number: { style: 'color: #f78c6c' },
null: { style: 'color: #bfc7d5; font-style: italic' },
separator: { style: 'color: #89ddff', suffix: ' ' },
}
if (type in config) {
const cfg = config[type]
return (
(cfg.prefix || '') +
(cfg.style ? `<span style='${cfg.style}'>${value}</span>` : value) +
(cfg.suffix || '')
)
}
return value
})
.join('')
return `<div><div data-wrap style='background-color: rgb(41, 45, 62); color: white; font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; padding: 1rem; border-radius: 0.4rem;white-space:pre'>${html}</div>
<script>
var wrap = document.currentScript.parentElement.querySelector('[data-wrap]')
for(const o of wrap.querySelectorAll('[data-for="object"]')) {
const plus = document.createElement('button')
plus.style.all = 'unset'
plus.style.position = 'absolute'
plus.style.transform = 'translate(-1.1rem, -0.2rem)'
plus.style.cursor = 'pointer'
plus.style.padding = '0.125rem'
plus.style.userSelect = 'none'
plus.innerText = '-'
o.prepend(plus)
}
for(const o of wrap.querySelectorAll('[data-for="object"] > [data-key]')) {
o.style.cursor = 'pointer'
}
wrap.addEventListener('click', (event) => {
if(event.target.tagName !== 'BUTTON' && (!event.target.dataset || event.target.dataset.key === undefined)) return
event.preventDefault()
const obj = event.target.parentElement.querySelector('button ~ [data-object]')
const button = event.target.parentElement.querySelector('button')
if (button.innerText === '+') {
button.innerText = '-'
const dots = event.target.parentElement.querySelector('[data-dots]')
event.target.parentElement.removeChild(dots)
obj.style.fontSize = ''
obj.style.position = ''
} else {
button.innerText = '+'
const dots = document.createElement('span')
dots.dataset.dots = ''
dots.innerText = '…'
dots.style.userSelect = 'none'
event.target.parentElement.insertBefore(dots, obj)
obj.style.fontSize = '0px'
obj.style.position = 'absolute'
}
})
console.log(wrap)
</script>
</div>`
}
function tokenizeJson(value) {
if (typeof value === 'string') {
return [{ type: 'string', value: JSON.stringify(value) }]
} else if (typeof value === 'number') {
return [{ type: 'number', value: value + '' }]
} else if (value === null) {
return [{ type: 'null', value: 'null' }]
} else if (value === undefined) {
return [{ type: undefined, value: 'undefined' }]
} else if (Array.isArray(value)) {
const parts = value
.map((v, i, list) => {
return [
{
type: 'key',
value: '',
for: typeof v !== 'object' ? typeof v : v ? 'object' : 'null',
},
...tokenizeJson(v),
...(i === list.length - 1 ? [] : [{ type: 'comma', value: ',' }]),
{ type: 'key-end', value: '' },
]
})
.reduce((a, b) => a.concat(b), [])
return [
{ type: 'object-open', value: '[' },
...parts,
{ type: 'object-close', value: ']' },
]
} else if (typeof value === 'object') {
const parts = Object.entries(value)
.filter(([k, v]) => v !== undefined)
.map(([k, v], i, list) => {
return [
{
type: 'key',
value: JSON.stringify(k + ''),
for: typeof v !== 'object' ? typeof v : v ? 'object' : 'null',
},
{ type: 'separator', value: ':' },
...tokenizeJson(v),
...(i === list.length - 1 ? [] : [{ type: 'comma', value: ',' }]),
{ type: 'key-end', value: '' },
]
})
.reduce((a, b) => a.concat(b), [])
return [
{ type: 'object-open', value: '{' },
...parts,
{ type: 'object-close', value: '}' },
]
}
return [{ type: 'null', value: 'null' }]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment