우아한테크코스 8기 precourse

JSON DTO Converter (2) - JSON 분석

hwangsoojin 2025. 11. 24. 17:16

JsonValidator → JsonNode → SchemaNode → TypeInferencer → ModelGraph

"JSON이 어떻게 DTO 설계도로 변환되는가?"를 깊이 있게 분석한다

이 글은 JSON DTO Converter의 핵심 로직이 담긴 "JSON 분석 계층"을 집중적으로 다룬다.

이 계층은 도구 전체의 심장이다.

  • JSON 파일을 로드하고
  • 구조를 분석하여
  • Java 타입을 추론하고
  • DTO 클래스 구조(ModelGraph)를 만드는

모든 과정이 여기에 포함된다.


0. JSON 분석 계층이 왜 중요한가?

CLI 계층이 "입력 검증"을 담당했다면,
JSON 분석 계층은 실제로 JSON을 분석하여 DTO 설계도를 만들어내는 엔진이다.

 

즉, JSON 분석 계층은 다음 역할을 한다:

JSON 파일           → JsonValidator
JsonNode 트리       → JsonAnalyzer
SchemaNode 스키마   → TypeInferencer
Java 타입           → ModelGraph
DTO ClassSpec       → Generator 계층 전달
 

여기서 잘못되면 올바른 DTO를 만들 수 없다.
그래서 이 계층이 가장 중요하며, 가장 정교하게 설계되어야 한다.


1. JsonValidator - JSON 파일을 안전하게 로드하기 위한 필터

JsonValidator는 프로그램 전체에서 가장 먼저 JSON 파일을 직접 다루는 계층이다.


✔ JsonValidator가 수행하는 검증 리스트

1) 파일 존재 여부 확인

  • 경로가 존재해야 한다
  • 존재하지만 디렉터리라면 금지

2) 파일 크기 제한 (5MB)

왜 제한을 둘까?

  • JSON이 지나치게 크면 메모리 사용량이 폭증한다
  • CLI 도구는 즉각적인 응답성이 중요하다
  • 파일 크기가 비정상적으로 크다면 사용자의 입력 실수 가능성 ↑

따라서 5MB 제한은 "안전 장치"이다.


3) BOM(Byte Order Mark) 검사 및 제거

💡 BOM이란?

일부 Windows 에디터는 UTF-8 파일을 저장할 때
맨 앞에 다음 3바이트를 붙인다:

EF BB BF
 

이게 BOM이다.

 

문제는 JSON의 시작은 {여야 하는데
BOM이 있으면…

{
 

처럼 보이지 않는 쓰레기 바이트가 앞에 붙는다.
파서에 따라 오류가 날 수 있으므로 반드시 제거해야 한다.

JsonValidator의 처리 방식:

  1. 파일을 raw byte로 읽는다
  2. 첫 3바이트가 BOM인지 검사
  3. 맞으면 제거
  4. 제거 여부를  boolean hadBom  에 기록

이것이 JsonValidator.Result 내부에 포함된다.


4) JSON 문법 파싱

Jackson  ObjectMapper.readTree()  사용.


5) 루트 타입 검사

루트는 반드시 ObjectNode여야 한다.
배열 루트는 지원하지 않는다.


✔ JsonValidator.Result

JsonValidator는 다음 데이터를 함께 반환한다:

필드 설명
 JsonNode root  JSON 루트 트리
 boolean hadBom  BOM 제거 여부
 sizeBytes  파일 크기

이 값들은 이후 JSON 분석 단계에서 활용된다.


2. JsonNode Tree Model — JSON을 메모리 트리로 표현하는 방식

JsonNode는 Jackson이 제공하는 트리 기반 JSON 모델이다.

예를 들어 아래 JSON을 보자:

{
  "user": {
    "name": "Alice",
    "age": 20
  }
}
 

JsonNode 트리 구조는 텍스트가 아니라 실제로 이렇게 생겼다:

ObjectNode
 └─ "user" → ObjectNode
         ├─ "name" → TextNode("Alice")
         └─ "age"  → IntNode(20)

JsonNode를 사용하는 이유

1) 구조적 탐색에 최적화

필드 키, 값, 노드 타입 등에 대해 자유롭게 탐색 가능.

2) JSON 스키마 분석을 정확하게 수행 가능

값이 객체인지, 배열인지, 숫자인지 쉽게 판단할 수 있음.

3) "스트리밍(JSON parser)"보다 더 안정적

  • 스트리밍 파서는 순차 처리라 구조 분석이 어렵다
  • DTO 구조 생성에는 트리 기반 파싱(Tree Model)이 필수

즉, JsonNode는 "스키마 분석용 내부 AST(Abstract Syntax Tree)"라고 볼 수 있다.


3. SchemaNode — 도구 내부 JSON 스키마 표현 모델

JsonAnalyzer는 JsonNode를 기반으로 JSON 구조를
언어 독립적인 계층 구조로 추출한다.

 

이것이 SchemaNode다.


✔ SchemaNode 구조 요약

SchemaNode
 ├─ SchemaPrimitive   : 문자열/숫자/boolean/null
 ├─ SchemaObject      : 필드 이름 → FieldInfo
 ├─ SchemaArray       : 요소 타입의 Set
 └─ SchemaUnion       : 혼합된 여러 타입 (예: [1, "text"])
 

각 타입은 서로 조합될 수 있다.


3-1. SchemaPrimitive

JSON 원시값에 해당한다.

JSON 값 SchemaPrimitive 타입
 "str"  STRING
 123  INTEGER
 12.3  NUMBER
 true  BOOLEAN
 null  NULL

3-2. SchemaObject

예:

{
  "name": "Alice",
  "age": 20
}
 

 

SchemaObject:

SchemaObject
 ├─ name → FieldInfo(STRING)
 └─ age  → FieldInfo(INTEGER)
 

여기서 FieldInfo는 옵셔널 여부까지 계산한다.

▷ presenceCount

JSON 여러 개를 분석할 때(배열 등),
각 필드가 얼마나 등장했는지를 센다.

▷ optional 판단

예를 들어,

[{ "name": "A" }, { "name": "B", "age": 20 }]
 

 

출현 횟수:

필드 count total optional
name 2 2 false
age 1 2 true

3-3. SchemaArray

배열의 요소 타입을 모두 모아 Set으로 저장한다.

예:

["A", "B", "C"]
 

→  SchemaArray{ STRING } 

 

예:

[ true, 42 ]
 

→  SchemaArray{ BOOLEAN, INTEGER } 
→ 이 경우 SchemaUnion으로 변환됨


3-4. SchemaUnion — 값이 여러 타입일 때

예:

{ "value": [1, "A", true] }
 

분석:

value → SchemaUnion{
            INTEGER,
            STRING,
            BOOLEAN
        }
 

이는 TypeInferencer 단계에서
공통 상위 타입(Object) 또는 Wrapper 타입으로 축소된다.


4. TypeInferencer — SchemaNode → Java 타입 매핑

이제 스키마 구조를 바탕으로 Java 실제 타입을 추론한다.

주요 매핑 규칙

1) Primitive 매핑

SchemaPrimitive Java 타입
STRING String
INTEGER int
NUMBER double
BOOLEAN boolean
NULL Object (또는 nullable wrapper)

2) Optional 필드 → Wrapper 승격

예:

int → Integer
boolean → Boolean
 

이는 Java에서는 primitive 타입이 null을 허용하지 않기 때문이다.


3) SchemaArray → List<T>

예:

{ "tags": ["dev", "cs"] }
 

→  List<String> 


4) SchemaObject → 별도 DTO 클래스

이는 ModelGraph에서 클래스로 승격된다.


5) SchemaUnion → 공통 타입으로 수렴

예:

[1, 2, 3] → int
[1, 1.5] → double
[1, "a"] → Object

5. ModelGraph — 최종 DTO 클래스 설계도(IR)

ModelGraph는 TypeInferencer의 결과를 받아서
실제로 생성될 클래스들의 관계도를 만든다.


클래스 생성 전략

1) SchemaObject마다 하나의 ClassSpec 생성

예:

Root
├─ User
└─ Address
 

2) inner-classes 옵션 반영

  • true → Root 클래스 내부에 static class로 생성
  • false → 모두 독립된  .java  파일로 생성

ModelGraph가 해결하는 문제들

✔ 클래스 이름 충돌

동일한 이름이 나오면 suffix를 붙인다:

Location, Location2
 

✔ import 목록 관리

필드 타입이 다른 클래스라면 import
동일 패키지는 생략

 

✔ 클래스 계층 유지

중첩 구조 유지


6. JSON 분석 전체 흐름을 하나의 예시로 정리

아래 JSON을 예로 들어 과정 전체를 시각화해보자:

{
  "user": {
    "name": "Alice",
    "age": 20
  }
}

6-1. JsonNode 트리

ObjectNode
 └─ user : ObjectNode
        ├─ name : "Alice"
        └─ age  : 20

6-2. SchemaNode 트리

SchemaObject
 └─ user : SchemaObject
        ├─ name : STRING
        └─ age  : INTEGER

6-3. TypeInferencer 결과

  • user → User 클래스
  • name → String
  • age → int

6-4. ModelGraph 결과

RootDto
 └─ User user

User
 ├─ String name
 └─ int age

7. 이 설계의 장점 — 유지보수성과 확장성

✔ 1) JSON 형식만 바뀌어도 DTO 생성 자동 대응

DTO 작성 자동화

✔ 2) 다양한 JSON 구조에 미래 대응

  • 가변 필드
  • optional 필드
  • union 타입
  • 중첩 구조

✔ 3) 구조 변경에 강하다

스키마 → 타입 → 클래스 구조
각 단계가 독립되어 있어 유지보수가 쉽다.


8. JSON 분석 파트 요약

단계 설명
JsonValidator 파일 검증, BOM 제거, JSON 파싱
JsonNode 실제 JSON 트리 모델
SchemaNode 언어 독립적 스키마 구조
TypeInferencer SchemaNode → Java 타입 추론
ModelGraph DTO 클래스 구조 결정

이 다섯 단계가 JSON DTO Converter의 핵심 엔진이다.

 

다음 편에서는 JSON을 구조적으로 해석하는

JsonAnalyzer와 TypeInferencer의 내부 흐름을 살펴본다

👉 JSON DTO Converter (3) - Generator & 출력 분석