Repository: tipsy/javalin
Pull request #1448 (2021-12-12 22:32:28)

[jackson] Register java time module if exist
Also include in bundle

tipsy: Thanks for having a look! Someone on Discord also gave it a LGTM :) 

Patch:
diff --git a/javalin-bundle/pom.xml b/javalin-bundle/pom.xml
index 05478bce..c59d57bc 100644
--- a/javalin-bundle/pom.xml
+++ b/javalin-bundle/pom.xml
@@ -38,6 +38,10 @@
             <groupId>com.fasterxml.jackson.module</groupId>
             <artifactId>jackson-module-kotlin</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-jsr310</artifactId>
+        </dependency>
 
         <!-- necessary for HTTP/2 and ALPN (application layer protocol negotiation) -->
         <dependency>
diff --git a/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt b/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt
index 94a7f68a..b2965840 100644
--- a/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt
+++ b/javalin/src/main/java/io/javalin/core/util/OptionalDependency.kt
@@ -11,6 +11,7 @@ enum class OptionalDependency(val displayName: String, val testClass: String, va
     // JSON handling
     JACKSON("Jackson", "com.fasterxml.jackson.databind.ObjectMapper", "com.fasterxml.jackson.core", "jackson-databind", "2.12.4"),
     JACKSON_KT("JacksonKt", "com.fasterxml.jackson.module.kotlin.KotlinModule", "com.fasterxml.jackson.module", "jackson-module-kotlin", "2.12.4"),
+    JACKSON_JSR_310("JacksonJsr310", "com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", "com.fasterxml.jackson.datatype", "jackson-datatype-jsr310", "2.12.4"),
 
     // Logging
     SLF4JSIMPLE("Slf4j simple", "org.slf4j.impl.StaticLoggerBinder", "org.slf4j", "slf4j-simple", "1.7.31"),
diff --git a/javalin/src/main/java/io/javalin/core/util/Util.kt b/javalin/src/main/java/io/javalin/core/util/Util.kt
index 7533863e..43177635 100644
--- a/javalin/src/main/java/io/javalin/core/util/Util.kt
+++ b/javalin/src/main/java/io/javalin/core/util/Util.kt
@@ -28,7 +28,7 @@ object Util {
     @JvmStatic
     fun prefixContextPath(contextPath: String, path: String) = if (path == "*") path else ("$contextPath/$path").replace("/{2,}".toRegex(), "/")
 
-    private fun classExists(className: String) = try {
+    fun classExists(className: String) = try {
         Class.forName(className)
         true
     } catch (e: ClassNotFoundException) {
diff --git a/javalin/src/main/java/io/javalin/plugin/json/JavalinJackson.kt b/javalin/src/main/java/io/javalin/plugin/json/JavalinJackson.kt
index ab2f3a9f..c10b6794 100644
--- a/javalin/src/main/java/io/javalin/plugin/json/JavalinJackson.kt
+++ b/javalin/src/main/java/io/javalin/plugin/json/JavalinJackson.kt
@@ -51,12 +51,15 @@ class JavalinJackson(private var objectMapper: ObjectMapper? = null) : JsonMappe
     }
 
     companion object {
-        fun defaultMapper(): ObjectMapper = try {
-            val className = OptionalDependency.JACKSON_KT.testClass
-            ObjectMapper().registerModule(Class.forName(className).getConstructor().newInstance() as Module)
-        } catch (e: ClassNotFoundException) {
-            ObjectMapper()
-        }
+        fun defaultMapper(): ObjectMapper = ObjectMapper()
+                .registerOptionalModule(OptionalDependency.JACKSON_KT.testClass)
+                .registerOptionalModule(OptionalDependency.JACKSON_JSR_310.testClass)
     }
+}
 
+private fun ObjectMapper.registerOptionalModule(classString: String): ObjectMapper {
+    if (Util.classExists(classString)) {
+        this.registerModule(Class.forName(classString).getConstructor().newInstance() as Module)
+    }
+    return this
 }


Test patch:
diff --git a/javalin/src/test/java/io/javalin/TestJson.kt b/javalin/src/test/java/io/javalin/TestJson.kt
index 7f596e55..01cbde52 100644
--- a/javalin/src/test/java/io/javalin/TestJson.kt
+++ b/javalin/src/test/java/io/javalin/TestJson.kt
@@ -17,6 +17,7 @@ import io.javalin.testing.TestUtil
 import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.Test
 import java.io.InputStream
+import java.time.Instant
 
 class TestJson {
 
@@ -30,6 +31,14 @@ class TestJson {
         assertThat(response.body).isEqualTo(serializableObjectString)
     }
 
+    @Test
+    fun `default mapper can serialize instant`() = TestUtil.test { app, http ->
+        app.get("/") { ctx -> ctx.json(Instant.EPOCH) }
+        val response = http.get("/")
+        assertThat(response.headers.getFirst(Header.CONTENT_TYPE)).isEqualTo("application/json")
+        assertThat(response.body).isEqualTo("0.0")
+    }
+
     @Test
     fun `default mapper treats strings as already being json`() = TestUtil.test { app, http ->
         app.get("/") { ctx -> ctx.json("ok") }