본문 바로가기
Software/JS & TS & React

JavaScript, 파일에 데이터(문자열, 바이너리) 저장하기

by lovey25 2023. 2. 21.
반응형

데이터를 로컬디스크에 파일로 저장하는 방법을 알아보겠습니다.

지난 포스팅(JavaScript, 파일에서 데이터(문자열, 바이너리) 불러오기)에서는 로컬디스크에 저장된 데이터를 가져와서 JS에서 다루는 방법에 대해서 공부했었는데 오늘은 그 반대입니다. JS에서 직접 파일에 저장하는데 문자열을 저장하는 방법 및 바이너리 데이터로 저장하는 방법입니다.

 

파일에 문자열 저장하기

먼저 문자열을 저장해 보겠습니다. 다음과 같이 Textarea에 문자를 입력하고 하단에 버튼을 누르면 입력된 문자가 파일로 입력되는 예제입니다.

로컬 저장장치에 접근하기 위해서 윈도 파일시스템을 다루는 showSaveFilePicker(), createWritable() 두 가지 메서드를 사용했습니다.

export default function App() {
  async function saveTxtFile() {
    let textArea = document.querySelector("textarea");
    var blob = new Blob([textArea.value], {
      type: "plain/text",
    });
    const fileHandle = await window.showSaveFilePicker({
      suggestedName: "everyx.txt",
    });
    const fileStream = await fileHandle.createWritable();

    await fileStream.write(blob);
    await fileStream.close();
  }

  return (
    <>
      <div>
        <textarea defaultValue={"500, 700, 800, 250"}></textarea>
      </div>
      <button onClick={saveTxtFile}>Save File</button>
    </>
  );
}

showSaveFilePicker 메서드는 윈도의 파일 경로를 지정하는 "Save As"인터페이스가 호출하고 지정한 경로를 반환해 주는 역할을 합니다.

여기서는 'suggestedName'이라는 옵션을 추가해서 저장할 파일이름을 "everx.txt"로 미리 지정해 주었습니다.

다음으로 "createWritable()" 메서드로 파일시스템 스트림을 만들고 Blob 객체로 변환된 textarea에 입력된 문자를 파일로 저장합니다. 

저장된 파일을 열어보면 다음과 같이 입력된 문자가 그대로 저장되어 있는 걸 확인할 수 있습니다.

파일에 바이너리 데이터 저장하기

다음으로 똑같은 데이터를 바이너리로 저장하는 방법입니다.

위 예제에서는 쉼표로 구분된 4개의 숫자를 저장하고 있습니다. 이런 문자열의 형태는 사람 입장에서 가독성이 좋긴 하지만 저장과 데이터 처리 측면에서는 별로 좋을 게 없습니다. 위에서 저장한 텍스트 파일을 바이너리로 열어보면 다음과 같습니다. 

3자리 숫자 하나 저장하는데 3바이트를 사용하는 데다가 실제로는 필요 없는 쉼표나 빈칸이 있어서 공간을 낭비하고 있습니다. 그런데 이걸 바이너리로 저장하면 이렇게 됩니다.

3자리의 정수를 저장하기 위해서 각 숫자는 2바이트의 공간을 사용하고 있고 첫 번째 숫자인 '500'은 16진수로 '1F4'가 되기 때문에 little edian 방식으로 저장해서 "F4, 01"이 되어 저장되어 있습니다. 원래 18바이트를 사용했던 정보가 8바이트 안에 모두 담긴 거죠.

그럼 바이너리를 저장하는 예제도 살펴보겠습니다.

데이터를 저장하는 화면은 위에서 문자열을 저장하는 예제와 동일합니다. 다만 버튼 이름만 바뀌었죠.

코드 보겠습니다.

export default function App() {
  async function saveBinFile() {
    let textArea = document.querySelector("textarea");
    const arrData = textArea.value.split(",");
    const uint16Data = new Uint16Array(arrData);
    const fileHandle = await window.showSaveFilePicker({
      suggestedName: "everyx.bin",
    });
    const fileStream = await fileHandle.createWritable();
    var blob = new Blob([uint16Data], {
      type: "application/octet-stream",
    });
    await fileStream.write(blob);
    await fileStream.close();
  }

  return (
    <>
      <div>
        <textarea defaultValue={"500, 700, 800, 250"}></textarea>
      </div>
      <button onClick={saveBinFile}>Save Bin. File</button>
    </>
  );
}

이 코드의 가장 큰 차이점은 3,4행인데 문자열을 쉼표를 기준으로 분리해서 배열로 만들고 이 배열을 Uint16Array의 typedArray로 만들었습니다. 이 부분에  대한 추가 설명은 이전 포스팅(JavaScript에서 바이너리 데이터 처리 - (ArrayBuffer, TypedArray))으로 대신하겠습니다.

 

이렇게 변환된 데이터는 위와 동일하게 JS의 파일시스템 메서드를 사용해서 파일에 저장합니다.

첨엔 어떻게 접근해야 할지 막막했는데 해보고 나니 간단하네요.

 

에러 관련 추가 (window.showSaveFilePicker is not a function)

여기서 사용한 윈도 파일시스템 API는 사용하시는 개발 환경에 따라 사용이 제한될 수 있습니다. 위에 있는 코드를 그대로 복사했는데, 'Save File'버튼을 눌러도 아무런 반응이 없어?! 하시는 분이 계실 수도 있습니다. 이럴 때는 브라우저의 개발자도구 열어서 콘솔창을 확인해 보시면 아래와 같이 에러가 찍혀있습니다. 내용을 보면 첫 줄에 "window.showSaveFilePicker is not a function"이라는 메시지가 있네요.

뭐지? MDN문서에서 가져온 멀쩡히 있는 함수인데 없다니 이게 무슨 소리인가 싶은데요. 이 에러는 테스트 서버의 연결상태가 보안접속이 아닐 경우에 발생합니다. 주소창 왼쪽을 보시면 "Not secure"라고 되어 있을 겁니다. 

파일 시스템을 다루는 API를 브라우저에서 지원하더라도 사용하는 웹이 보안접속이 되지 않으면 관련 API를 차단합니다. MDN web 문서를 보시면 첫 줄에 HTTPS에서만 사용가능함이 명시되어 있습니다.

그럼 해결책은 테스트 서버에 HTTPS 연결을 활성화시켜 주거나 아래와 같이 파일 다운로드에 편법을 적용할 수 있습니다.

export default function App() {
  const onClick = () => {
    let textArea = document.querySelector("textarea");
    const arrData = textArea.value.split(",");
    const uint16Data = new Uint16Array(arrData);
    var blob = new Blob([uint16Data], { type: "text/plain" });

    var tempLink = document.createElement("a");
    tempLink.setAttribute("href", URL.createObjectURL(blob));
    tempLink.setAttribute("download", `everyx.bin`);
    tempLink.click();
    URL.revokeObjectURL(tempLink.href);
  };

  return (
    <>
      <div>
        <textarea defaultValue={"500, 700, 800, 250"}></textarea>
      </div>
      <button onClick={onClick}>Save Bin. File</button>
      <a style={{ display: "none" }}>Download Blob</a>
    </>
  );
}

8~12행, 그리고 21행이 기존 API를 대신하는 꼼수입니다. 파일시스템 API를 사용하는 대신 보이지 않는 <a> 태그를 추가하고(21행) 파일 저장 버튼을 누르면 JS에서 이 링크가 마치 파일을 다운로드하는 링크인 것처럼 만들고 클릭까지 해서 파일 다운로드 창이 뜨도록 만드는 코드입니다. 결과는 아래와 같이 보안연결이 되지 않은 상태에서도 파일 저장이 가능해집니다.

끝!

반응형

댓글