-
동적인 데이터를 처리하는 다양한 방법.간단기법 2023. 3. 13. 13:16
동적인 데이터를 다뤄야 하는 상황
서버가 클라이언트로부터 데이터를 받는 지점이라 거나, npm jwt 라이브러리와 타입스크립트를 같이 사용하다 보면, 타입을 어떻게 해야 할지 고민이 생기는 곳이 있다.
// jwt는 내가 원하는 키:밸류 를 이용하여 서명을하고 검증을 할 수 있다. token = jwt.sign({id: "1234"}, secret) // 을 통해 얻은 token을 쿠키에 담아 클라이언트로 응답한다. // 클라이언트는 서버에 요청할 때마다 쿠키를 같이 보내게 되고, 서버는 쿠키에서 토큰을 꺼내 검증한다. result = jwt.verify(token, secret); // 이 때, verify로 얻은 result에 id가 있는지 아닌지는 verfiy의 리턴 타입만으론 알 수 없다. // verify의 리턴은 다음과 같다. export interface JwtPayload { [key: string]: any; iss?: string | undefined; sub?: string | undefined; aud?: string | string[] | undefined; exp?: number | undefined; nbf?: number | undefined; iat?: number | undefined; jti?: string | undefined; }
위 코드를 보면, JwtPayload 타입만으로는 jwt에 서명할 때 어떤 키를 사용했는지 알 수 없다. 익스프레스를 사용할 때, 리퀘스트의 바디에서 데이터를 뽑을 때도 동일하다. 외부(파일, 서버 등)로부터 입력을 받는 가장 경계선에선 항상 발생하는 상황이다. 그렇다면, 어떻게 타입을 명확하게 나타낼 수 있을까.
방법1 타입 단언(type assertion, casting)
1. 타입 단언(타입 캐스팅) interface IPayload extends JwtPayload { email: string; } result = jwt.verify(token, secret) as IPayload; // 이후엔 result.email로 접근하여 사용할 수 있다. // 하지만, 타입 캐스팅이기에 result에 정말 email 프로퍼티가 있는지는 모른다 // 다음 예제 코드를 보면, result.email이 없는게 확실하지만, 타입 단언은 이를 무시한다. interface A { id: string; } interface B extends A { email: string; } function GetA(): A { return {id: "MyId"}; } const result = GetA() as B; console.log(result.email); // undefined
방법2 in 연산자
in 연산자는 해당 key가 오브젝트에 존재하는 확인하는 연산자이다. in 연산자를 이용하면 type narrowing과 함께 실제로 객체 안에 원하는 프로퍼티가 있는지 확인할 수 있다.
interface A { id: string; } interface B extends A { email: string; } function GetA(): A { return {id: "MyId"}; } const result = GetA(); if('email' in result) { console.log(result.email); }
방법3 type predicate
방법2는 in 연산자를 활용하였다. 방법3 type predicate는 리턴 타입을 '파라미터 is 타입'으로 작성하는 방식이다.
interface IPayload extends Payload { id: string; } function hasId(obj: any): obj is IPayload { return (obj.id !== undefined); } const decoded = jwt.verify(token, process.env.JWT_SECRET!); if(hasId(decoded)){ // 타입이 좁혀져서 decoded.id 와같이 접근 가능하다 decoded.id = ... }
방법4 assertion signature
리턴 타입을 이용하는 점에서 방법3과 비슷하다. 타입스크립트 3.7에 새로 소개된 기능으로, 다음과 같이 사용한다.
function assert(condition: any, msg?: string): asserts condition { if (!condition) { throw new AssertionError(msg); } } function yell(str) { assert(typeof str === "string"); // assert 이후로 str은 string으로 확실하게 좁혀진다 //아래 함수는 철자가 잘못되었으므로 타입스크립트 컴파일러가 에러를 잡아준다. return str.toUppercase(); }
결론
위의 방법들 많고도 더 있어 보인다. 방법이 너무 많다. 이러한 방법을 모두 외우고 기억하는 것은 좋은 방향은 아닌 것 같다. 중요한 것은 타입을 한정 지을 수 있는 어떤 구문을 만나면(타입가드) 타입스크립트 컴파일러는 이를 분석하고 그다음 코드에서 타입을 좁힐 수 있다(타입내로잉)는 원리를 이해하고 자주 써보는 것으로 보인다.
타입 캐스팅이 가장 간단하지만, 객체에 내가 원하는 프로퍼티가 없을 수도 있으므로 확인하는 코드가 필요하다. 이러한 코드는 'key1' in obj, 'key2' in obj... 와 같은 코드 진행을 갖기에 번거로운 부분이 있다. class-transformer, class-validator 같은 라이브러리가 선호된 이유이기도 하다.
'간단기법' 카테고리의 다른 글
에러 핸들링, PHP, mysqli, prepared statement (0) 2023.05.17 PHP 문득 궁금한거 생길때 정리 (0) 2023.05.16 매크로 expand(STRING_CAT 매크로를 예시로) (0) 2022.10.05 문자열 뒤집기 두 가지 버전 (0) 2022.09.17 [PowerShell] 디렉토리에서 postfix 없애기 (0) 2022.09.17