Spring Batch 입문 32편 — JSON Reader · Writer · Jackson · Gson

2026-05-17Spring Batch 입문에서 운영까지

Spring Batch 입문 32편. 모던 batch 의 표준 포맷 — JSON 처리. JsonItemReader · JsonFileItemWriter, JsonObjectReader 인터페이스 (Jackson · Gson 구현), JSON array 입력 가정, JsonObjectMarshaller 출력 직렬화, NDJSON 같은 변형 처리까지 정리한 학습 노트.

📚 Spring Batch 입문에서 운영까지 · 32편 — JSON Reader · Writer · Jackson · Gson

이 글은 Spring Batch 입문에서 운영까지 시리즈 48편 중 32편이에요. 31편 의 XML 처리에서 한 발 더 — 모던 batch 의 가장 흔한 포맷 JSON 처리.

Spring Batch 의 JSON 입력 가정

입력 JSON resource 가 JSON object 의 array 라고 가정. — 공식 reference

[
  {
    "isin": "123",
    "quantity": 1,
    "price": 1.2,
    "customer": "foo"
  },
  {
    "isin": "456",
    "quantity": 2,
    "price": 1.4,
    "customer": "bar"
  }
]

→ 최상위 = JSON array [...], 각 원소 = JSON object = item 1개.

NDJSON (newline-delimited JSON, JSON Lines) 같은 변형은 기본 지원 X — 별도 처리.

JsonItemReader 인터페이스

@Bean
public JsonItemReader<Trade> jsonReader() {
    return new JsonItemReaderBuilder<Trade>()
        .name("tradeJsonReader")
        .resource(new ClassPathResource("trades.json"))
        .jsonObjectReader(new JacksonJsonObjectReader<>(Trade.class))
        .build();
}

필수는 둘. resource 가 입력 JSON 파일을 가리키고, jsonObjectReader 가 JSON 파싱·매핑을 JsonObjectReader 인터페이스에 위임한다.

JsonObjectReader — JSON 파싱 추상화

public interface JsonObjectReader<T> {
    void open(Resource resource) throws Exception;
    T read() throws Exception;
    void close() throws IOException;
}

Spring Batch 가 특정 JSON 라이브러리 강제하지 않음 — 인터페이스만 만족하면 OK.

표준 구현 2종

구현 라이브러리
JacksonJsonObjectReader<T> Jackson — com.fasterxml.jackson.databind
GsonJsonObjectReader<T> Gson — com.google.gson

대부분 환경 = Jackson. Spring Boot 가 기본 포함.

JacksonJsonObjectReader 사용

@Bean
public JsonItemReader<Trade> jacksonReader() {
    return new JsonItemReaderBuilder<Trade>()
        .name("jacksonTradeReader")
        .resource(new ClassPathResource("trades.json"))
        .jsonObjectReader(new JacksonJsonObjectReader<>(Trade.class))
        .build();
}

생성자 인자 = target type (도메인 class). Trade.class 지정 = Jackson 이 각 JSON object → Trade 매핑.

GsonJsonObjectReader 사용

@Bean
public JsonItemReader<Trade> gsonReader() {
    return new JsonItemReaderBuilder<Trade>()
        .name("gsonTradeReader")
        .resource(new ClassPathResource("trades.json"))
        .jsonObjectReader(new GsonJsonObjectReader<>(Trade.class))
        .build();
}

Jackson vs Gson 선택 — 환경 의존성 + 팀 표준 으로 결정. 두 라이브러리 기능적 차이 거의 없음.

Streaming Parsing 의 중요성

JsonObjectReader 의 핵심:

streaming API 를 사용해 chunk 단위로 JSON object 를 read — 공식 reference

전체 JSON 을 메모리에 적재하지 않고 array 의 원소를 하나씩 추출.

→ XML 의 StAX (Streaming API for XML, 이벤트 기반 XML 파서) 와 같은 원리. 대용량 JSON 도 메모리 O(1) 처리.

Jackson JsonParser·Gson JsonReaderlow-level streaming API. JacksonJsonObjectReader·GsonJsonObjectReader 가 그 위 wrapping.

도메인 예제 — Trade

Jackson annotation

public class Trade {
    @JsonProperty("isin")
    private String isin;

    @JsonProperty("quantity")
    private long quantity;

    @JsonProperty("price")
    private BigDecimal price;

    @JsonProperty("customer")
    private String customer;

    // getter/setter
}

대부분 field 이름 = JSON key 일치 시 @JsonProperty 생략 가능. Jackson 이 snake_case ↔ camelCase명시 설정 시 자동 처리.

Java Record + Jackson

public record Trade(
    String isin,
    long quantity,
    BigDecimal price,
    String customer
) {}

Jackson 2.12+ = Record 자동 인식. setter 불필요. 모던 권장.

JsonFileItemWriter

@Bean
public JsonFileItemWriter<Trade> jsonWriter() {
    return new JsonFileItemWriterBuilder<Trade>()
        .name("tradeJsonWriter")
        .resource(new FileSystemResource("trades-out.json"))
        .jsonObjectMarshaller(new JacksonJsonObjectMarshaller<>())
        .build();
}

여기도 필수는 둘. resource 가 출력 JSON 파일이고, jsonObjectMarshaller 가 Java → JSON 변환을 담당한다.

JsonObjectMarshaller — 출력 추상화

public interface JsonObjectMarshaller<T> {
    String marshal(T item);
}

각 item → JSON String 변환.

표준 구현 2종

구현 라이브러리
JacksonJsonObjectMarshaller<T> Jackson
GsonJsonObjectMarshaller<T> Gson

출력 구조

[
  {"isin":"123","quantity":1,"price":1.2,"customer":"foo"},
  {"isin":"456","quantity":2,"price":1.4,"customer":"bar"}
]

JsonFileItemWriter 가:

  1. 파일 시작[ 작성
  2. 각 item — Marshaller 로 JSON string 생성 + comma 구분
  3. 파일 끝] 작성

표준 JSON array 구조 유지.

옵션

@Bean
public JsonFileItemWriter<Trade> tradeWriter() {
    return new JsonFileItemWriterBuilder<Trade>()
        .name("tradeWriter")
        .resource(new FileSystemResource("trades.json"))
        .jsonObjectMarshaller(new JacksonJsonObjectMarshaller<>())
        .encoding("UTF-8")
        .lineSeparator("\n")
        .append(false)
        .shouldDeleteIfEmpty(true)
        .shouldDeleteIfExists(false)
        .transactional(true)
        .headerCallback(w -> w.write("/* header comment */"))
        .footerCallback(w -> w.write("/* footer */"))
        .build();
}

FlatFileItemWriter (30편) 와 동일 옵션 다수.

End-to-end 예제

Reader

@Bean
@StepScope
public JsonItemReader<Trade> jsonReader(
        @Value("#{jobParameters['input.file']}") Resource resource) {
    return new JsonItemReaderBuilder<Trade>()
        .name("jsonReader")
        .resource(resource)
        .jsonObjectReader(new JacksonJsonObjectReader<>(Trade.class))
        .build();
}

Writer

@Bean
@StepScope
public JsonFileItemWriter<Trade> jsonWriter(
        @Value("#{jobParameters['output.file']}") Resource resource) {
    return new JsonFileItemWriterBuilder<Trade>()
        .name("jsonWriter")
        .resource(resource)
        .jsonObjectMarshaller(new JacksonJsonObjectMarshaller<>())
        .build();
}

Step

@Bean
public Step jsonStep(JobRepository repo, PlatformTransactionManager tx,
                     JsonItemReader<Trade> reader,
                     JsonFileItemWriter<Trade> writer) {
    return new StepBuilder("jsonStep", repo)
        .<Trade, Trade>chunk(100, tx)
        .reader(reader)
        .writer(writer)
        .build();
}

JSON → JSON pipeline 완성.

NDJSON 처리 — 직접 구현

NDJSON (Newline-Delimited JSON) = 각 line 이 독립 JSON object:

{"isin":"123","quantity":1}
{"isin":"456","quantity":2}
{"isin":"789","quantity":3}

Spring Batch 표준 지원 X — 직접 구현 또는 외부 라이브러리.

방법 1: FlatFileItemReader + Jackson

@Bean
public FlatFileItemReader<Trade> ndjsonReader() {
    return new FlatFileItemReaderBuilder<Trade>()
        .name("ndjsonReader")
        .resource(new FileSystemResource("trades.ndjson"))
        .lineMapper((line, lineNumber) ->
            objectMapper.readValue(line, Trade.class))
        .build();
}

FlatFileItemReaderline 단위 read + custom LineMapper 가 각 line 을 Jackson 으로 parse.

방법 2: Custom JsonObjectReader

26편 Custom Reader 패턴 — JsonObjectReader 직접 구현 + NDJSON 의 line 기반 streaming.

Jackson 추가 설정

ObjectMapper customization

기본 JacksonJsonObjectReader 의 ObjectMapper (Jackson 의 JSON ↔ Java 변환기) 가 기본 설정. custom ObjectMapper 사용:

@Bean
public ObjectMapper customMapper() {
    return new ObjectMapper()
        .registerModule(new JavaTimeModule())          // LocalDate 등
        .registerModule(new ParameterNamesModule())    // Record 강화
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

@Bean
public JacksonJsonObjectReader<Trade> jsonReader(ObjectMapper mapper) {
    return new JacksonJsonObjectReader<>(mapper, Trade.class);
}

FAIL_ON_UNKNOWN_PROPERTIES = false = 알 수 없는 필드 무시. 운영 batch 권장 (스키마 변경 흡수).

LocalDate·LocalDateTime

{"orderDate": "2026-05-17"}

Java LocalDate 매핑 = JavaTimeModule (Jackson 의 Java 8 시간 타입 지원 모듈) 등록 필수. Spring Boot 가 자동 등록Jackson2ObjectMapperBuilder 활용 시.

snake_case ↔ camelCase

mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);

JSON "order_date" ↔ Java orderDate 자동 변환.

자주 만나는 사고

사고 1: 최상위 array 가 아닌 단일 object

원인 — JSON 파일이 [{...}, {...}] 가 아닌 {...} (단일 object).

해결 — JSON 을 array 로 wrap 후 read, 또는 custom JsonObjectReader.

사고 2: Unknown property exception

원인 — Jackson 의 FAIL_ON_UNKNOWN_PROPERTIES = true (default).

해결 — Custom ObjectMapper 로 false 설정.

사고 3: Date format mismatch

원인 — JSON "2026-05-17T10:30:00" 가 Jackson 의 기본 ISO 처리 안 맞음.

해결@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "...") 또는 ObjectMapper config.

사고 4: Record 인식 안 됨

원인 — Jackson 버전 2.12 미만.

해결 — Jackson 2.12+ + ParameterNamesModule (생성자 파라미터 이름 기반 매핑 모듈) 등록. Spring Boot 3+ = 자동.

사고 5: BigDecimal 정밀도 손실

원인 — JSON 1.2 가 Java double 으로 parse 후 BigDecimal 변환 → 정밀도 손실.

해결mapper.enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS).

사고 6: 출력 JSON 의 indent 없음

원인JacksonJsonObjectMarshaller 기본 = compact.

해결 — Custom ObjectMapper + mapper.enable(SerializationFeature.INDENT_OUTPUT).

사고 7: NDJSON 을 표준 JsonItemReader 로 시도

원인 — NDJSON 은 array 아닌 line-based.

해결 — FlatFileItemReader + custom LineMapper 또는 custom JsonObjectReader.

사고 8: 출력 array 가 append 모드 에서 깨짐

원인append(true) + 두 번째 실행 → ][ 가 중간에 생김.

해결append(false) + shouldDeleteIfExists(true). 또는 NDJSON 으로 전환.

운영 권장 패턴

Pattern 1: 표준 Jackson reader

@Bean
@StepScope
public JsonItemReader<Trade> tradeReader(
        @Value("#{jobParameters['input.file']}") Resource resource,
        ObjectMapper objectMapper) {
    return new JsonItemReaderBuilder<Trade>()
        .name("tradeReader")
        .resource(resource)
        .jsonObjectReader(new JacksonJsonObjectReader<>(objectMapper, Trade.class))
        .build();
}

Spring Boot 의 기본 ObjectMapper 재사용 — JavaTimeModule 자동 등록.

Pattern 2: NDJSON reader

@Bean
@StepScope
public FlatFileItemReader<Trade> ndjsonReader(
        @Value("#{jobParameters['input.file']}") Resource resource,
        ObjectMapper mapper) {
    return new FlatFileItemReaderBuilder<Trade>()
        .name("ndjsonReader")
        .resource(resource)
        .lineMapper((line, lineNumber) -> mapper.readValue(line, Trade.class))
        .build();
}

NDJSON 처리 표준 패턴. 대용량 stream 친화.

Pattern 3: XML → JSON 변환 batch

@Bean
public Step xmlToJsonStep(JobRepository repo, PlatformTransactionManager tx,
                          StaxEventItemReader<Trade> xmlReader,
                          JsonFileItemWriter<Trade> jsonWriter) {
    return new StepBuilder("xmlToJson", repo)
        .<Trade, Trade>chunk(100, tx)
        .reader(xmlReader)
        .writer(jsonWriter)
        .build();
}

XML 원본 → JSON 출력. format 변환 batch.

Pattern 4: JSON → DB

@Bean
public Step jsonToDbStep(JobRepository repo, PlatformTransactionManager tx,
                          JsonItemReader<Trade> jsonReader,
                          JdbcBatchItemWriter<Trade> dbWriter) {
    return new StepBuilder("jsonToDb", repo)
        .<Trade, Trade>chunk(500, tx)
        .reader(jsonReader)
        .writer(dbWriter)
        .build();
}

JSON dump → DB 적재. 외부 시스템 동기화 패턴.

Pattern 5: Indent + 가독성 출력

@Bean
public ObjectMapper prettyMapper() {
    return new ObjectMapper()
        .registerModule(new JavaTimeModule())
        .enable(SerializationFeature.INDENT_OUTPUT)
        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
}

@Bean
public JsonFileItemWriter<Trade> prettyWriter(ObjectMapper mapper) {
    return new JsonFileItemWriterBuilder<Trade>()
        .name("prettyWriter")
        .resource(new FileSystemResource("trades-pretty.json"))
        .jsonObjectMarshaller(new JacksonJsonObjectMarshaller<>(mapper))
        .build();
}

운영자 검토용 가독성 출력. 성능보다 사람이 읽기 좋게.

시험 직전 한 번 더 — JSON Reader/Writer 함정 압축 노트

  • 입력 가정 = JSON object 의 array ([{...}, {...}])
  • NDJSON = 표준 지원 X (FlatFileItemReader + custom LineMapper 로 처리)
  • JsonItemReader 2가지 필수 = resource · jsonObjectReader
  • JsonFileItemWriter 2가지 필수 = resource · jsonObjectMarshaller
  • JsonObjectReader 인터페이스 = open·read·close (ItemStream 유사)
  • JsonObjectMarshaller 인터페이스 = marshal(item) → JSON String
  • 표준 구현 2종 = Jackson (JacksonJsonObjectReader · JacksonJsonObjectMarshaller) · Gson (GsonJsonObjectReader · GsonJsonObjectMarshaller)
  • Jackson = 대부분 환경 (Spring Boot 기본)
  • streaming API = Jackson JsonParser · Gson JsonReader — 메모리 O(1)
  • target type = new JacksonJsonObjectReader<>(Trade.class)
  • custom ObjectMapper = new JacksonJsonObjectReader<>(mapper, Trade.class)
  • Java Record = Jackson 2.12+ 자동 인식
  • 출력 구조 = [{...},{...}] 표준 array
  • 옵션 — encoding · lineSeparator · append · shouldDeleteIfEmpty · shouldDeleteIfExists · transactional · headerCallback · footerCallback (FlatFile 과 동일)
  • 함정 — 단일 object 입력 (array 가 아님) → custom reader
  • 함정 — Unknown property → FAIL_ON_UNKNOWN_PROPERTIES = false
  • 함정 — LocalDate ↔ JavaTimeModule 등록 필수
  • 함정 — BigDecimal 정밀도 → USE_BIG_DECIMAL_FOR_FLOATS
  • 함정 — indent 없음 → INDENT_OUTPUT
  • 함정 — append + array 가 ][ 깨짐 → append 금지 또는 NDJSON
  • snake_case ↔ camelCase = PropertyNamingStrategies.SNAKE_CASE
  • 패턴 — 표준 Jackson + Spring Boot ObjectMapper 재사용
  • 패턴 — NDJSON = FlatFileItemReader + custom LineMapper
  • 패턴 — XML → JSON / JSON → DB 변환 batch
  • 패턴 — pretty output (INDENT_OUTPUT)

공식 문서: JSON Item Readers And Writers 에서 원문을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!