I/O 소스로 JSON 직렬화하기
I/O 소스로 JSON 직렬화하기
Kotlin 직렬화 라이브러리는 JVM 스트림과 kotlinx-io 또는 Okio의 source·sink를 다루는 API를 제공해요.
이 API를 쓰면 중간 문자열을 만들지 않고 I/O 소스에서 직접 JSON을 직렬화·역직렬화할 수 있어요. 이 API는 UTF-8 인코딩을 사용하며, 잘못된 JSON 데이터에는 SerializationException, I/O 실패에는 IOException을 던집니다.
I/O 리소스를 다룰 때는 자원 누수를 막기 위해 제대로 닫아 주는 게 중요해요. .use() 함수를 쓰면 작업이 끝날 때 리소스를 자동으로 닫아 줍니다.
본문
JSON을 JVM 출력 스트림으로 직렬화하기
.encodeToStream() 확장 함수를 쓰면 JSON을 JVM OutputStream에 직접 직렬화할 수 있어요.
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.io.FileOutputStream
@Serializable
data class Project(val name: String, val stars: Int)
fun main() {
val project = Project("kotlinx.serialization", 9000)
// Creates an OutputStream for the project.json file
FileOutputStream("project.json").use { output ->
// Serializes the project instance into the OutputStream
Json.encodeToStream(project, output)
}
}
이 예시에서 Project의 JSON 표현이 project.json 파일로 직렬화돼요.
JSON을 JVM 입력 스트림에서 역직렬화하기
JVM InputStream에서 JSON을 직접 역직렬화하려면 .decodeFromStream() 확장 함수를 사용하면 됩니다.
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.io.FileInputStream
@Serializable
data class Project(val name: String, val stars: Int)
fun main() {
// Opens an InputStream
FileInputStream("project.json").use { input ->
// Deserializes the JSON contents of the InputStream into a Project instance
val project = Json.decodeFromStream<Project>(input)
// Prints the deserialized Project instance
println(project)
}
}
이 예시에서 입력 스트림의 JSON 내용은 하나의 Project 인스턴스로 역직렬화돼요.
입력이 최상위 JSON 배열이나 공백으로 구분된 객체들 속에 여러 JSON 객체를 담고 있다면, .decodeToSequence()로 요소들을 지연(lazily) 처리할 수 있어요. 이렇게 하면 파싱되는 대로 각 값을 처리할 수 있죠. 예를 들면 다음과 같아요.
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import java.io.FileInputStream
@Serializable
data class Project(val name: String, val stars: Int)
fun main() {
// Opens an InputStream for the projects.json file containing a JSON array of Project objects
FileInputStream("projects.json").use { input ->
// Lazily deserializes each Project from the InputStream
val projects = Json.decodeToSequence<Project>(input)
// Processes elements one by one
for (project in projects) {
println(project)
}
}
}
.decodeToSequence()가 반환한 시퀀스는 내부적으로 기본 스트림과 연결되어 있어서 한 번만 반복할 수 있어요.
시퀀스를 완전히 평가하기 전에 내부 스트림이 닫히면, 시퀀스에 접근할 때 예외가 발생할 수 있습니다.
kotlinx-io와 Okio로 JSON 직렬화하기
JVM 스트림 외에도 kotlinx.io.Sink와 kotlinx.io.Source(kotlinx-io 라이브러리의 타입, 현재 Alpha 단계)나 okio.BufferedSink와 okio.BufferedSource(Okio 라이브러리의 타입) 같은 I/O 타입으로도 JSON을 다룰 수 있어요.
다음 Json 확장 함수들을 사용해 이런 I/O 타입으로 JSON을 직접 읽고 쓸 수 있습니다.
.encodeToSink()와.encodeToBufferedSink()로kotlinx.io.Sink나okio.BufferedSink에 JSON을 쓸 수 있어요..decodeFromSource()와.decodeFromBufferedSource()로kotlinx.io.Source나okio.BufferedSource에서 단일 JSON 값을 읽을 수 있어요..decodeSourceToSequence()와.decodeBufferedSourceToSequence()로 여러 JSON 값을Sequence<T>로 지연 디코딩할 수 있어요.
다음 절들에서는 kotlinx-io 타입을 이 API와 함께 쓰는 예시를 다룰게요. Okio 타입도 해당 okio.BufferedSink·okio.BufferedSource API로 비슷하게 사용할 수 있어요.
kotlinx-io와 Okio 의존성 추가하기
kotlinx-io나 Okio 타입과 함께 확장 함수를 쓰려면 해당 의존성을 추가해야 해요.
kotlinx-io 의존성 추가하기
// build.gradle(.kts)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-io:1.11.0")
implementation("org.jetbrains.kotlinx:kotlinx-io-core:0.9.1")
}
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-io</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-io-core</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
Okio 의존성 추가하기
// build.gradle(.kts)
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.11.0")
implementation("com.squareup.okio:okio:3.16.2")
}
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-serialization-json-okio</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>3.16.2</version>
</dependency>
</dependencies>
Sink로 JSON 직렬화하기
Sink로 JSON을 직렬화하려면 .encodeToSink() 함수를 사용하면 돼요.
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
// Imports declarations for kotlinx-io types and JSON I/O support
import kotlinx.serialization.json.io.*
import kotlinx.io.*
import kotlinx.io.files.*
@Serializable
data class Project(val name: String, val stars: Int)
@OptIn(ExperimentalSerializationApi::class)
fun main() {
val project = Project("kotlinx.serialization", 9000)
// Creates a Sink for the project.json file
val path = Path("project.json")
SystemFileSystem.sink(path).buffered().use { sink: Sink ->
// Serializes the Project instance directly into a Sink
Json.encodeToSink(project, sink)
}
}
Source에서 JSON 역직렬화하기
Source에서 JSON을 역직렬화하려면 .decodeFromSource() 함수를 사용하면 돼요.
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
// Imports declarations for kotlinx-io types and JSON I/O support
import kotlinx.serialization.json.io.*
import kotlinx.io.*
import kotlinx.io.files.*
@Serializable
data class Project(val name: String, val stars: Int)
@OptIn(ExperimentalSerializationApi::class)
fun main() {
// Opens a Source for the project.json file
val path = Path("project.json")
SystemFileSystem.source(path).buffered().use { source: Source ->
// Deserializes a Project instance directly from a Source
val project = Json.decodeFromSource<Project>(source)
println(project)
}
}
입력이 큰 JSON 배열이나 여러 최상위 JSON 객체를 담고 있다면, .decodeSourceToSequence() 함수로 Source를 지연 디코딩되는 Sequence<T>로 바꿀 수 있어요.
// Imports declarations from the serialization library
import kotlinx.serialization.*
import kotlinx.serialization.json.*
// Imports declarations for kotlinx-io types and JSON I/O support
import kotlinx.serialization.json.io.*
import kotlinx.io.*
import kotlinx.io.files.*
@Serializable
data class Project(val name: String, val stars: Int)
@OptIn(ExperimentalSerializationApi::class)
fun main() {
// Opens a Source for the projects.json file containing multiple JSON objects
val path = Path("projects.json")
SystemFileSystem.source(path).buffered().use { source: Source ->
// Lazily deserializes each Project as it is read from the Source
val projects: Sequence<Project> = Json.decodeSourceToSequence(source)
for (project in projects) {
println(project)
}
}
}
.decodeSourceToSequence()가 반환한 시퀀스는 내부적으로 기본 Source와 연결되어 있어서 한 번만 반복할 수 있어요.
시퀀스를 완전히 평가하기 전에 내부 source가 닫히면, 시퀀스에 접근할 때 예외가 발생할 수 있습니다.
더 알아보기
- 다양한 직렬화·역직렬화 사용 사례에 맞춰 Json 인스턴스 커스터마이즈하는 방법을 배울 수 있어요.
- 파싱·직렬화 전에 JSON 데이터를 조작하고 다루려면 고급 JSON 요소 처리를 살펴볼 수 있어요.
- 직렬화·역직렬화 중 JSON을 변환하는 방법은 JSON 변환하기에서 더 제어할 수 있어요.