[TYPESCRIPT] TypeScript에서 JSON 다루기 - 타입 안정성으로 런타임 오류 줄이기

 

TypeScript 프로젝트에서 JSON은 거의 모든 곳에 등장합니다. API 응답, 설정 파일, 로컬 스토리지, 외부 데이터 연동 등 JSON은 사실상 프론트엔드와 백엔드를 잇는 표준 포맷입니다.

하지만 JSON은 본질적으로 타입 정보가 없는 문자열 데이터이기 때문에, TypeScript 환경에서도 방심하면 런타임 오류로 이어지기 쉽습니다. 

 

JSON과 TypeScript 타입의 본질적 차이

가장 먼저 명확히 이해해야 할 점은 다음입니다.

  • JSON은 런타임 데이터
  • TypeScript 타입은 컴파일 타임 개념

즉, JSON 데이터 자체는 TypeScript 타입을 절대 보장하지 않습니다. 이 차이를 인식하지 못하면 잘못된 타입 단언으로 위험한 코드를 만들게 됩니다.

 

JSON.parse의 기본 문제점

JSON.parse의 반환 타입은 항상 any입니다.

const data = JSON.parse(jsonString);
// data: any

이 상태에서는 TypeScript의 모든 타입 안전성이 사라집니다.

data.user.name.toUpperCase(); // 컴파일 통과, 런타임 오류 가능

따라서 JSON.parse 결과를 그대로 사용하는 것은 TypeScript를 쓰지 않는 것과 다르지 않습니다.

 

가장 흔한 실수 - 무분별한 타입 단언

type User = {
  id: number;
  name: string;
};

const user = JSON.parse(jsonString) as User;

이 코드는 언뜻 안전해 보이지만, 실제로는 매우 위험합니다.

  • JSON 구조가 달라도 컴파일 에러 없음
  • 필드 누락/타입 오류를 전혀 감지하지 못함

as는 검증이 아니라 단언이라는 점을 반드시 기억해야 합니다.

 

안전한 접근 unknown으로 시작하기

JSON을 파싱할 때 가장 안전한 출발점은 unknown입니다.

const raw: unknown = JSON.parse(jsonString);

이제 TypeScript는 이 값을 함부로 사용하지 못하게 막습니다.

raw.user; // X 오류

이 상태에서 명시적인 검증 로직을 거쳐야만 타입으로 변환할 수 있습니다.

 

타입 가드를 이용한 JSON 검증

type User = {
  id: number;
  name: string;
};

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value &&
    typeof (value as any).id === "number" &&
    typeof (value as any).name === "string"
  );
}
const raw: unknown = JSON.parse(jsonString);

if (isUser(raw)) {
  raw.name.toUpperCase(); // 안전
}

이 방식은 다소 코드가 길지만, 런타임 안정성을 가장 확실하게 보장합니다.

 

JSON.stringify와 타입

JSON.stringify는 비교적 안전하지만, 다음과 같은 점은 주의해야 합니다.

JSON.stringify({
  value: undefined,
  fn: () => {}
});
  • undefined → 제거됨
  • 함수 → 제거됨
  • Date → 문자열로 변환

따라서 JSON으로 직렬화할 객체는 미리 타입으로 제한하는 것이 좋습니다.

type SerializableUser = {
  id: number;
  name: string;
};

 

API 응답(JSON) 모델링 패턴

실무에서는 API 응답을 다음과 같이 모델링하는 경우가 많습니다.

type ApiResponse<T> =
  | { ok: true; data: T }
  | { ok: false; error: string };
const raw: unknown = JSON.parse(jsonString);

if (isApiResponse(raw)) {
  if (raw.ok) {
    // data 타입 안전
  }
}

JSON + Discriminated Union 조합은 대규모 프로젝트에서 사실상 표준 패턴입니다.

 

JSON 파일 import와 타입

TypeScript에서는 JSON 파일을 직접 import할 수도 있습니다.

{
  "name": "app",
  "version": "1.0.0"
}
import config from "./config.json";

이 경우 타입은 기본적으로 다음과 같습니다.

{
  name: string;
  version: string;
}

resolveJsonModule 옵션이 필요합니다.

{
  "compilerOptions": {
    "resolveJsonModule": true
  }
}

 

실무에서 권장되는 JSON 처리 전략

  • JSON.parse 결과는 unknown으로 받기
  • 타입 단언(as) 남용 금지
  • 타입 가드 또는 검증 함수로 구조 확인
  • API 응답은 Discriminated Union으로 모델링
  • 직렬화 대상은 명확한 타입으로 제한

이 원칙만 지켜도 JSON 관련 버그는 눈에 띄게 줄어듭니다.

 


 

 

TypeScript에서 JSON을 다룬다는 것은 “신뢰할 수 없는 데이터를 어떻게 신뢰 가능한 타입으로 변환할 것인가”의 문제입니다. JSON 자체를 믿는 것이 아니라, 검증 과정을 거쳐 타입으로 승격시키는 것이 중요한 포인트 입니다.

  • JSON.parse → unknown
  • 검증 → 타입 가드
  • 타입 확정 후 안전한 사용