From 79d6745ee660d7029f3e4215fe83fdc6f79d24f3 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Mon, 22 Dec 2025 14:52:02 -0500 Subject: [PATCH] Adds faster parsers for Messages and Commands Unwraps inline runCatching because it seems faster for some reason. --- .../quartz/benchmark/EventBenchmark.kt | 21 ++++---- .../quartz/nip01Core/EventSigCheck.kt | 3 +- .../nip01Core/core/OptimizedJsonMapper.kt | 6 +++ .../client/single/basic/BasicRelayClient.kt | 3 +- .../quartz/nip59Giftwrap/rumors/Rumor.kt | 2 +- .../nip01Core/core/OptimizedJsonMapper.ios.kt | 6 +++ .../core/OptimizedJsonMapper.jvmAndroid.kt | 51 +++++++++++++++---- .../nip01Core/jackson/EventDeserializer.kt | 1 - .../quartz/nip01Core/jackson/JacksonMapper.kt | 6 +++ 9 files changed, 73 insertions(+), 26 deletions(-) diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt index 745176f98..12a88f4dc 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventBenchmark.kt @@ -29,7 +29,6 @@ import com.vitorpamplona.quartz.nip01Core.crypto.verifyId import com.vitorpamplona.quartz.nip01Core.crypto.verifySignature import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage -import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.utils.EventFactory import com.vitorpamplona.quartz.utils.TimeUtils @@ -49,36 +48,36 @@ class EventBenchmark { @get:Rule val benchmarkRule = BenchmarkRule() @Test - fun parseComplete() { + fun parseVerify() { benchmarkRule.measureRepeated { - val event = (OptimizedJsonMapper.fromJsonTo(reqResponseEvent) as EventMessage).event + val event = (OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) as EventMessage).event assertTrue(event.verify()) } } @Test - fun parseREQString() { + fun parseREQTree() { benchmarkRule.measureRepeated { JacksonMapper.mapper.readTree(reqResponseEvent) } } @Test - fun parseREQString2() { + fun parseREQTree2() { benchmarkRule.measureRepeated { JacksonMapper.mapper.readTree(reqResponseEvent2) } } @Test - fun parseREQStringComplete() { - benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonTo(reqResponseEvent) } + fun parseREQString() { + benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) } } @Test - fun parseREQStringComplete2() { - benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonTo(reqResponseEvent2) } + fun parseREQString2() { + benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent2) } } @Test fun checkId() { - val event = (OptimizedJsonMapper.fromJsonTo(reqResponseEvent) as EventMessage).event + val event = (OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) as EventMessage).event benchmarkRule.measureRepeated { // Should pass assertTrue(event.verifyId()) @@ -87,7 +86,7 @@ class EventBenchmark { @Test fun checkSignature() { - val event = (OptimizedJsonMapper.fromJsonTo(reqResponseEvent) as EventMessage).event + val event = (OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) as EventMessage).event benchmarkRule.measureRepeated { // Should pass assertTrue(event.verifySignature()) diff --git a/quartz/src/androidInstrumentedTest/kotlin/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt b/quartz/src/androidInstrumentedTest/kotlin/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt index 7b7cd67ee..6f442361c 100644 --- a/quartz/src/androidInstrumentedTest/kotlin/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt +++ b/quartz/src/androidInstrumentedTest/kotlin/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt @@ -27,7 +27,6 @@ import com.vitorpamplona.quartz.nip01Core.crypto.checkSignature import com.vitorpamplona.quartz.nip01Core.crypto.verifyId import com.vitorpamplona.quartz.nip01Core.crypto.verifySignature import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage -import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent @@ -64,7 +63,7 @@ class EventSigCheck { @Test fun testUnicode2028and2029ShouldNotBeEscaped() { - val msg = OptimizedJsonMapper.fromJsonTo(payload1) as EventMessage + val msg = OptimizedJsonMapper.fromJsonToMessage(payload1) as EventMessage // Should pass msg.event.checkSignature() diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.kt index 7276cce4e..9e9b6de35 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.quartz.nip01Core.core +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message +import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor import kotlinx.serialization.json.Json @@ -31,6 +33,10 @@ expect object OptimizedJsonMapper { fun toJson(event: Event): String + fun fromJsonToMessage(json: String): Message + + fun fromJsonToCommand(json: String): Command + fun fromJsonToTagArray(json: String): Array> fun fromJsonToEventTemplate(json: String): EventTemplate diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt index 4dfb17704..0d765370c 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt @@ -24,7 +24,6 @@ import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient.Companion.DELAY_TO_RECONNECT_IN_SECS -import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocket @@ -124,7 +123,7 @@ open class BasicRelayClient( override fun onMessage(text: String) { try { - val msg = OptimizedJsonMapper.fromJsonTo(text) + val msg = OptimizedJsonMapper.fromJsonToMessage(text) listener.onIncomingMessage(this@BasicRelayClient, text, msg) } catch (e: Throwable) { if (e is CancellationException) throw e diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt index 632e9f1b3..c393bc486 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt @@ -49,7 +49,7 @@ class Rumor( } companion object { - fun fromJson(json: String): Rumor = OptimizedJsonMapper.fromJsonTo(json) + fun fromJson(json: String): Rumor = OptimizedJsonMapper.fromJsonToRumor(json) fun toJson(event: Rumor): String = OptimizedJsonMapper.toJson(event) diff --git a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.ios.kt b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.ios.kt index ae709b411..22d6024d2 100644 --- a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.ios.kt +++ b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.ios.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.quartz.nip01Core.core +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message +import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor @@ -28,6 +30,10 @@ actual object OptimizedJsonMapper { actual fun toJson(event: Event): String = TODO("Not yet implemented") + actual fun fromJsonToMessage(json: String): Message = TODO("Not yet implemented") + + actual fun fromJsonToCommand(json: String): Command = TODO("Not yet implemented") + actual fun fromJsonToTagArray(json: String): Array> = TODO("Not yet implemented") actual fun fromJsonToEventTemplate(json: String): EventTemplate = TODO("Not yet implemented") diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.jvmAndroid.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.jvmAndroid.kt index 7ee005d79..cf0338c50 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.jvmAndroid.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/core/OptimizedJsonMapper.jvmAndroid.kt @@ -21,31 +21,64 @@ package com.vitorpamplona.quartz.nip01Core.core import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper +import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message +import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor actual object OptimizedJsonMapper { - inline fun runCatching(parsingAction: () -> T): T = + actual fun fromJson(json: String): Event = try { - parsingAction() - // wraps jackson errors + JacksonMapper.fromJson(json) } catch (e: com.fasterxml.jackson.core.JsonParseException) { throw IllegalArgumentException(e.message, e) } - actual fun fromJson(json: String): Event = runCatching { JacksonMapper.fromJson(json) } - actual fun toJson(event: Event) = JacksonMapper.toJson(event) - actual fun fromJsonToTagArray(json: String): Array> = runCatching { JacksonMapper.fromJsonToTagArray(json) } + actual fun fromJsonToMessage(json: String): Message = + try { + JacksonMapper.fromJsonToMessage(json) + } catch (e: com.fasterxml.jackson.core.JsonParseException) { + throw IllegalArgumentException(e.message, e) + } - actual fun fromJsonToRumor(json: String): Rumor = runCatching { JacksonMapper.fromJsonToRumor(json) } + actual fun fromJsonToCommand(json: String): Command = + try { + JacksonMapper.fromJsonToCommand(json) + } catch (e: com.fasterxml.jackson.core.JsonParseException) { + throw IllegalArgumentException(e.message, e) + } - actual fun fromJsonToEventTemplate(json: String): EventTemplate = runCatching { JacksonMapper.fromJsonToEventTemplate(json) } + actual fun fromJsonToTagArray(json: String): Array> = + try { + JacksonMapper.fromJsonToTagArray(json) + } catch (e: com.fasterxml.jackson.core.JsonParseException) { + throw IllegalArgumentException(e.message, e) + } + + actual fun fromJsonToRumor(json: String): Rumor = + try { + JacksonMapper.fromJsonToRumor(json) + } catch (e: com.fasterxml.jackson.core.JsonParseException) { + throw IllegalArgumentException(e.message, e) + } + + actual fun fromJsonToEventTemplate(json: String): EventTemplate = + try { + JacksonMapper.fromJsonToEventTemplate(json) + } catch (e: com.fasterxml.jackson.core.JsonParseException) { + throw IllegalArgumentException(e.message, e) + } actual fun toJson(tags: Array>): String = JacksonMapper.toJson(tags) - actual inline fun fromJsonTo(json: String): T = runCatching { JacksonMapper.fromJsonTo(json) } + actual inline fun fromJsonTo(json: String): T = + try { + JacksonMapper.fromJsonTo(json) + } catch (e: com.fasterxml.jackson.core.JsonParseException) { + throw IllegalArgumentException(e.message, e) + } actual fun toJson(value: OptimizedSerializable): String = JacksonMapper.toJson(value) } diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt index e989ffc5a..2dd3c09b1 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt @@ -63,7 +63,6 @@ class EventDeserializer : StdDeserializer(Event::class.java) { } } - // NPE on purpose. If the object isn't fully filled, it should throw. return EventFactory.create(id, pubKey, createdAt, kind, tags, content, sig) } } diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapper.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapper.kt index 66f8c9b61..a6ca2615b 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapper.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonMapper.kt @@ -109,9 +109,15 @@ class JacksonMapper { val tagArrayTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef()) val rumorTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef()) val eventTemplateTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef>()) + val messageTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef()) + val commandTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef()) fun fromJson(json: String): Event = mapper.readValue(json, eventTypeInstance) + fun fromJsonToMessage(json: String): Message = mapper.readValue(json, messageTypeInstance) + + fun fromJsonToCommand(json: String): Command = mapper.readValue(json, commandTypeInstance) + fun fromJsonToTagArray(json: String): TagArray = mapper.readValue(json, tagArrayTypeInstance) fun fromJsonToRumor(json: String): Rumor = mapper.readValue(json, rumorTypeInstance)