JSON 포맷
JSON 포맷 (JSON Format)
JSON 포맷은 JSON 스키마에 기반해 JSON 데이터를 읽고 쓸 수 있게 해주는 직렬화/역직렬화 스키마 포맷이에요. 현재 JSON 스키마는 테이블 스키마에서 파생돼요.
출처: 문서
본문
JSON 포맷은 retract 스트림 및/또는 upsert 스트림을 명시적으로 지원하는 커넥터(예: Upsert Kafka 커넥터)를 사용하지 않는 한 append-only 스트림을 지원해요. retract 스트림 및/또는 upsert 스트림을 써야 한다면, Debezium JSON, Canal JSON 같은 CDC JSON 포맷을 살펴보는 것을 권장해요.
의존성 (Dependencies)
JSON 포맷을 사용하려면 빌드 자동화 도구(예: Maven 또는 SBT)를 사용하는 프로젝트와 SQL JAR 번들을 사용하는 SQL Client 양쪽 모두에서 다음 의존성이 필요해요.
org.apache.flink
flink-json
2.3.0
JSON 포맷으로 테이블 생성하기
Kafka 커넥터와 JSON 포맷을 사용해 테이블을 생성하는 예시예요.
CREATE TABLE user_behavior (
user_id BIGINT,
item_id BIGINT,
category_id BIGINT,
behavior STRING,
ts TIMESTAMP(3)
) WITH (
'connector' = 'kafka',
'topic' = 'user_behavior',
'properties.bootstrap.servers' = 'localhost:9092',
'properties.group.id' = 'testGroup',
'format' = 'json',
'json.fail-on-missing-field' = 'false',
'json.ignore-parse-errors' = 'true'
)
포맷 옵션 (Format Options)
| Option | Required | Forwarded | Default | Type | Description |
|---|---|---|---|---|---|
| format | required | no | (none) | String | 사용할 포맷을 지정. 여기서는 'json'이어야 해요. |
| json.fail-on-missing-field | optional | no | false | Boolean | 필드가 누락되었을 때 실패할지 여부. |
| json.ignore-parse-errors | optional | no | false | Boolean | 실패하는 대신 파싱 오류가 있는 필드와 행을 건너뜀. 오류가 있을 때 필드는 null로 설정돼요. |
| json.timestamp-format.standard | optional | yes | 'SQL' | String | TIMESTAMP 및 TIMESTAMP_LTZ 유형에 대한 입력·출력 타임스탬프 형식을 지정. 현재 지원 값은 'SQL'과 'ISO-8601': 'SQL' 옵션은 "yyyy-MM-dd HH:mm:ss.s{precision}" 형식으로 입력 TIMESTAMP 값을, "yyyy-MM-dd HH:mm:ss.s{precision}'Z'" 형식으로 입력 TIMESTAMP_LTZ 값을 파싱하고 같은 형식으로 타임스탬프를 출력해요. 'ISO-8601' 옵션은 "yyyy-MM-ddTHH:mm:ss.s{precision}" 형식으로 입력 TIMESTAMP를, "yyyy-MM-ddTHH:mm:ss.s{precision}'Z'" 형식으로 입력 TIMESTAMP_LTZ를 파싱하고 같은 형식으로 출력해요. |
| json.map-null-key.mode | optional | yes | 'FAIL' | String | 맵 데이터의 null 키를 직렬화할 때의 처리 모드를 지정. 현재 지원 값은 'FAIL', 'DROP', 'LITERAL': 'FAIL' 옵션은 null 키를 가진 맵을 만나면 예외를 던져요. 'DROP' 옵션은 맵 데이터에서 null 키 항목을 버려요. 'LITERAL' 옵션은 null 키를 문자열 리터럴로 대체해요. 문자열 리터럴은 json.map-null-key.literal 옵션으로 정의돼요. |
| json.map-null-key.literal | optional | yes | 'null' | String | 'json.map-null-key.mode'가 LITERAL일 때 null 키를 대체할 문자열 리터럴을 지정. |
| json.encode.decimal-as-plain-number | optional | yes | false | Boolean | 모든 소수를 과학적 표기법 대신 일반 숫자로 인코딩. 기본적으로 소수는 과학적 표기법으로 기록될 수 있어요. 예를 들어 0.000000027은 기본적으로 2.7E-8로 인코딩되며, 이 옵션을 true로 설정하면 0.000000027로 기록돼요. |
| json.encode.ignore-null-fields | optional | yes | false | Boolean | null이 아닌 필드만 인코딩. 기본적으로 모든 필드가 포함돼요. |
| decode.json-parser.enabled | optional | true | Boolean | JSON 디코딩에 Jackson JsonParser를 사용할지 여부. JsonParser는 JSON 데이터를 읽는 Jackson JSON 스트리밍 API예요. 이전 JsonNode 방식보다 훨씬 빠르고 메모리를 덜 소비해요. 또한 JsonParser는 데이터를 읽을 때 중첩 프로젝션 푸시다운(nested projection pushdown)도 지원해요. 이 옵션은 기본적으로 활성화돼 있어요. 호환성 문제가 발생하면 비활성화하고 이전 JsonNode 방식으로 대체할 수 있어요. |
데이터 타입 매핑 (Data Type Mapping)
현재 JSON 스키마는 항상 테이블 스키마에서 파생돼요. JSON 스키마를 명시적으로 정의하는 것은 아직 지원되지 않아요. Flink JSON 포맷은 JSON 문자열을 파싱하고 생성하기 위해 jackson databind API를 사용해요.
다음 표는 Flink 타입에서 JSON 타입으로의 타입 매핑을 나열해요.
| Flink SQL type | JSON type |
|---|---|
| CHAR / VARCHAR / STRING | string |
| BOOLEAN | boolean |
| BINARY / VARBINARY | string with encoding: base64 |
| DECIMAL | number |
| TINYINT | number |
| SMALLINT | number |
| INT | number |
| BIGINT | number |
| FLOAT | number |
| DOUBLE | number |
| DATE | string with format: date |
| TIME | string with format: time |
| TIMESTAMP | string with format: date-time |
| TIMESTAMP_WITH_LOCAL_TIME_ZONE | string with format: date-time (with UTC time zone) |
| INTERVAL | number |
| ARRAY | array |
| MAP / MULTISET | object |
| ROW | object |
기능 (Features)
최상위 JSON 배열 허용 (Allow top-level JSON Arrays)
보통 우리는 JSON 문자열의 최상위가 문자열화된 JSON 객체라고 가정해요. 그러면 이 문자열화된 JSON 객체는 하나의 SQL 행으로 변환될 수 있어요.
JSON 문자열의 최상위가 문자열화된 JSON 배열이고, 그 배열을 여러 레코드로 확장(explode)하고 싶은 경우가 있어요. 배열 안의 각 요소는 JSON 객체이고, 그러한 각 JSON 객체의 스키마는 SQL에 정의된 것과 같으며, 각 JSON 객체는 하나의 행으로 변환될 수 있어요. Flink JSON 포맷은 그러한 데이터 읽기를 지원해요.
예를 들어 다음 SQL DDL에 대해:
CREATE TABLE user_behavior (
col1 BIGINT,
col2 VARCHAR
) WITH (
'format' = 'json',
...
)
Flink JSON 포맷은 다음 두 JSON 문자열 모두에 대해 (123, "a")와 (456, "b") 두 행을 생성해요.
최상위가 JSON 배열인 경우:
[{"col1": 123, "col2": "a"}, {"col1": 456, "col2": "b"}]
최상위가 JSON 객체인 경우:
{"col1": 123, "col2": "a"}
{"col1": 456, "col2": "b"}