tag:crieit.net,2005:https://crieit.net/tags/Jackson/feed 「Jackson」の記事 - Crieit Crieitでタグ「Jackson」に投稿された最近の記事 2020-04-12T16:25:35+09:00 https://crieit.net/tags/Jackson/feed tag:crieit.net,2005:PublicArticle/15821 2020-04-12T16:25:35+09:00 2020-04-12T16:25:35+09:00 https://crieit.net/posts/data-class-JSON 単一プロパティを持つdata classをJSONのプリミティブ型として表現する <p>Value Objectとして使っている、単一プロパティを持つdata classを、シンプルなJSONとして表現するのにちょっと手こずったのでまとめておきます。</p> <h2 id="問題設定"><a href="#%E5%95%8F%E9%A1%8C%E8%A8%AD%E5%AE%9A">問題設定</a></h2> <p>例として、次のようなクラス定義を考えます。</p> <pre><code class="kt">data class User(val userId: UserId, val userName: UserName) data class UserId(val value: Int) data class UserName(val value: String) </code></pre> <p>UserオブジェクトをJacksonでシリアライズした場合、普通だと次のようになります。</p> <pre><code class="json">{"userId": {"value": 1}, "userName": {"value": "sato"<span>}</span><span>}</span> </code></pre> <p>これを次のようにシリアライズした上で、元の形にデシリアライズしたいというのが、この記事の主題です。</p> <pre><code class="json">{"userId": 1, "userName": "sato"} </code></pre> <p>なお、 <code>UserId</code> や <code>UserName</code> は <code>User</code> 以外のクラスからも使われる可能性があります。これらを使うクラス間で挙動を統一できるよう、クラス定義に手を加えるなら <code>User</code> よりも <code>UserId</code> や <code>UserName</code> に加えたいです。</p> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p>次のように、 <code>UserId</code> や <code>UserName</code> にアノテーション <code>@JsonCreator</code> と <code>@JsonValue</code> をつけると実現できます。</p> <pre><code class="kt">data class User(val userId: UserId, val userName: UserName) data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int) data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) </code></pre> <h2 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h2> <p>まず、 <a target="_blank" rel="nofollow noopener" href="http://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonValue.html">@JsonValue</a> はシリアライズに影響を与えるアノテーションで、シリアライズ結果を取得するメソッドに付けます。</p> <p>次に、 <a target="_blank" rel="nofollow noopener" href="http://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonCreator.html">@JsonCreator</a> はデシリアライズに影響を与えるアノテーションで、JSONの値からオブジェクトを作るためのコンストラクターやファクトリーメソッドに付けます。Javaの場合はコンストラクターに <code>@JsonCreator</code> をつけるだけでいいのですが、Kotlin Moduleを使っている場合はうまく行かないので、 <code>mode = JsonCreator.Mode.DELEGATING</code> が必要です [1]。</p> <p>なお、Kotlinでプライマリコンストラクターにアノテーションをつけるには、 <code>constructor</code> キーワードが必要です [2]。</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <p>実際のコードは次のようになります。</p> <p>build.gradleの一部</p> <pre><code class="gradle">dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "com.fasterxml.jackson.core:jackson-databind:2.9.10.4" implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.10" } </code></pre> <p>Main.kt</p> <pre><code class="kt">import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue fun main() { val u = User(UserId(1), UserName("sato")) println(u) val mapper = jacksonObjectMapper() val json = mapper.writeValueAsString(u) println(json) val parsed: User = mapper.readValue(json) println(parsed) } data class User(val userId: UserId, val userName: UserName) data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int) data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) </code></pre> <p>実行結果</p> <pre><code>User(userId=UserId(value=1), userName=UserName(value=sato)) {"userId":1,"userName":"sato"} User(userId=UserId(value=1), userName=UserName(value=sato)) </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li>java - How to instruct Jackson to serialize a field inside an Object instead of the Object it self? - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/11031110/how-to-instruct-jackson-to-serialize-a-field-inside-an-object-instead-of-the-object-it-self">https://stackoverflow.com/questions/11031110/how-to-instruct-jackson-to-serialize-a-field-inside-an-object-instead-of-the-object-it-self</a></li> <li>java - Is there a generic way to deserialize single-valued value objects (without custom Deserializer) in Jackson - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/55492657/is-there-a-generic-way-to-deserialize-single-valued-value-objects-without-custo">https://stackoverflow.com/questions/55492657/is-there-a-generic-way-to-deserialize-single-valued-value-objects-without-custo</a></li> </ul> <h2 id="注釈"><a href="#%E6%B3%A8%E9%87%88">注釈</a></h2> <ul> <li>[1] @jsonCreator on primary constructor is not working · Issue #305 · FasterXML/jackson-module-kotlin<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/FasterXML/jackson-module-kotlin/issues/305">https://github.com/FasterXML/jackson-module-kotlin/issues/305</a></li> <li>[2] https://kotlinlang.org/docs/reference/annotations.html#usage</li> </ul> かと