오늘은 복잡한 구조로 이루어져있는 json을 변환해서 트리구조로 읽기 쉽게끔 리더기를 만들어볼 거다
디자인에 신경을 쓰고싶지만.. 귀찮아서 대충 긁어오는중..ㅋㅋ
JSON Reader
구조
<h1>JSON Reader</h1>
<div class="container">
<textarea name="jsonInput" id="jsonInput" cols="30" rows="10"></textarea>
<div class="buttons">
<button onclick="displayJson()" class="learn-more">
<span class="circle" aria-hidden="true">
<span class="icon arrow"></span>
</span>
<span class="button-text">변환</span>
</button>
<!-- <input type="file" id="fileInput" accept=".json, .txt" /> -->
</div>
<div id="jsonViewer"></div>
</div>
원래 파일 첨부기능도 넣고싶었지만.. 그 input type=file 스타일링이 귀찮아서 뺐다..ㅋㅋ
스크립트는 적용시켜놨으니까 참고해주시긔
여기서는 button을 눌렀을 때 displayJson함수가 실행되는 onClick 이벤트 정도만 봐도 될 듯 싶다.
CSS
@font-face {
font-family: "Pretendard-Regular";
src: url("https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff")
format("woff");
font-weight: 400;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
font-family: "Pretendard-Regular", sans-serif;
}
body {
width: 100%;
height: 100vh;
overflow: hidden;
}
h1 {
margin-top: 30px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
text-transform: uppercase;
letter-spacing: 6px;
font-size: 3vw;
font-weight: 900;
text-decoration: none;
color: white;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-moz-background-clip: text;
-moz-text-fill-color: transparent;
-ms-background-clip: text;
-ms-text-fill-color: transparent;
background-clip: text;
background-image: linear-gradient(45deg, #7794ff, #44107a, #ff1361, #fff800);
animation: 0.8s shake infinite alternate;
}
@keyframes shake {
0% {
transform: skewX(-15deg);
}
5% {
transform: skewX(15deg);
}
10% {
transform: skewX(-15deg);
}
15% {
transform: skewX(15deg);
}
20% {
transform: skewX(0deg);
}
100% {
transform: skewX(0deg);
}
}
.container {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
position: relative;
z-index: 1000;
}
textarea {
width: 500px;
height: 400px;
padding: 10px;
border: 1px solid #ccc;
resize: none;
overflow: scroll;
}
.buttons {
display: flex;
flex-direction: column;
gap: 10px;
}
input[type="file"]::file-selector-button {
font-family: "Pretendard-Regular";
background-color: #8a5cff;
color: #fff;
border: 0;
padding: 10px;
border-radius: 50%;
cursor: pointer;
transition: background-color 0.3s ease;
}
input[type="file"]::file-selector-button:hover {
background-color: #5f2ed9;
transition: background-color 0.3s ease;
}
button {
margin: 0 20px;
position: relative;
display: inline-block;
cursor: pointer;
outline: none;
border: 0;
vertical-align: middle;
text-decoration: none;
background: transparent;
padding: 0;
font-size: 20px;
font-family: inherit;
}
button.learn-more {
width: 15rem;
height: auto;
}
button.learn-more .circle {
transition: all 0.45s cubic-bezier(0.65, 0, 0.076, 1);
position: relative;
display: block;
margin: 0;
width: 3rem;
height: 3rem;
background: #8a5cff;
border-radius: 1.625rem;
}
button.learn-more .circle .icon {
transition: all 0.45s cubic-bezier(0.65, 0, 0.076, 1);
position: absolute;
top: 0;
bottom: 0;
margin: auto;
background: #fff;
}
button.learn-more .circle .icon.arrow {
transition: all 0.45s cubic-bezier(0.65, 0, 0.076, 1);
left: 0.625rem;
width: 1.125rem;
height: 0.125rem;
background: none;
}
button.learn-more .circle .icon.arrow::before {
position: absolute;
content: "";
top: -0.25rem;
right: 0.0625rem;
width: 0.625rem;
height: 0.625rem;
border-top: 0.125rem solid #fff;
border-right: 0.125rem solid #fff;
transform: rotate(45deg);
}
button.learn-more .button-text {
transition: all 0.45s cubic-bezier(0.65, 0, 0.076, 1);
position: absolute;
top: -5px;
left: -20px;
right: 0;
bottom: 0;
padding: 0.75rem 0;
margin: 0 0 0 1.85rem;
color: #282936;
font-weight: 700;
line-height: 1.6;
text-align: center;
text-transform: uppercase;
}
button:hover .circle {
width: 100%;
}
button:hover .circle .icon.arrow {
background: #fff;
transform: translate(1rem, 0);
}
button:hover .button-text {
color: #fff;
}
#jsonViewer {
width: 500px;
height: 400px;
line-height: 1.5;
background-color: #f9f9f9;
border: 1px solid #ddd;
padding: 10px;
overflow: scroll;
font-size: 14px;
}
.collapsible {
cursor: pointer;
position: relative;
padding-left: 20px;
}
.collapsible::before {
position: absolute;
content: "➡️";
display: inline-block;
left: 0;
top: 0;
font-size: 14px;
line-height: 1;
padding-right: 5px;
}
.collapsible.collapsed::before {
content: "🔽";
}
.hidden {
display: none;
}
.key {
color: #0366d6;
font-weight: bold;
}
.string {
color: #22863a;
}
.number {
color: #032f62;
}
.object {
margin-left: 15px;
}
.array {
margin-left: 15px;
}
.unknown {
color: #555;
}
@media screen and (max-width: 1350px) {
.container {
flex-direction: column;
padding: 30px;
}
}
반응형으로는 줄였을 때 세로로 정렬되게끔 했다.
스크립트
function displayJson() {
const jsonInput = document.getElementById("jsonInput").value;
try {
const jsonObj = JSON.parse(jsonInput);
document.getElementById("jsonViewer").innerHTML = "";
buildTree(jsonObj, document.getElementById("jsonViewer"), "root");
} catch (e) {
document.getElementById("jsonViewer").innerHTML =
'<p style="color: red;">Invalid JSON</p>';
}
}
function buildTree(obj, parentElement, key) {
const item = document.createElement("div");
parentElement.appendChild(item);
if (typeof obj === "object" && obj !== null) {
const keySpan = document.createElement("span");
keySpan.className = "key collapsible";
keySpan.textContent = key + ": ";
item.appendChild(keySpan);
const childContainer = document.createElement("div");
childContainer.className =
"hidden " + (Array.isArray(obj) ? "array" : "object");
item.appendChild(childContainer);
for (const childKey in obj) {
buildTree(obj[childKey], childContainer, childKey);
}
keySpan.onclick = function (event) {
event.stopPropagation();
const childDiv = this.parentElement.querySelector(".hidden");
if (childDiv.style.display === "block") {
childDiv.style.display = "none";
this.classList.remove("collapsed");
} else {
childDiv.style.display = "block";
this.classList.add("collapsed");
}
};
} else {
item.innerHTML =
'<span class="key">' +
key +
": </span>" +
'<span class="' +
getType(obj) +
'">' +
obj +
"</span>";
}
}
function getType(value) {
if (typeof value === "string") return "string";
if (typeof value === "number") return "number";
if (Array.isArray(value)) return "array";
if (typeof value === "object" && value !== null) return "object";
return "unknown";
}
document
.getElementById("fileInput")
.addEventListener("change", function (event) {
const file = event.target.files[0];
if (!file) {
return;
}
let reader = new FileReader();
reader.onload = function (event) {
const fileContent = event.target.result;
document.getElementById("jsonInput").value = fileContent;
parseAndDisplayJson();
};
reader.readAsText(file);
});
1. displayJson 함수
이 함수는 사용자가 "jsonInput"이라는 ID를 가진 입력 필드에 JSON 데이터를 입력하고 버튼을 클릭하거나 이 함수를 호출하는 다른 동작을 수행했을 때 트리거됩니다.
2. JSON 입력 가져오기
jsonInput"이라는 ID를 가진 HTML 입력 요소에서 JSON 입력을 가져옵니다.
3. JSON 파싱
JSON.parse를 사용하여 JSON 입력을 파싱을 시도합니다. 성공하면 파싱된 JSON 객체(jsonObj)가 얻어집니다.
4. 이전 콘텐츠 지우기
"jsonViewer"라는 ID를 가진 HTML 요소의 콘텐츠를 지웁니다.
5. 트리 빌드
buildTree 함수를 호출하여 JSON 객체에 대한 트리 구조를 생성하고 이를 "jsonViewer"라는 ID를 가진 HTML 요소에 표시합니다. 루트 노드는 "root"로 레이블이 지정됩니다.
6. 에러 처리
JSON 파싱 중에 오류가 발생하면 "jsonViewer"라는 ID를 가진 HTML 요소에 오류 메시지를 표시합니다.
7. buildTree 함수
이 함수는 재귀적으로 호출되며 JSON 객체를 표시하기 위한 트리 구조를 생성합니다.
8. 요소 생성
div 요소를 생성합니다.
9. 객체 또는 값 추가
JSON 객체가 아닌 경우 또는 객체 또는 배열이 비어있지 않은 경우, "span" 요소를 생성하여 키와 콜론(:)을 표시하고, 자식 요소를 저장할 "div" 컨테이너를 생성합니다.
10. 재귀 호출
객체 또는 배열이 비어있지 않은 경우, 자식 키에 대해 buildTree 함수를 재귀적으로 호출하여 하위 요소를 생성합니다.
11. 축소 및 확장 기능 추가
생성된 노드를 축소 및 확장할 수 있는 기능을 추가합니다. 클릭 이벤트가 발생하면 해당 노드의 하위 "div" 요소의 표시 여부를 변경하고, 시각적으로 표시되는 모양을 변경합니다.
12. 값 표시
만약 JSON 객체가 아닌 경우, 키와 값이 있는 "span" 요소를 생성하여 값을 표시합니다.
13. getType 함수
이 함수는 값의 유형을 판별하여 해당하는 문자열을 반환합니다. 문자열, 숫자, 배열, 객체, 그 외 유형 등을 식별합니다.
14. 파일 입력 이벤트
fileInput이라는 ID를 가진 파일 입력 요소의 변경 이벤트를 수신하며, 선택한 파일을 읽어와 파일 내용을 "jsonInput"이라는 ID를 가진 입력 요소에 설정한 후 parseAndDisplayJson 함수를 호출합니다.
완성된 화면과 코드 보기
https://github.com/YeoDaSeul4355/web2023/tree/main/javascirpt/project/jsonReader