단일 HTML 파일로 JSON diff 도구를 만들었습니다 (빌드 단계 없이)
Source: Dev.to
Overview
JSON 페이로드를 나란히 비교해야 할 일이 계속 생겼습니다 (API 응답 vs 기대값, 이전 vs 이후, 프로덕션 vs 스테이징). 매번 무작위 온라인 도구에 붙여넣고, 눈으로 살펴보고, 몇 분 뒤에 다시 같은 과정을 반복했습니다.
이를 해결하기 위해 fknjsn.com을 만들었습니다 – 빌드 단계가 없는 단일 HTML 파일에 존재하는 로컬‑우선 JSON 비교 도구입니다.
- CDN을 통한 Vue 3 (전역 빌드)
- CDN을 통한
vue-json-pretty로 트리 렌더링 - 영속성을 위한
localStorage
그게 전부 – webpack, Vite, node_modules, package.json 없이도 됩니다. 단 하나의 index.html만 있으면 되고, 심지어 누군가에게 이메일로 보낼 수도 있습니다.
Setup
전체 앱은 두 개의 <script> 태그와 Vue 전역 내보내기의 구조 분해 할당으로 부트스트랩됩니다:
const { createApp, ref, computed, watch, onMounted, nextTick } = Vue;
// ... the rest is just Vue
전역 빌드는 최신 트렌드는 아니지만 동작합니다. 번들러도, import map도 없이 – 2014년처럼 <script> 태그만 사용하면 되면서도 반응형 프레임워크를 얻을 수 있습니다.
Paste‑anywhere UX
앱은 전역에서 paste 이벤트를 청취하지만, 입력창이나 텍스트 영역이 포커스된 경우에는 무시합니다. 이를 통해 페이지 어디서든 JSON을 붙여넣을 수 있고, 선택된 행에 자동으로 들어갑니다.
window.addEventListener('paste', (e) => {
const tag = document.activeElement?.tagName?.toLowerCase()
if (tag === 'input' || tag === 'textarea') return
try {
const json = JSON.parse(e.clipboardData.getData('text'))
addJsonToSelectedRow(json)
} catch {
// not valid JSON, ignore
}
})
Recursive Search
각 JSON 블록마다 자체 검색 입력란이 있습니다. 필터는 트리를 순회하면서, 하위 항목 중 하나라도 매치되면 해당 부모 노드를 유지합니다. 개념적인 구현은 다음과 같습니다:
function filterJson(obj, search) {
if (!search) return obj
const lower = search.toLowerCase()
if (Array.isArray(obj)) {
const filtered = obj
.map(item => filterJson(item, search))
.filter(item => item !== undefined)
return filtered.length ? filtered : undefined
}
if (obj && typeof obj === 'object') {
const result = {}
for (const [key, value] of Object.entries(obj)) {
if (key.toLowerCase().includes(lower)) {
result[key] = value
} else {
const filtered = filterJson(value, search)
if (filtered !== undefined) result[key] = filtered
}
}
return Object.keys(result).length ? result : undefined
}
// primitives
const str = String(obj).toLowerCase()
return str.includes(lower) ? obj : undefined
}
Debounced Persistence
상태는 localStorage에 저장되지만, 급격한 변경이 있을 때 저장을 과도하게 하지 않도록 500 ms 디바운스로 처리합니다:
watch(rows, () => {
clearTimeout(saveTimeout)
saveTimeout = setTimeout(() => {
localStorage.setItem('json-rows', JSON.stringify(state))
}, 500)
}, { deep: true })
Scaling Ideas
도구가 더 큰 JSON 페이로드나 추가 기능을 다뤄야 한다면 다음을 고려할 것입니다:
- 큰 페이로드 필터링을 위한 Web Worker 추가
- 트리 뷰를 위한 virtual scrolling 사용
- 단순 나란히 비교가 아니라 실제 diff view 구현
현재는 제가 필요로 하는 기능을 정확히 수행하고, 전체가 제 머리 속에 편안히 들어갑니다.
Links
- Live demo: https://fknjsn.com
- Source: view‑source of the page (the entire app is right there)
The name is pronounced exactly as you think it is.