React 에서 JSON 파일을 파싱해보자

오늘의 목적은 React 프론트엔드에 특정한 JSON 형식의 파일을 업로드 하고,

이 JSON  파일 내부에 있는 정보를 통해서 프론트엔드에 어떠한 “값”들을 저장할 겁니다.

이를 위해서는 JSON 업로드 창을 구현해야 할 것이며 

업로드 된 JSON 파일 정보에서 특정한 값들만을 꺼낼 수 있도록 파싱을 하는 방법도 필요하겠네요.

바로 시작하죠.

우선은 업로드 섹션을 디자인하자.

				
					      <input
        type="file"
        accept=".json"
        id="json-upload"
        style={{ display: "block" }}
      />
				
			

우선은 위과 같은 코드부터 시작을 합시다.

위 코드는 input 의 type 이 file 입니다.

그리고, .json 파일만을 업로드 가능하게 하는 input 창이죠. 

위 코드만 댕그러니 출력을 하면 위와 같은 모양이 됩니다. 

이거를 적절한 코드로 꾸며보겠습니다. 

				
					        <input
          type="file"
          accept=".json"
          id="json-upload"
          style={{ display: "none" }}
        />
        <label htmlFor="json-upload" className="upload-label">
          {isDraggingOver
            ? "여기에 파일을 놓으세요!"
            : "JSON 파일 드래그 앤 드롭 또는 클릭"}{" "}
          {/* 조건부 문구 */}
        </label>
				
			

요렇게 해보고자 합니다. 

파일 업로드를 위한 input창과 label을 같이 넣어두는 거죠.

이때 input 창 자체는 실제로 화면에 나타나지 않게 display를 none으로 했습니다. 

그리고 isDraggingOver 상태에 따라서 문구가 달라지게 설정을 해줍니다. 

				
					import React, { useState, useCallback } from "react";

const Test = () => {
  //여기서 드래그 상태를 체크할 거임... 파일 드래그가 된 상태인지, 아닌 상태인지!
  const [isDraggingOver, setIsDraggingOver] = useState(false);

  //파일을 드래그 하자마자 열리는데, 이걸 통해서 드래그 중에는 파일이 열리지 않도록 막을 것이다.
  const handleDragOver = useCallback((e) => {
    e.preventDefault();
  }, []);

  //영역 안으로 파일을 드래그 한 상태라면? 우선은 내부에 발생하는 이벤트는 막는다. 드래그에서 뗄 때 파일을 업로드 할 거니까~ 대신 이쁘게 하기 위해서 isDraggingOver 을 True로 바꾼다!
  const handleDragEnter = useCallback((e) => {
    e.preventDefault();
    setIsDraggingOver(true); // 드래그 시작 상태로 변경
  }, []);

  //영역 밖으로 드래그 중인 게 나간다면? 파일이 열리지 않도록 여기도 이벤트는 막는다. 그리고 나갔으니까 드래그 체크 변수를 False로 변경한다!
  const handleDragLeave = useCallback((e) => {
    e.preventDefault();
    setIsDraggingOver(false); // 드래그 종료 상태로 변경
  }, []);

  //영역 내에서 드래그 중이던 파일을 놓았을 때 발동될 거다. 드래그 상태를 초기화 하고, 메시지를 호출할 거다.
  const handleDrop = useCallback((e) => {
    e.preventDefault();
    setIsDraggingOver(false); // 드롭 후 드래그 상태 초기화
    const droppedFile = e.dataTransfer.files[0]; // 전송된 파일들 중의 0번째 파일의 정보를 뜻함, 업로드가 됐다면 True가 된다는 뜻.
    if (droppedFile && droppedFile.type === "application/json") {
      alert("JSON 파일 드랍에 성공했습니다!");
    } else if (droppedFile) {
      alert("JSON 파일만 업로드해주세요.");
    }
  }, []);

  return (
    <>
      <div
        className={`upload-box ${isDraggingOver ? "drag-over" : ""}`} // 조건부 클래스 추가
        onDragOver={handleDragOver}
        onDragEnter={handleDragEnter} // dragenter 이벤트 핸들러 추가
        onDragLeave={handleDragLeave} // dragleave 이벤트 핸들러 추가
        onDrop={handleDrop}
      >
        <input
          type="file"
          accept=".json"
          id="json-upload"
          style={{ display: "none" }}
        />
        <label htmlFor="json-upload" className="upload-label">
          {isDraggingOver
            ? "여기에 파일을 놓으세요!"
            : "JSON 파일 드래그 앤 드롭 또는 클릭"}{" "}
          {/* 조건부 문구 */}
        </label>
      </div>
    </>
  );
};

export default Test;

				
			

그리고 위와 같이 거대한 div 내부에 앞서 만든 input 과 label을 담습니다.

그리고 이 div 에는 “드래그 드랍” 설정을 해줍니다. 

드래그 드랍 설정을 위해 작동하는 동작은 총 4가지가 있습니다.

  1. 드래그 중인 상황 
  2. 드래그를 놓은 상황 
  3. 드래그가 div 안으로 들어왔을 때!
  4. 드래그가 div 밖으로 빠져나갔을 때!
				
					
.upload-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 20px;
    border: 2px dashed #ccc;
    border-radius: 10px;
    cursor: pointer;
    width: 800px;
    margin-bottom: 20px;
    transition: all 0.2s ease-in-out; /* 부드러운 효과를 위해 transition 추가 */
}

.upload-box:hover {
    border-color: #888;
}

/* 드래그 중 스타일 */
.upload-box.drag-over {
    border-style: solid; /* 점선 -> 실선 */
    border-color: #007bff; /* 강조 색상 */
    background-color: #f8f8f8; /* 배경색 변경 */
    padding: 50px 20px; /* 패딩 증가 */
    transform: scale(1.05); /* 살짝 확대 효과 */
}

.upload-label {
    font-size: 16px;
    color: #555;
    text-align: center;
    margin-bottom: 10px;
    transition: color 0.2s ease-in-out; /* 문구 색상 변화 transition */
}

.upload-box.drag-over .upload-label {
    color: #333; /* 드래그 중 문구 색상 변경 */
    font-size: 18px; /* 드래그 중 문구 크기 변경 */
}

.template-btn {
    padding: 10px 15px;
    background-color: #f0f0f0;
    border: 1px solid #ccc;
    border-radius: 5px;
    cursor: pointer;
    font-size: 14px;
}

.template-btn:hover {
    background-color: #e0e0e0;
}

				
			

여기서 위와 같은 CSS 디자인을 입혀서 보겠습니다.

어떻게 나타날까요?

위와 같이 전체적으로 JSON 파일 드래그 앤 드롭 또는 클릭! 이라는 메시지가 있는 창이 나타납니다. 

그리고 이곳에 파일을 드래그 하면 upload-box.drag-over 상태가 적용이 되게 됩니다. 

동작을 구현하자!

				
					  //영역 내에서 드래그 중이던 파일을 놓았을 때 발동될 거다. 드래그 상태를 초기화 하고, 메시지를 호출할 거다.
  const handleDrop = useCallback((e) => {
    e.preventDefault();
    setIsDraggingOver(false); // 드롭 후 드래그 상태 초기화
    const droppedFile = e.dataTransfer.files[0]; // 전송된 파일들 중의 0번째 파일의 정보를 뜻함, 업로드가 됐다면 True가 된다는 뜻.
    if (droppedFile && droppedFile.type === "application/json") {
      alert("JSON 파일 드랍에 성공했습니다!");
    } else if (droppedFile) {
      alert("JSON 파일만 업로드해주세요.");
    }
  }, []);

				
			

방금 전에는 handleDrop 이 실제로 파일을 놓게 되면 작동하는 함수였습니다. 

그런데 이 포스팅의 목적은 업로드한 JSON 파일을 파싱해서, 내가 원하는 값을 사용하는 것이 목적입니다. 

지금은 파일 업로드가 성공하면 alter(“…”) 이 작동하지만…

실제로 작동을 시킬 함수를 만들 필요가 있습니다. 

				
					  //업로드한 JSON 파일을 놓게 되면 여기가 바뀔 거임!
  const [gojo, setGojo] = useState("");
  const [megumi, setMegumi] = useState("");

				
			

이를 위해서 요딴 코드와…

				
					      <div className="test-field">
        <li>
          <ul>고죠 사토루 : {gojo}</ul>
          <ul>후시구로 메구미 : {megumi}</ul>
        </li>
      </div>
				
			

요딴 필드를 추가해봤습니다. 

그럼 요렇게 아직 아무런 대사가 없습니다.

json 파일을 업로드해서,setGojo 와 setMegumi 를 발동해보죠. 

				
					// 드롭 이벤트 처리 함수
const handleDrop = useCallback((e) => {
  e.preventDefault();
  setIsDraggingOver(false); // 드롭 후 드래그 상태 초기화

  const droppedFile = e.dataTransfer.files[0]; // 드롭된 파일 가져오기

  if (droppedFile && droppedFile.type === "application/json") {
    processJsonFile(droppedFile, "gojo"); // JSON 파일 처리 함수 호출
  } else if (droppedFile) {
    alert("JSON 파일만 업로드해주세요.");
  }
}, []);
				
			

요런 식으로 사용할 계획입니다.

앞서 alert 으로 그냥 성공했어요~ 메시지만 보냈는데 실제 어떤 함수가 동작하는 거죠.

				
					  // JSON 파일 읽고 특정 key와 value를 가져오는 함수
  const processJsonFile = (file, targetKey) => {
    const reader = new FileReader();

    reader.onload = (event) => {
      try {
        const jsonData = JSON.parse(event.target.result); // JSON 파싱
        console.log(jsonData); // 전체 JSON 데이터 확인

        // 특정 key와 value 가져오기
        if (jsonData[targetKey] !== undefined) {
          alert(`Key: ${targetKey}, Value: ${jsonData[targetKey]}`);
        } else {
          alert(`Key "${targetKey}"를 찾을 수 없습니다.`);
        }
      } catch (error) {
        console.error("JSON 파싱 에러:", error);
        alert("유효하지 않은 JSON 파일입니다.");
      }
    };

    reader.onerror = () => {
      console.error("파일 읽기 실패");
      alert("파일을 읽는 중 오류가 발생했습니다.");
    };

    reader.readAsText(file); // 파일을 텍스트로 읽기
  };
				
			

실제로 동작을 할 어떤 함수입니다.

우선은 이대로 한 번 사용을 해볼까요?

				
					{
  "gojo": "료요키 텐카이!",
  "megumi": "후루베 유라유라!"
}

				
			

위와 같은 json 파일을 업로드 하면, 이렇게 나옵니다~

제가 handleDrop 내부에서 작동시킨 processJsonFile 에다가 “gojo” 를 넣었기 때문이죠.

그럼 processJsonFile 의 구조를 좀 더 변경하겠습니다. 

				
					  // JSON 파일 읽고 특정 key와 value를 가져오는 함수
  const processJsonFile = (file, targetKey) => {
    const reader = new FileReader();

    reader.onload = (event) => {
      try {
        const jsonData = JSON.parse(event.target.result); // JSON 파싱
        console.log(jsonData); // 전체 JSON 데이터 확인

        // 특정 key와 value 가져오기
        if (jsonData[targetKey] !== undefined) {
          setGojo(jsonData[targetKey]);
        } else {
          alert(`Key "${targetKey}"를 찾을 수 없습니다.`);
        }
      } catch (error) {
        console.error("JSON 파싱 에러:", error);
        alert("유효하지 않은 JSON 파일입니다.");
      }
    };

    reader.onerror = () => {
      console.error("파일 읽기 실패");
      alert("파일을 읽는 중 오류가 발생했습니다.");
    };

    reader.readAsText(file); // 파일을 텍스트로 읽기
  };
				
			

이런 식으로 setGojo를 넣었습니다.

그럼 다시 한 번 json을 업로드 하면?

오오, 대사가 발동되네요!

나쁘지 않습니다. 

그럼 메구미의 대사도 발동하려면 어떻게 조작할까요?

				
					  //고조가 말한다.
  const sadGojo = (file) => {
    const reader = new FileReader();

    const targetKey = "gojo";

    reader.onload = (event) => {
      try {
        const jsonData = JSON.parse(event.target.result); // JSON 파싱
        console.log(jsonData); // 전체 JSON 데이터 확인

        // 특정 key와 value 가져오기
        if (jsonData[targetKey] !== undefined) {
          setGojo(jsonData[targetKey]);
        } else {
          alert(`Key "${targetKey}"를 찾을 수 없습니다.`);
        }
      } catch (error) {
        console.error("JSON 파싱 에러:", error);
        alert("유효하지 않은 JSON 파일입니다.");
      }
    };

    reader.onerror = () => {
      console.error("파일 읽기 실패");
      alert("파일을 읽는 중 오류가 발생했습니다.");
    };

    reader.readAsText(file); // 파일을 텍스트로 읽기
  };

  //메구미가 말한다.
  const sadMegumi = (file) => {
    const reader = new FileReader();

    const targetKey = "megumi";

    reader.onload = (event) => {
      try {
        const jsonData = JSON.parse(event.target.result); // JSON 파싱
        console.log(jsonData); // 전체 JSON 데이터 확인

        // 특정 key와 value 가져오기
        if (jsonData[targetKey] !== undefined) {
          setMegumi(jsonData[targetKey]);
        } else {
          alert(`Key "${targetKey}"를 찾을 수 없습니다.`);
        }
      } catch (error) {
        console.error("JSON 파싱 에러:", error);
        alert("유효하지 않은 JSON 파일입니다.");
      }
    };

    reader.onerror = () => {
      console.error("파일 읽기 실패");
      alert("파일을 읽는 중 오류가 발생했습니다.");
    };

    reader.readAsText(file); // 파일을 텍스트로 읽기
  };

				
			

저는 이런 식으로 각각의 함수를 만드는 방법으로 관리를 해봤습니다.

이런 방식으로 사용하기 위해서는…

				
					  //영역 내에서 드래그 중이던 파일을 놓았을 때 발동될 거다. 드래그 상태를 초기화 하고, 메시지를 호출할 거다.
  const handleDrop = useCallback((e) => {
    e.preventDefault();
    setIsDraggingOver(false); // 드롭 후 드래그 상태 초기화
    const droppedFile = e.dataTransfer.files[0]; // 전송된 파일들 중의 0번째 파일의 정보를 뜻함, 업로드가 됐다면 True가 된다는 뜻.
    if (droppedFile && droppedFile.type === "application/json") {
      sadGojo(droppedFile);
      sadMegumi(droppedFile);
    } else if (droppedFile) {
      alert("JSON 파일만 업로드해주세요.");
    }
  }, []);
				
			

위와 같이 handleDrop을 변경하면 되겠네요.

업로드가 성공하면 차례차례 필요한 함수들이 실행이 되는 거죠.

이렇게 잘 되는 것을 확인했습니다.

마치며...

이제 위 방법을 활용해서 json 파일을 업로드 하고, 파싱해서 활용하는 방안을 모색해보세요. 

다음에도 또 비루한 주제를 가지고 포스팅을 해보겠습니다.

여기까지 봐주신 분들 정말 감사합니다.^^