Skip to content

Commit

Permalink
Merge branch 'jkschneider-master'
Browse files Browse the repository at this point in the history
Pull request #5
Kotlin 1.0.2
Code cleanup
  • Loading branch information
apatrida committed Jun 4, 2016
2 parents 43bcab4 + 99f7375 commit efe4bbf
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 72 deletions.
31 changes: 13 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
[![Kotlin](https://img.shields.io/badge/kotlin-1.0.0-blue.svg)](http://kotlinlang.org) [![Build Status](https://travis-ci.org/FasterXML/jackson-module-kotlin.svg)](https://travis-ci.org/FasterXML/jackson-module-kotlin) [![Kotlin Slack](https://img.shields.io/badge/chat-kotlin%20slack-orange.svg)](http://kotlinslackin.herokuapp.com)
[![Kotlin](https://img.shields.io/badge/kotlin-1.0.2-blue.svg)](http://kotlinlang.org) [![Build Status](https://travis-ci.org/FasterXML/jackson-module-kotlin.svg)](https://travis-ci.org/FasterXML/jackson-module-kotlin) [![Kotlin Slack](https://img.shields.io/badge/chat-kotlin%20slack-orange.svg)](http://kotlinslackin.herokuapp.com)

# Overview

Module that adds support for serialization/deserialization of [Kotlin](http://kotlinlang.org) classes and data classes. Previously a default constructor must have existed on the Kotlin object for Jackson to deserialize into the object. With this module, single constructor classes can be used automatically, and those with secondary constructors or static factories are also supported.

# Status

Older versions of the Jackson-Kotlin module are not compatible with Kotlin 1.0.0. You must update or you will have silent failures (the module cannot recognize a Kotlin class, so ignores it). Releases for Kotlin 1.0.0 are now in Maven Central:
[![Build Status](https://travis-ci.org/FasterXML/jackson-module-kotlin.svg)](https://travis-ci.org/FasterXML/jackson-module-kotlin)

For Kotlin 1.0.0, use one of:
Releases are available on Maven Central:

* release `2.7.1-2` (for Jackson `2.7.x`)
* release `2.7.5` (for Jackson `2.7.x`)
* release `2.6.5-2` (for Jackson `2.6.x`)
* release `2.5.5-2` (for Jackson `2.5.x`)

Expand All @@ -19,22 +19,18 @@ Releases require that you have included Kotlin stdlib and reflect libraries alre

Gradle:
```
compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.7.1-2"
compile "com.fasterxml.jackson.module:jackson-module-kotlin:2.7.5"
```

Maven:
```xml
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
<version>2.7.1-2</version>
<version>2.7.5</version>
</dependency>
```

# KNOWN PROBLEMS

In M12+ of Kotlin, keep your constructors simple, if you have default values for parameters then alternatively generated constructors might cause Jackson to not be able to select the correct constructor. Working on this for later releases.

# Usage

For any Kotlin class or data class constructor, the JSON property names will be inferred from the parameters using Kotlin runtime type information.
Expand Down Expand Up @@ -71,25 +67,24 @@ val state: MyStateObject = mapper.readValue(json)
myMemberWithType = mapper.readValue(json)
```

With `2.6.3-2` or newer of the module all inferred types for the extension functions carry in full generic information (reified generics).
Therefore using `readValue()` extension without the `Class` parameter will reify the type and automatically create a `TypeReference` for Jackson.

# Annotations

You can intermix non-field values in the constructor and `JsonProperty` annotation in the constructor. Any fields not present in the constructor will be set after the constructor call and therefore must be nullable with default value. An example of these concepts:
You can intermix non-field values in the constructor and `JsonProperty` annotation in the constructor. Any fields not present in the constructor will be set after the constructor call. An example of these concepts:

```kotlin
@JsonInclude(JsonInclude.Include.NON_EMPTY)
class StateObjectWithPartialFieldsInConstructor(val name: String, @JsonProperty("age") val years: Int) {
@JsonProperty("address") var primaryAddress: String? = null
var createdDt: DateTime by Delegates.notNull()
@JsonProperty("address") lateinit var primaryAddress: String // set after construction
var createdDt: DateTime by Delegates.notNull() // set after construction
var neverSetProperty: String? = null // not in JSON so must be nullable with default
}
```

Note that using Delegates.notNull() will ensure that the value is never null when read, while letting it be instantiated after the construction of the class.
Note that using `lateinit` or `Delegates.notNull()` will ensure that the value is never null when read, while letting it be instantiated after the construction of the class.

# Caveats

* The `JsonCreator` annotation is optional unless you have more than one constructor that is valid, or you want to use a static factory method (which also must have `platformStatic` annotation). In these cases, annotate only one method as `JsonCreator`.
* The `@JsonCreator` annotation is optional unless you have more than one constructor that is valid, or you want to use a static factory method (which also must have `platformStatic` annotation). In these cases, annotate only one method as `JsonCreator`.
* Currently we use parameter name information in Kotlin that is compatible with Kotlin M8 through M11
* Serializing a member or top-level Kotlin class that implements Iterator requires a workaround, see [Issue #4](https://github.com/FasterXML/jackson-module-kotlin/issues/4) for easy workarounds.

Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
</scm>

<properties>
<version.jackson.annotations>2.8.0-SNAPSHOT</version.jackson.annotations>
<version.jackson.core>2.7.4</version.jackson.core>
<version.jackson.annotations>2.8.0</version.jackson.annotations>
<version.jackson.core>2.8.0-SNAPSHOT</version.jackson.core>

<!-- Joda only for testing -->
<version.jackson.joda>${version.jackson.core}</version.jackson.joda>
Expand Down
30 changes: 15 additions & 15 deletions src/main/kotlin/com/fasterxml/jackson/module/kotlin/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ import java.io.InputStream
import java.io.Reader
import java.net.URL

public fun jacksonObjectMapper(): ObjectMapper = ObjectMapper().registerKotlinModule()
public fun ObjectMapper.registerKotlinModule(): ObjectMapper = this.registerModule(KotlinModule())
fun jacksonObjectMapper(): ObjectMapper = ObjectMapper().registerKotlinModule()
fun ObjectMapper.registerKotlinModule(): ObjectMapper = this.registerModule(KotlinModule())

public inline fun <reified T: Any> ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectMapper.readValues(jp: JsonParser): MappingIterator<T> = readValues(jp, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(jp: JsonParser): T = readValue(jp, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValues(jp: JsonParser): MappingIterator<T> = readValues(jp, object: TypeReference<T>() {})

public inline fun <reified T: Any> ObjectMapper.readValue(src: File): T = readValue(src, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectMapper.readValue(src: URL): T = readValue(src, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectMapper.readValue(src: Reader): T = readValue(src, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectMapper.readValue(src: InputStream): T = readValue(src, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectMapper.readValue(src: ByteArray): T = readValue(src, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(src: File): T = readValue(src, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(src: URL): T = readValue(src, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(content: String): T = readValue(content, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(src: Reader): T = readValue(src, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(src: InputStream): T = readValue(src, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.readValue(src: ByteArray): T = readValue(src, object: TypeReference<T>() {})

public inline fun <reified T: Any> ObjectMapper.treeToValue(n: TreeNode): T = treeToValue(n, T::class.java)
public inline fun <reified T: Any> ObjectMapper.convertValue(from: Any): T = convertValue(from, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectMapper.treeToValue(n: TreeNode): T = treeToValue(n, T::class.java)
inline fun <reified T: Any> ObjectMapper.convertValue(from: Any): T = convertValue(from, object: TypeReference<T>() {})

public inline fun <reified T: Any> ObjectReader.readValue(jp: JsonParser): T = readValue(jp, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectReader.readValues(jp: JsonParser): Iterator<T> = readValues(jp, object: TypeReference<T>() {})
public inline fun <reified T: Any> ObjectReader.treeToValue(n: TreeNode): T = treeToValue(n, T::class.java)
inline fun <reified T: Any> ObjectReader.readValue(jp: JsonParser): T = readValue(jp, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectReader.readValues(jp: JsonParser): Iterator<T> = readValues(jp, object: TypeReference<T>() {})
inline fun <reified T: Any> ObjectReader.treeToValue(n: TreeNode): T = treeToValue(n, T::class.java)
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.fasterxml.jackson.module.kotlin

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.databind.Module.SetupContext
import com.fasterxml.jackson.databind.introspect.*
import com.fasterxml.jackson.databind.module.SimpleModule
import java.lang.reflect.Constructor
Expand All @@ -17,7 +16,7 @@ fun Class<*>.isKotlinClass(): Boolean {
return this.declaredAnnotations.singleOrNull { it.annotationClass.java.name == metadataFqName } != null
}

public class KotlinModule() : SimpleModule(PackageVersion.VERSION) {
class KotlinModule() : SimpleModule(PackageVersion.VERSION) {
companion object {
private val serialVersionUID = 1L;
}
Expand All @@ -29,7 +28,7 @@ public class KotlinModule() : SimpleModule(PackageVersion.VERSION) {
Triple::class.java
))

override public fun setupModule(context: SetupContext) {
override fun setupModule(context: SetupContext) {
super.setupModule(context)

fun addMixin(clazz: Class<*>, mixin: Class<*>) {
Expand All @@ -56,18 +55,18 @@ internal class KotlinNamesAnnotationIntrospector(val module: KotlinModule) : Nop
*/

// since 2.4
override public fun findImplicitPropertyName(member: AnnotatedMember): String? {
override fun findImplicitPropertyName(member: AnnotatedMember): String? {
if (member is AnnotatedParameter) {
return findKotlinParameterName(member)
}
return null
}

@Suppress("UNCHECKED_CAST")
override public fun hasCreatorAnnotation(member: Annotated): Boolean {
override fun hasCreatorAnnotation(member: Annotated): Boolean {
// don't add a JsonCreator to any constructor if one is declared already

if (member is AnnotatedConstructor) {
if (member is AnnotatedConstructor && !member.declaringClass.isEnum) {
// if has parameters, is a Kotlin class, and the parameters all have parameter annotations, then pretend we have a JsonCreator
if (member.getParameterCount() > 0 && member.getDeclaringClass().isKotlinClass()) {
val kClass = (member.getDeclaringClass() as Class<Any>).kotlin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test

public class TestExtensionMethods {
class TestExtensionMethods {
val mapper: ObjectMapper = jacksonObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, false)

data class BasicPerson(val name: String, val age: Int)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.junit.Test
import kotlin.test.assertEquals

public class TestGithub15 {
@Test public fun testEnumConstructorWithParm() {
class TestGithub15 {
@Test fun testEnumConstructorWithParm() {
val one = jacksonObjectMapper().readValue("\"ONE\"", TestEnum::class.java)
assertEquals(TestEnum.ONE, one)
val two = jacksonObjectMapper().readValue("\"TWO\"", TestEnum::class.java)
assertEquals(TestEnum.TWO, two)
}

@Test public fun testNormEnumWithoutParam() {
@Test fun testNormEnumWithoutParam() {
val one = jacksonObjectMapper().readValue("\"ONE\"", TestOther::class.java)
assertEquals(TestOther.ONE, one)
val two = jacksonObjectMapper().readValue("\"TWO\"", TestOther::class.java)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package com.fasterxml.jackson.module.kotlin.test

import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonValue
import com.fasterxml.jackson.module.kotlin.*
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.junit.Test
import java.io.File
import java.io.PrintWriter
import kotlin.test.assertEquals

class StringValue constructor (s: String) {
class StringValue constructor(s: String) {
val other: String = s

@JsonValue override fun toString() = other
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
package com.fasterxml.jackson.module.kotlin.test

import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.annotation.*
import org.junit.Ignore
import org.junit.Test

public class TestHiddenKotlinThings {
class TestHiddenKotlinThings {

// make a class seem kinda like a Java class, but with a second generated constructor
// this is Ugly Kotlin but is making it easier for Java people to see and debug.
class Something @JsonCreator constructor (@JsonProperty("name") name: String,
@JsonProperty("age") age: Int = 0) { // default value causes synthetic constructor to be generated
public val name: String = name
public val age: Int = age
class Something @JsonCreator constructor(@JsonProperty("name") name: String,
@JsonProperty("age") age: Int = 0) { // default value causes synthetic constructor to be generated
val name: String = name
val age: Int = age
}

@Test public fun testSyntheticGeneratedConstructorIsIgnored() {
val thing: Something = ObjectMapper().readValue("""{"name":"fred","age":99}""")
@Test fun testSyntheticGeneratedConstructorIsIgnored() {
val thing: Something = ObjectMapper().readValue("""{"name":"fred","age":99}""")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package com.fasterxml.jackson.module.kotlin.test

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.ObjectWriter
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.module.kotlin.*
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Ignore
import org.junit.Test


public class TestIteratorSubclass {
class TestIteratorSubclass {
class TinyPerson(val name: String, val age: Int)
class KotlinPersonIterator(private val personList: List<TinyPerson>) : Iterator<TinyPerson> by personList.iterator() {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package com.fasterxml.jackson.module.kotlin.test

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.*
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test

public class TestJacksonWithKotlinBuiltins {
class TestJacksonWithKotlinBuiltins {
private val mapper: ObjectMapper = jacksonObjectMapper().configure(SerializationFeature.INDENT_OUTPUT, false)

private data class ClassWithPair(val name: Pair<String, String>, val age: Int)

@Test fun testPair() {
val json = """{"name":{"first":"John","second":"Smith"},"age":30}"""
val expected = ClassWithPair(Pair("John", "Smith"), 30)
Expand All @@ -21,6 +23,7 @@ public class TestJacksonWithKotlinBuiltins {
}

private data class ClassWithPairMixedTypes(val person: Pair<String, Int>)

@Test fun testPairMixedTypes() {
val json = """{"person":{"first":"John","second":30}}"""
val expected = ClassWithPairMixedTypes(Pair("John", 30))
Expand All @@ -31,6 +34,7 @@ public class TestJacksonWithKotlinBuiltins {
}

private data class ClassWithTriple(val name: Triple<String, String, String>, val age: Int)

@Test fun testTriple() {
val json = """{"name":{"first":"John","second":"Davey","third":"Smith"},"age":30}"""
val expected = ClassWithTriple(Triple("John", "Davey", "Smith"), 30)
Expand All @@ -41,6 +45,7 @@ public class TestJacksonWithKotlinBuiltins {
}

private data class ClassWithRanges(val ages: IntRange, val distance: LongRange)

@Test fun testRanges() {
val json = """{"ages":{"start":18,"end":40},"distance":{"start":5,"end":50}}"""
val expected = ClassWithRanges(IntRange(18, 40), LongRange(5, 50))
Expand Down
Loading

0 comments on commit efe4bbf

Please sign in to comment.