はじめに
Kotlin で JSON を使用した際のことについて記載しています.
最適な方法や間違った表記等があると思いますので, 指摘お願いします.
環境
- Kotlin version 1.6.10-release-923 (JRE 17.0.3+3)
- jackson-module-kotlin: 2.13.2
Kotlin: 1.2.31 (Kotlin version 1.2.31-release-95 (JRE 1.8.0_162-b12))jackson-module-kotlin: 2.9.5
jackson-module-kotlin の追加
Gradle の使用を想定 build.gradle -> dependencies
に以下を追加
compile group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.9.5'
LocalDateTime
に対応させるため, 以下のように修正いたしました.
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.13.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.2.2'
implementation group: 'com.fasterxml.jackson.module', name: 'jackson-module-kotlin', version: '2.13.2'
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.13.2'
build.gradle
については Kotlin Reference を参照してください.
単純な JSON
以下の JSON 文字列を対象にします.
{
"serial": 100,
"id": "pp100",
"title": "Paper Title",
"description": "Paper Description"
}
今回の場合, JSON と クラスのメンバの名称が一致しているため アノテーション @JsonPropery
は付ける必要はありません.
JSON から Class へ変換
1. JSON の形式に沿って以下のクラスを作成します.
class Paper(
@JsonProperty("serial") val serial: Long,
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String,
@JsonProperty("description") val description: String
)
2. Class への変換
import com.fasterxml.jackson.annotation.*
//import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.module.kotlin.*
class Paper(
@JsonProperty("serial") val serial: Long,
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String,
@JsonProperty("description") val description: String
)
fun main(args: Array<String>): Unit {
// 対象の JSON 文字列
val jsonString = "{\"serial\":100,\"id\":\"pp100\",\"title\":\"Paper Title\",\"description\":\"Paper Description.\"}"
/* ObjectMapper を作成
以下のうち 1 つを使用します. 場合によって import 文を追加してください.
import com.fasterxml.jackson.databind.*
*/
//val mapper = ObjectMapper().registerModule(KotlinModule())
//val mapper = ObjectMapper().registerKotlinModule()
val mapper = jacksonObjectMapper()
try {
//val paper = mapper.readValue<Paper>(jsonString)
val paper: Paper = mapper.readValue(jsonString)
println("[${paper.id}] ${paper.title}")
} catch(e: Exception) {
e.printStackTrace()
}
}
追加 日時(LocalDateTime) を含む Json の変換
この記事を書いた際, Class から Json への変換のみを確認しただけで日付を示す文字列が存在する Json のクラスへの変換は行っていませんでした. 数年ぶりに Android に携わることになり, 今回の問題を確認いたしました.
LocalDateTime
を含むクラスへ変換を行うと下記の例外発生します(一部省略).
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
at [Source: (String)"{"id":"f119cc22-33c5-495e-a790-36fd6ff722d7","name":"Example Name","createdAt":"2022-04-08T15:19:10.089517206"}"; line: 1, column: 80] (through reference chain: )
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1904)
at com.fasterxml.jackson.databind.deser.impl.UnsupportedTypeDeserializer.deserialize(UnsupportedTypeDeserializer.java:48)
...
JavaTimeModule
の登録
始めに jacksonObjectMapper
に対して, LocalDateTime
の復元処理を追加したモジュールの登録を行います.
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer
val timeModule = JavaTimeModule()
timeModule.addDeserializer<LocalDateTime>(
LocalDateTime::class.java,
LocalDateTimeDeserializer(DateTimeFormatter.ISO_DATE_TIME))
mapper.registerModule(timeModule)
Json から クラスへ変換
下記の Json 文字列を SampleClass
として定義されたクラスへの変換を行います.
{
"id" : "f119cc22-33c5-495e-a790-36fd6ff722d7",
"name" : "Example Name",
"createdAt" : "2022-04-08T15:19:10.089517206"
}
class SampleClass (
val id: UUID = UUID.randomUUID(),
val name: String = "Example",
val createdAt: LocalDateTime = LocalDateTime.now())
val sampleJson = "{\"id\":\"f119cc22-33c5-495e-a790-36fd6ff722d7\",\"name\":\"Example Name\",\"createdAt\":\"2022-04-08T15:19:10.089517206\"}";
val sampleClass = mapper.readValue(sampleJson, SampleClass::class.java)
println("${sampleClass.id} ${sampleClass.name} ${sampleClass.createdAt}")
実行結果は下記の通りとなり問題なく変換することができます.
f119cc22-33c5-495e-a790-36fd6ff722d7 Example Name 2022-04-08T15:19:10.089517206
ここからは個人的に使用していて, 対応したことについて記載しております.
- クラスから Json へ変換した場合に,
LocalDateTime
の形式について.
Json へ変換した際のLocalDateTime
の扱いが数値の配列となります.
日付を示す文字列のフォーマットを統一したいため次の調整方法を用いております.
{
"id" : "15dafacb-eaf0-4814-a697-8b6d3cc3628e",
"name":"Example",
"createdAt":[2022,4,8,15,32,0,444600164]
}
- 調整方法
jacksonObjectMapper
にdateFormat
を設定する.
val mapper = jacksonObjectMapper()
mapper.dateFormat = java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
Class から JSON へ変換
上記の記載事項とかぶるところがあるので Class の宣言等は省略します.
文字列への変換は ObjectMapper.writeValueAsString(Object value)
を使用します.
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.module.kotlin.*
fun main(args: Array<String>): Unit {
val paper = Paper(
serial = 101,
id = "pp101",
title = "Paper 101",
description = "Paper Description"
)
try {
val mapper = jacksonObjectMapper()
val jsonString = mapper.writeValueAsString(paper)
println(jsonString)
} catch(e: Exception) {
e.printStackTrace()
}
}
継承を利用した Class について
継承した Class を使用する場面があったので, それについて記述します.
Class の宣言
Paper クラスを継承するため open class Paper
に変更します.
open class Paper(
@JsonProperty("serial") val serial: Long,
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String,
@JsonProperty("description") val description: String
)
class ExtendPaper(
serial: Long,
id: String,
title: String,
description: String,
val registerDateTime: LocalDateTime = LocalDateTime.now()
) : Paper(serial = serial, id = id, title = title, description = description)
動作確認
以下のコードで動作を確認します.
import com.fasterxml.jackson.annotation.*
import com.fasterxml.jackson.module.kotlin.*
open class Paper(
@JsonProperty("serial") val serial: Long,
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String,
@JsonProperty("description") val description: String
)
class ExtendPaper(
serial: Long,
id: String,
title: String,
description: String,
val registerDateTime: LocalDateTime = LocalDateTime.now()
) : Paper(serial = serial, id = id, title = title, description = description)
fun main(args: Array<String>): Unit {
val paper = ExtendPaper(
serial = 101,
id = "pp101",
title = "Paper 101",
description = "Paper Description",
registerDateTime = LocalDateTime.now()
)
try {
val mapper = jacksonObjectMapper()
val jsonString = mapper.writeValueAsString(paper)
println(jsonString)
} catch(e: Exception) {
e.printStackTrace()
}
}
出力結果
出力された結果は以下のようになります.
{
"serial": 101,
"id": "pp101",
"title": "Paper 101",
"description": "Paper Description",
"registerDateTime": {
"year": 2018,
"monthValue": 4,
"month": "APRIL",
"dayOfMonth": 24,
"dayOfYear": 114,
"dayOfWeek": "TUESDAY",
"hour": 12,
"minute": 29,
"second": 22,
"nano": 84000000,
"chronology": {
"calendarType": "iso8601",
"id": "ISO"
}
}
}
メンバ変数を JSON に追加させない場合とパースする形を変更
継承した Class で LocalDateTime
がそのまま JSON に変換されてれいる.
このままでも問題はなっかたのですが, ISO 8601 形式で扱いたかったので少し手を加えました.
ExtendPaper クラスを変更
@JsonIgnore
, @JsonSetter
, @JsonGetter
の3つを追加します.
@JsonIgnore
を設定することで, そのメンバ変数が変換の対象外になります.
まとめて設定する方法等は下記を参考にしてください.
Jackson使い方メモ
@JsonSetter
, @JsonGetter
は value
に対応するメンバ変数を変換する際の専用のメソッドになります.
下記を参考にしてください.
シリアライズLOBパターンとJAX-RSを組み合わせる(Jackson, Doma)
class ExtendPaper(
serial: Long,
id: String,
title: String,
description: String,
@JsonIgnore val registerDateTime: LocalDateTime = LocalDateTime.now()
) : Paper(serial = serial, id = id, title = title, description = description) {
@JsonGetter(value="registerDateTime")
private fun ldtToString(): String {
return this.registerDateTime.toString()
}
@JsonSetter(value="registerDateTime")
private fun ldtFromString(str: String): LocalDateTime {
return LocalDateTime.parse(str)
}
}
出力結果
変更後の Class から JSON への変換結果は, 以下の様になります.
また, 以下のフォーマットで JSON から Class への変換も可能です.
{
"serial": 101,
"id": "pp101",
"title": "Paper 101",
"description": "Paper Description",
"registerDateTime": "2018-04-24T13:02:59.631"
}
参照文献
https://github.com/FasterXML/jackson-module-kotlin
jackson-databind wiki
Jackson使い方メモ
シリアライズLOBパターンとJAX-RSを組み合わせる(Jackson, Doma)