Language/Typescript

Typescript 환경에서 역직렬화하는 방법

Joonfluence 2021. 11. 30.

서론

대상독자

TypeScript를 활용하여, 서버로부터 전달 받은 json 데이터를 object(class) 형태로 변환하고 싶은 개발자.

학습목표

직렬화와 역직렬화의 필요성에 대해 이해하고 실제 프로덕트에 적용할 수 있다.
데이터 검증 과정을 통해, 웹/앱 어플리케이션에서 네트워크를 통해 전송되는 데이터가 바르게 전달되었는지 확인할 수 있다.

본론

직렬화와 역직렬화의 필요성

오늘은 데이터 전송과 데이터 수신 과정에서 빠질 수 없는 두 요소가 있습니다. 바로 직렬화와 역직렬화입니다. 첫째, 직렬화란 서버로 전송될 데이터를 json 형태로 변환하는 과정입니다. 이는 HTTP 네트워크 상에서 데이터를 전송하기 위해 필요합니다. 둘째, 역직렬화란 서버에서 전달 받은 josn 데이터를 객체 형태(여기선 object(class))로 변환하는 과정입니다. 역직렬화를 하는 까닭은 json 데이터에는 데이터 타입에 대한 제한을 줄 수 없지만, object(class) 형태에선 데이터에 대한 타입을 제한할 수 있기 때문입니다. 이를 통해, 클라이언트 사이드에서 서버로부터 전송 받은 데이터를 올바른 형태로 변환하여 활용할 수 있습니다. 오늘은 역직렬화에 대해 먼저 알아보도록 하겠습니다.

관련 패키지 설치

그럼 해당 과정을 수행하기 위해, 패키지를 설치해봅시다. 참고로 class-transformer는 TypeScript 진영에서 직렬화와 역직렬화를 위해 자주 활용되는 라이브러리입니다. 공식문서를 참조하면, 다양한 용례를 확인할 수 있는데요. 이번 포스팅에선 자주 활용되는 요소들에 대한 내용을 짧고 굵게 다뤄보도록 하겠습니다.

yarn add class-transformer or npm install class-transformer;

예를 들어, getDiarylists라는 함수를 호출해, 서버에서 사용자의 다이어리 목록을 불러올 수 있다고 가정해보겠습니다. 따라서 받아온 데이터 형식은 당연히 json 형식일 것입니다. 그리고 변환할 형태는 다음과 같은 속성 값들을 갖는다고 가정합시다. 짧게 설명하자면, Diary Model은 다음과 같이 기본적인 데이터 형태를 띄고 있습니다.

class DiaryModel {
  id: string;
  title: string;
  posts: PostModel[];
  createAt: Date;
  updatedAt?: Date;
}

이 때, class-transformer에서 제공하는 plainToClass 함수에 첫번째 인자로 데이터 모델(클래스 형식)을 넣고 두번째 인자로 json 데이터를 넣어줍니다. 단, 지금처럼 배열의 형태일 때는 map 함수를 활용해, 하나씩 하나씩 변환해주는 과정을 거쳐야 합니다. 그럼 성공적으로 값이 객체 형태로 변환됨을 알 수 있습니다.

import { plainToClass } from 'class-transformer';

 async getDiarylists(): Promise<DiaryModel[]> {
    const diaryLists = await this._remote.GetDiaries();
    const diaryInstances = diaryLists.items.map((post) =>
      plainToClass(DiaryModel, {
        ...post,
      })
    );
    return diaryInstances;
  }
}

class-transformer를 쓰기 전에는 어떻게 해줘야 했나?

만약 해당 라이브러리가 없었다면 어떻게 했어야 했을까요? JSON 데이터의 모든 프로퍼티를 새로운 객체에 일일이 복사해줬어야 했습니다. Object.assign 와 같은 메소드를 통해 말이죠. 이는 매우 귀찮고 번거로운 작업이므로 라이브러리를 사용하는 것입니다.

역직렬화된 데이터에 대한 검증

자, 이대로 끝난다면 아쉽겠죠? 아직 한가지 부족한 부분이 있습니다. 바로 변환된 데이터가 올바르게 변환됐는지 검증하는 과정을 거쳐야 할 것입니다. 그럼 다음 단계에서 알아보도록 하죠. 이번엔 데이터 검증을 위한 라이브러리를 설치하겠습니다. 또 데코레이터 함수를 활용하기 위한, 바벨 플러그인을 추가로 설치해줍니다.

yarn add class-validator or npm install class-validator
yarn add -D @babel/plugin-proposal-decorators or npm install --dev @babel/plugin-proposal-decorators

이번에 다뤄볼 class-validator는 TypeScript 진영에서 클래스에 대한 데이터 검증를 위해 자주 활용되는 라이브러리입니다. 공식문서를 참조하면, 다양한 용례를 확인할 수 있는데요. 이번 포스팅에선 자주 활용되는 요소들에 대한 내용을 짧고 굵게 다뤄보도록 하겠습니다. 앞서 다이어리 객체에 대해 데코레이터 함수를 활용하여, 검증 조건들을 추가해보도록 하겠습니다. 예시 모델에는 ID, 제목, 포스팅 목록, 생성일과 수정일 등이 있습니다. 이를 아래와 같이, 데코레이터 함수로 감싸주면 해당 데이터 형식에 대한 검증이 가능해집니다. 참고로 데코레이터 함수는 데이터에 더 가깝게 위치할수록 먼저 실행됩니다.

class DiaryModel {
  @IsString()
  @IsNotEmpty()
  id: string;

  @IsString()
  @IsNotEmpty()
  title: string;

  @Type(() => PostModel)
  @ArrayNotEmpty()
  @IsNotEmpty()
  posts: PostModel[];

  @IsDate()
  @IsNotEmpty()
  createAt: Date;

  @IsDate()
  @IsOptional()
  updatedAt?: Date;
}

가장 먼저 해줘야 할 작업은 값이 필수값인지, 선택값인지 먼저 따져주는 것입니다. 필수값이 빠지거나, 선택값이 필수적으로 들어오지 않도록 주의해야 합니다. IsNotEmpty는 undefined, null, 빈 string이 아닌 데이터 형식만을 허용합니다. IsOptional는 해당 값이 null이거나 undefined인 경우, 다른 validatior를 무력화시킵니다. 그 위에 IsString 데코레이터가 있다고 해도 말이죠.

그 다음에 해당하는 작업은 해당 데이터 형식을 검증해주는 것입니다. id, title 모두 string 형식을 갖습니다. 따라서 string인지 검증해주는 IsString 데코레이터를 사용해줍니다. 이와 같은 메커니즘으로 다양한 모델을 구성하실 수 있을 것 입니다. 더 자세한 내용은 공식문서를 참조해주세요!

배열의 경우에는 같은 방식에서, @ArrayNotEmpty 형태를 추가해주면 됩니다. 또 posts 필드에선 사용자 정의타입인 PostModel을 사용했는데요, 이와 같은 사용자 정의타입에 대한 검증은 class-transformer의 Type 형식을 통해, 검증해주면 됩니다.

import { ValidationError, validate } from 'class-validator';

 async getDiarylists(): Promise<DiaryModel[]> {
    const diaryLists = await this._remote.GetDiaries();
    const diaryInstances = diaryLists.items.map((post) =>
      plainToClass(DiaryModel, {
        ...post,
      })
    );

    for(instance in diaryInstances){
        const err = await validate(instance as any, {
        validationError: true,
      });
      if (err.length > 0) {
        err.forEach((e) => printError(e));
        throw err;
      }
    }

    return diaryInstances;
  }
}

또한 추가된 검증조건은 class-validator에서 제공하는 validate 함수를 실행함으로써 검증이 가능해집니다. 만약 에러가 발생된다면, 에러가 실행되도록 처리해줌으로써 이번 시간 설명을 마무리하도록 하겠습니다.

리액트 환경에서의 데코레이터 함수 활용

yarn add reflect-metadata

추가로 리액트/리액트 네이티브 환경에서 타입스크립트의 데코레이터 함수를 활용하기 위해선 새로운 라이브러리가 하나 더 필요합니다. 설치 후, 앱의 시작 파일의 맨 꼭대기에 import "reflect-metadata"를 추가해줍니다. 그래야, 데코레이터 함수를 프로젝트 전체에서 사용할 수 있게 될 것입니다. 만약 리액트 환경이라면 앱의 시작점인 App.tsx에 추가해줘야 할 것입니다.

마무리

이것으로 타입스크립트 환경에서의 역직렬화와 해당 데이터 검증 과정에 대해서 알아보았습니다. 궁금하신 점이 있으시다면, 댓글로 남겨주시면 최대한 성심껏 답변드리겠습니다!

반응형

댓글