TIL

JSON 파일 읽고 쓰기

커스텀 LineMapper

jsonl 처리

```json lines {“command”:”destroy”,”cpu”:99,”status”:”memory overflow”} {“command”:”explode”,”cpu”:100,”status”:”cpu meltdown”} {“command”:”collapse”,”cpu”:95,”status”:”disk burnout”}


- 위 파일은 [json lines](https://jsonlines.org/) 형식의 파일이다.
    - json 배열을 작성할 때 `[ ]` 안에 구성하는 것이 아닌 각 줄마다 하나의 json 객체로 저장되어 있다.
    - 로그 파일, 스트리밍 데이터 처리 등에서 이런 형식이 사용된다.
- 한 줄씩 구성되어 있기에 아래처럼 `ItemReader`를 구성할 수 있다.
    - `lineMapper`에서 `objectMapper`를 사용해 역직렬화

```kotlin
@Bean
@StepScope
fun systemDeathReader(
    @Value("#{jobParameters['inputFile']}") inputFile: String,
): FlatFileItemReader<SystemDeath> {
    return FlatFileItemReaderBuilder<SystemDeath>()
        .name("systemDeathReader")
        .resource(ClassPathResource(inputFile))
        .lineMapper { line: String, _: Int ->
            objectMapper.readValue(line, SystemDeath::class.java)
        }
        .build()
}

포맷팅된 JSON

```json lines { “command”: “destroy”, “cpu”: 99, “status”: “memory overflow” } { “command”: “explode”, “cpu”: 100, “status”: “cpu meltdown” }


- 어디까지가 하나의 레코드인지 판별하기 위해 `recordSeparatorPolicy`를 사용한다.
    - `JsonRecordSeparatorPolicy`는 여는 중괄호(`{`)와 닫는 중괄호(`}`)를 기준으로 하나의 객체를 인식한다.

```kotlin
// ...
        .lineMapper { line: String, _: Int ->
            objectMapper.readValue(line, SystemDeath::class.java)
        }
        .recordSeparatorPolicy(JsonRecordSeparatorPolicy())
        .build()
}

JsonItemReader

[
    {"command": "destroy", "cpu": 99, "status": "memory overflow"},
    {"command": "explode", "cpu": 100, "status": "cpu meltdown"},
    {"command": "collapse", "cpu": 95, "status": "disk burnout"}
]
@Bean
@StepScope
fun systemDeathReader(
    @Value("#{jobParameters['inputFile']}") inputFile: String,
): JsonItemReader<SystemDeath> {
    return JsonItemReaderBuilder<SystemDeath>()
        .name("systemDeathReader")
        .jsonObjectReader(JacksonJsonObjectReader(objectMapper,SystemDeath::class.java))
        .resource(ClassPathResource(inputFile))
        .build()
}

코틀린의 data class를 사용하기 위해선 ObjectMapper에 특별한 설정이 필요한데 코프링을 쓰는 경우 빈 주입 받는 ObjectMapper에 그러한 처리가 이미 되어 있다. 위 코드처럼 빈 주입 받은 매퍼를 설정해주면 data class 역직렬화를 할 수 있게 된다. 그렇지 않으면 기본 생성자와 setter가 필요하게 되어버린다.

JsonFileItemWriter

JsonFileItemWriter 구성 요소

@Bean
@StepScope
fun systemDeathJsonWriter(
    @Value("#{jobParameters['outputDir']}") outputDir: String,
): JsonFileItemWriter<SystemDeath> =
    JsonFileItemWriterBuilder<SystemDeath>()
        .name("logEntryJsonWriter")
        .jsonObjectMarshaller(JacksonJsonObjectMarshaller())
        .resource(FileSystemResource("$outputDir/death_notes.json"))
        .build()