perf: drop jackson-module-kotlin, removing kotlin-reflect from the app

Nothing in the app binds JSON reflectively any more, so the module whose whole
job is reflective Kotlin binding has no work left to do. Swapping it for plain
jackson-databind takes kotlin-reflect out with it:

    kotlin.reflect.jvm.internal          969 -> 0 classes
    com.fasterxml.jackson.module.kotlin   73 -> 0 classes
    DEX                              30.94 MB -> 29.94 MB, 3 files -> 2

A megabyte and a whole secondary DEX, for code the app never called.

`jacksonObjectMapper()` became `ObjectMapper()` in all eight remaining places:
JacksonMapper and JsonMapperNip55 (both drive registered StdSerializer /
StdDeserializer pairs) and six readTree-only users (LNURL resolvers, TorService,
ArtiGuardState, Nip96Uploader). None of them needed the Kotlin module.

The one thing the module did that databind has no equivalent for is
`jacksonTypeRef<T>()`, which NIP-55 needs: `readValue<List<IntentResult>>` erases
to `List` under `T::class.java` and every element comes back a LinkedHashMap. It
is a one-liner — `reified` substitutes the concrete type into the anonymous
subclass before erasure — so it now lives in quartz as jacksonTypeRefOf().

quartz exported the module as an `api` dependency, so test sources three modules
away were quietly using it. Those move over too, same two swaps: `ObjectMapper()`
where the mapper only walks maps or readTree (the pretty-printer test, the BIP-39
vectors, the event-command benchmark, quic's qlog writer and its test), and
`jacksonTypeRefOf<ArrayList<Event>>()` where the reified `readValue<T>` extension
was doing the typeref's job (LargeDBSignatureCheck, the thread-order test, the
two cache benchmarks). Event's own deserializer is registered on the mapper, so
databind resolves it either way.

`cli` keeps the module. It genuinely binds its config files reflectively
(Config, OperatorKeys, Aliases, StatusReport), it declares the dependency itself,
and it is never minified, so none of this applies there. desktopApp, geode,
relayBench and quic-interop likewise declare it directly and are unaffected.

Verified: `./gradlew test` green, the instrumented sources compile
(:quartz:compileAndroidDeviceTestSources, :amethyst and :benchmark androidTest),
R8 release build green, 19/19 contract checks, and both package prefixes
confirmed at zero classes in the shipped DEX.

Note for whoever hits it next: a full recompile of :amethyst needs more than a
2 GB Kotlin daemon. Mine died with "Couldn't transform method node" pointing at a
Compose lambda in NoteCompose.kt, which is an OutOfMemoryError in the ASM frame
analyzer wearing a disguise, not a code fault.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DEoxktEZyTrAS33vVZBiwm
This commit is contained in:
Claude
2026-09-19 14:01:07 +00:00
parent 2d1e398910
commit e7d3bcdc01
20 changed files with 104 additions and 55 deletions
@@ -21,7 +21,6 @@
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.amethyst.commons.relayClient.assemblers.CashuMintDirectoryFilterAssembler
import com.vitorpamplona.amethyst.commons.relayClient.nip47WalletConnect.NWCPaymentFilterAssembler
import com.vitorpamplona.amethyst.commons.viewmodels.thread.ThreadFeedFilter
@@ -34,6 +33,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.verify
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.jackson.jacksonTypeRefOf
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
@@ -163,7 +163,7 @@ class ThreadDualAxisChartAssemblerTest {
fun threadOrderTest() =
runBlocking {
val eventArray =
JacksonMapper.mapper.readValue<List<Event>>(db) + Event.fromJson(header)
JacksonMapper.mapper.readValue(db, jacksonTypeRefOf<List<Event>>()) + Event.fromJson(header)
var counter = 0
eventArray.forEach {
@@ -21,7 +21,7 @@
package com.vitorpamplona.amethyst.service.lnurl
import android.content.Context
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.HttpStatusMessages
import com.vitorpamplona.amethyst.ui.stringRes
@@ -167,7 +167,7 @@ class LightningAddressResolver {
val errorMessage =
runCatching {
jacksonObjectMapper().readTree(body)
ObjectMapper().readTree(body)
}.getOrNull()?.let { tree ->
val errorNode = tree.get("error")
val messageNode = tree.get("message")
@@ -218,7 +218,7 @@ class LightningAddressResolver {
context: Context,
onZapRequestSent: (LnZapRequestEvent?) -> Unit = {},
): String {
val mapper = jacksonObjectMapper()
val mapper = ObjectMapper()
val lnurlpUrl = assembleUrl(lnAddress)
@@ -26,7 +26,7 @@ import android.net.Uri
import android.provider.OpenableColumns
import android.webkit.MimeTypeMap
import androidx.core.net.toFile
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.HttpStatusMessages
import com.vitorpamplona.amethyst.service.checkNotInMainThread
@@ -209,7 +209,7 @@ class Nip96Uploader {
val errorMessage =
try {
val tree = jacksonObjectMapper().readTree(msg)
val tree = ObjectMapper().readTree(msg)
val status = tree.get("status")?.asText()
val message = tree.get("message")?.asText()
if (status == "error" && message != null) {
@@ -21,7 +21,7 @@
package com.vitorpamplona.amethyst.ui.tor
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
/**
* Pure, file- and JNI-free parsers over Arti's persisted guard sample
@@ -42,7 +42,7 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
* circuit — i.e. a real bootstrap reached the guard-confirmation stage.
*/
object ArtiGuardState {
private val mapper = jacksonObjectMapper()
private val mapper = ObjectMapper()
/** Convenience for tests/callers holding the raw file text. */
fun parse(json: String): JsonNode = mapper.readTree(json)
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.tor
import android.content.Context
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -206,7 +206,7 @@ class TorService(
val file = guardsFile()
if (!file.exists()) return null
return try {
jacksonObjectMapper().readTree(file)
ObjectMapper().readTree(file)
} catch (e: Exception) {
Log.w("TorService") { "Could not inspect guards.json: ${e.message}" }
null
@@ -20,10 +20,10 @@
*/
package com.vitorpamplona.quartz.benchmark
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.jackson.jacksonTypeRefOf
import com.vitorpamplona.quartz.utils.cache.LargeCache
import org.junit.Assert.assertTrue
import java.util.function.Consumer
@@ -35,8 +35,9 @@ open class BaseLargeCacheBenchmark {
// This file includes duplicates
val fullDBInputStream = javaClass.classLoader!!.getResourceAsStream("nostr_vitor_startup_data.json.gz")
return JacksonMapper.mapper.readValue<ArrayList<Event>>(
return JacksonMapper.mapper.readValue(
GZIPInputStream(fullDBInputStream),
jacksonTypeRefOf<ArrayList<Event>>(),
)
}
}
@@ -23,10 +23,10 @@ package com.vitorpamplona.quartz.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.jackson.jacksonTypeRefOf
import com.vitorpamplona.quartz.utils.cache.LargeCache
import org.junit.Assert.assertTrue
import org.junit.Rule
@@ -43,8 +43,9 @@ open class BaseCacheBenchmark {
// This file includes duplicates
val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json.gz")
return JacksonMapper.mapper.readValue<ArrayList<Event>>(
return JacksonMapper.mapper.readValue(
GZIPInputStream(fullDBInputStream),
jacksonTypeRefOf<ArrayList<Event>>(),
)
}
@@ -23,7 +23,7 @@ package com.vitorpamplona.quartz.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.utils.sha256.Sha256Hasher
import org.junit.Rule
@@ -54,7 +54,7 @@ class EventCmdSerializerBenchmark {
@Test
fun jsonStringEncoderJackson() {
val jsonMapper = jacksonObjectMapper()
val jsonMapper = ObjectMapper()
benchmarkRule.measureRepeated {
jsonMapper.writeValueAsString(specialEncoders)
}
@@ -62,7 +62,7 @@ class EventCmdSerializerBenchmark {
@Test
fun jsonStringEncoderSha256Jackson() {
val jsonMapper = jacksonObjectMapper()
val jsonMapper = ObjectMapper()
benchmarkRule.measureRepeated {
val digest = Sha256Hasher()
digest.hash(jsonMapper.writeValueAsString(specialEncoders).toByteArray())
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.commons.service.lnurl
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
import com.vitorpamplona.quartz.lightning.Lud06
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
@@ -48,7 +48,7 @@ import kotlin.coroutines.cancellation.CancellationException
class LightningAddressResolver(
private val httpClient: OkHttpClient,
) {
private val mapper = jacksonObjectMapper()
private val mapper = ObjectMapper()
/**
* Result of resolving a lightning address to a BOLT11 invoice.
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.amethyst.commons.service.lnurl
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.quartz.nip57Zaps.validate.LnurlEndpointCache
import com.vitorpamplona.quartz.nip57Zaps.validate.LnurlEndpointInfo
import com.vitorpamplona.quartz.nip57Zaps.validate.LnurlEndpointResolver
@@ -47,7 +47,7 @@ import kotlin.coroutines.cancellation.CancellationException
class OkHttpLnurlEndpointResolver(
private val okHttpClient: (String) -> OkHttpClient,
) : LnurlEndpointResolver {
private val mapper = jacksonObjectMapper()
private val mapper = ObjectMapper()
override suspend fun resolve(lnurlpUrl: String): LnurlEndpointInfo? = LnurlEndpointCache.getOrFetch(lnurlpUrl, ::fetch)
+2
View File
@@ -38,6 +38,7 @@ firebaseBom = "34.19.0"
fragmentKtx = "1.9.0"
gms = "4.5.0"
healthConnect = "1.1.0"
jackson = "2.22.2"
jacksonModuleKotlin = "2.22.2"
javaKeyring = "1.0.4"
kmpTorRuntime = "2.6.0"
@@ -203,6 +204,7 @@ google-mlkit-genai-rewriting = { group = "com.google.mlkit", name = "genai-rewri
google-mlkit-genai-image-description = { group = "com.google.mlkit", name = "genai-image-description", version.ref = "genaiImageDescription" }
google-mlkit-language-id = { group = "com.google.mlkit", name = "language-id", version.ref = "languageId" }
google-mlkit-translate = { group = "com.google.mlkit", name = "translate", version.ref = "translate" }
jackson-databind = { group = "com.fasterxml.jackson.core", name = "jackson-databind", version.ref = "jackson" }
jackson-module-kotlin = { group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version.ref = "jacksonModuleKotlin" }
java-keyring = { group = "com.github.javakeyring", name = "java-keyring", version.ref = "javaKeyring" }
kmp-tor-runtime = { group = "io.matthewnelson.kmp-tor", name = "runtime", version.ref = "kmpTorRuntime" }
+10 -2
View File
@@ -177,8 +177,16 @@ kotlin {
dependsOn(commonMain.get())
dependencies {
// Performant Parser of JSONs into Events
api(libs.jackson.module.kotlin)
// Performant Parser of JSONs into Events.
//
// jackson-databind, NOT jackson-module-kotlin: the module exists to bind
// Kotlin classes reflectively off their constructor parameter names, and
// nothing in the app does that any more — every wire format goes through a
// hand-written serializer or kotlinx. Dropping it takes kotlin-reflect with
// it, which is ~1,000 classes of DEX the app never called. `cli` still
// declares the module itself: it genuinely binds its config files
// reflectively, and is never minified.
api(libs.jackson.databind)
// Websockets API
implementation(libs.okhttp)
@@ -21,10 +21,10 @@
package com.vitorpamplona.quartz
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.verify
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.jackson.jacksonTypeRefOf
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertTrue
@@ -41,8 +41,9 @@ class LargeDBSignatureCheck {
val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_short.json")
val eventArray =
JacksonMapper.mapper.readValue<ArrayList<Event>>(
JacksonMapper.mapper.readValue(
InputStreamReader(fullDBInputStream),
jacksonTypeRefOf<ArrayList<Event>>(),
) as List<Event>
var counter = 0
@@ -61,8 +62,9 @@ class LargeDBSignatureCheck {
val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json.gz")
val eventArray =
JacksonMapper.mapper.readValue<ArrayList<Event>>(
JacksonMapper.mapper.readValue(
GZIPInputStream(fullDBInputStream),
jacksonTypeRefOf<ArrayList<Event>>(),
) as List<Event>
var counter = 0
@@ -21,7 +21,7 @@
package com.vitorpamplona.quartz.nip06KeyDerivation
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.RandomInstance
@@ -34,7 +34,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class Bip39MnemonicsTest {
private val tests =
jacksonObjectMapper()
ObjectMapper()
.readTree(javaClass.classLoader?.getResourceAsStream("bip39.vectors.json"))
@Test
@@ -26,9 +26,8 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.jackson.InliningTagArrayPrettyPrinter
import com.vitorpamplona.quartz.nip01Core.jackson.jacksonTypeRefOf
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.results.IntentResult
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.results.IntentResultJsonDeserializer
import com.vitorpamplona.quartz.nip55AndroidSigner.api.foreground.intents.results.IntentResultJsonSerializer
@@ -39,7 +38,7 @@ import java.io.InputStream
object JsonMapperNip55 {
val defaultMapper: ObjectMapper =
jacksonObjectMapper()
ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
.setDefaultPrettyPrinter(InliningTagArrayPrettyPrinter())
@@ -51,9 +50,9 @@ object JsonMapperNip55 {
.addSerializer(Permission::class.java, PermissionSerializer()),
)
inline fun <reified T> fromJsonTo(json: String): T = defaultMapper.readValue<T>(json)
inline fun <reified T> fromJsonTo(json: String): T = defaultMapper.readValue(json, jacksonTypeRefOf<T>())
inline fun <reified T> fromJsonTo(json: InputStream): T = defaultMapper.readValue<T>(json)
inline fun <reified T> fromJsonTo(json: InputStream): T = defaultMapper.readValue(json, jacksonTypeRefOf<T>())
fun toJson(event: ArrayNode): String = defaultMapper.writeValueAsString(event)
@@ -23,12 +23,10 @@ package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JavaType
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonTypeRef
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
import com.vitorpamplona.quartz.nip01Core.core.RawJson
@@ -64,7 +62,7 @@ class JacksonMapper {
val defaultPrettyPrinter = InliningTagArrayPrettyPrinter()
val mapper =
jacksonObjectMapper()
ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_TRAILING_TOKENS, false)
.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, false)
@@ -104,13 +102,13 @@ class JacksonMapper {
/**
* Shortcuts
*/
val eventTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Event>())
val tagArrayTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<TagArray>())
val rumorTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Rumor>())
val eventTemplateTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<EventTemplate<Event>>())
val eventListTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<List<Event>>())
val messageTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Message>())
val commandTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Command>())
val eventTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<Event>())
val tagArrayTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<TagArray>())
val rumorTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<Rumor>())
val eventTemplateTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<EventTemplate<Event>>())
val eventListTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<List<Event>>())
val messageTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<Message>())
val commandTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRefOf<Command>())
fun fromJson(json: String): Event = mapper.readValue(json, eventTypeInstance)
@@ -169,12 +167,12 @@ class JacksonMapper {
inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T {
checkRegistered(T::class)
return mapper.readValue<T>(json)
return mapper.readValue(json, jacksonTypeRefOf<T>())
}
inline fun <reified T : OptimizedSerializable> fromJsonTo(json: InputStream): T {
checkRegistered(T::class)
return mapper.readValue<T>(json)
return mapper.readValue(json, jacksonTypeRefOf<T>())
}
fun toJson(event: Event): String = EventManualSerializer.toJson(event.id, event.pubKey, event.createdAt, event.kind, event.tags, event.content, event.sig)
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.core.type.TypeReference
/**
* A [TypeReference] carrying the full generic type, e.g. `List<Event>`.
*
* This is the one thing the app used jackson-module-kotlin for that plain
* jackson-databind has no equivalent of — and it is a one-liner, because `reified`
* substitutes the concrete type into the anonymous subclass before erasure can
* lose it. `T::class.java` cannot replace it: for `List<Event>` that erases to
* `List`, and every element comes back a LinkedHashMap.
*
* Copied here rather than kept as a dependency so the Kotlin module — and
* kotlin-reflect behind it, ~1,000 classes — can leave the app. The CLI still
* depends on the module directly, because it genuinely binds reflectively and is
* never minified.
*/
inline fun <reified T> jacksonTypeRefOf(): TypeReference<T> = object : TypeReference<T>() {}
@@ -20,13 +20,13 @@
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import kotlin.test.Test
import kotlin.test.assertEquals
class InliningTagArrayPrettyPrinterTest {
val mapper =
jacksonObjectMapper().apply {
ObjectMapper().apply {
setDefaultPrettyPrinter(InliningTagArrayPrettyPrinter())
}
@@ -21,7 +21,6 @@
package com.vitorpamplona.quic.interop
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.quic.connection.EncryptionLevel
import com.vitorpamplona.quic.observability.QlogObserver
import java.io.BufferedWriter
@@ -295,7 +294,7 @@ class QlogWriter(
}
companion object {
private val DEFAULT_MAPPER: ObjectMapper = jacksonObjectMapper()
private val DEFAULT_MAPPER: ObjectMapper = ObjectMapper()
private fun packetTypeFor(level: EncryptionLevel): String =
when (level) {
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.quic.interop
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.databind.ObjectMapper
import com.vitorpamplona.quic.connection.EncryptionLevel
import org.junit.Test
import java.io.File
@@ -73,7 +73,7 @@ class QlogWriterTest {
val lines = tmp.readLines().filter { it.isNotBlank() }
assertTrue(lines.size >= 12, "expected >= 12 lines (header + at least 11 events) but got ${lines.size}")
val mapper = jacksonObjectMapper()
val mapper = ObjectMapper()
// Line 1: qlog header.
val header = mapper.readTree(lines[0])
@@ -128,7 +128,7 @@ class QlogWriterTest {
w.onAlpnNegotiated("h3")
}
val lines = tmp.readLines().filter { it.isNotBlank() }
val mapper = jacksonObjectMapper()
val mapper = ObjectMapper()
val event = mapper.readTree(lines[1])
assertEquals(50L, event.get("time").asLong(), "time must be relative to constructor (1050 - 1000)")
}
@@ -152,7 +152,7 @@ class QlogWriterTest {
QlogWriter(tmp, odcidHex = "00").use { w ->
w.onPacketSent(EncryptionLevel.INITIAL, 0, 1200, emptyList())
}
val mapper = jacksonObjectMapper()
val mapper = ObjectMapper()
val lines = tmp.readLines().filter { it.isNotBlank() }
val frames = mapper.readTree(lines[1]).get("data").get("frames")
assertTrue(frames.isArray, "frames must be an array even when empty")