Skip to content

Commit

Permalink
Jackson Serialiser and Deserialisers (#194)
Browse files Browse the repository at this point in the history
* adds jackson Serilaiser and Deserialisers

* improve jackson Bytes SerDe
  • Loading branch information
jpnovais authored Oct 17, 2024
1 parent 4e1e2a5 commit 74529e6
Show file tree
Hide file tree
Showing 11 changed files with 478 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class ProofToFinalizeJsonResponseTest {
val jsonParser = jsonNode.traverse()
while (!jsonParser.isClosed) {
if (jsonParser.nextToken() == JsonToken.FIELD_NAME) {
keys.add(jsonParser.currentName)
keys.add(jsonParser.currentName())
}
}
return keys
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jreleaser = {id = "org.jreleaser", version = "1.13.1"}
besu = "22.4.2"
caffeine = "3.1.6"
hoplite = "2.7.5"
jackson = "2.14.2"
jackson = "2.18.0"
jna = "5.14.0"
junit = "5.10.1"
kotlinxDatetime = "0.4.0"
Expand All @@ -23,5 +23,6 @@ tuweni = "2.3.1"
vertx = "4.5.0"
web3j = "4.12.0"
wiremock = "3.0.1"
jsonUnit = "3.4.1"
blobCompressor = "0.0.4"
blobShnarfCalculator = "0.0.4"
13 changes: 13 additions & 0 deletions jvm-libs/generic/serialization/jackson/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
id 'net.consensys.zkevm.kotlin-library-conventions'
}

dependencies {
implementation(project(':jvm-libs:kotlin-extensions'))
api "com.fasterxml.jackson.core:jackson-annotations:${libs.versions.jackson.get()}"
api "com.fasterxml.jackson.core:jackson-databind:${libs.versions.jackson.get()}"
api "com.fasterxml.jackson.module:jackson-module-kotlin:${libs.versions.jackson.get()}"
api "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${libs.versions.jackson.get()}"

testImplementation "net.javacrumbs.json-unit:json-unit-assertj:${libs.versions.jsonUnit.get()}"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package build.linea.s11n.jackson

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import java.util.HexFormat

private val hexFormatter = HexFormat.of()

object ByteArrayToHexSerializer : JsonSerializer<ByteArray>() {
override fun serialize(value: ByteArray, gen: JsonGenerator, serializers: SerializerProvider?) {
gen.writeString("0x" + hexFormatter.formatHex(value))
}

override fun handledType(): Class<ByteArray> {
return ByteArray::class.java
}
}

object ByteToHexSerializer : JsonSerializer<Byte>() {
override fun serialize(value: Byte, gen: JsonGenerator, serializers: SerializerProvider?) {
gen.writeString("0x" + hexFormatter.toHexDigits(value))
}
}

object UByteToHexSerializer : JsonSerializer<UByte>() {
override fun serialize(value: UByte, gen: JsonGenerator, serializers: SerializerProvider?) {
gen.writeString("0x" + hexFormatter.toHexDigits(value.toByte()))
}
}

object ByteArrayToHexDeserializer : JsonDeserializer<ByteArray>() {
override fun deserialize(parser: JsonParser, contex: DeserializationContext): ByteArray {
return hexFormatter.parseHex(parser.text.removePrefix("0x"))
}
}

object ByteToHexDeserializer : JsonDeserializer<Byte>() {
override fun deserialize(parser: JsonParser, contex: DeserializationContext): Byte {
return hexFormatter.parseHex(parser.text.removePrefix("0x"))[0]
}
}

object UByteToHexDeserializer : JsonDeserializer<UByte>() {
override fun deserialize(parser: JsonParser, contex: DeserializationContext): UByte {
return hexFormatter.parseHex(parser.text.removePrefix("0x"))[0].toUByte()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package build.linea.s11n.jackson

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import kotlinx.datetime.Instant

object InstantISO8601Serializer : JsonSerializer<Instant>() {
override fun serialize(value: Instant, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toString())
}
}

// To uncomment and add the tests when necessary
object InstantISO8601Deserializer : JsonDeserializer<Instant>() {
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Instant {
return Instant.parse(p.text)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package build.linea.s11n.jackson

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import java.math.BigInteger

internal fun Number.toHex(): String = "0x" + BigInteger(toString()).toString(16)
internal fun ULong.toHex(): String = "0x" + BigInteger(toString()).toString(16)

object IntToHexSerializer : JsonSerializer<Int>() {
override fun serialize(value: Int, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toHex())
}
}

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
object JIntegerToHexSerializer : JsonSerializer<Integer>() {
override fun serialize(value: Integer, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toHex())
}
}

object LongToHexSerializer : JsonSerializer<Long>() {
override fun serialize(value: Long, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toHex())
}
}

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
object JLongToHexSerializer : JsonSerializer<java.lang.Long>() {
override fun serialize(value: java.lang.Long, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toHex())
}
}

object ULongToHexSerializer : JsonSerializer<ULong>() {
override fun serialize(value: ULong, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toHex())
}
}

object BigIntegerToHexSerializer : JsonSerializer<BigInteger>() {
override fun serialize(value: BigInteger, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeString(value.toHex())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package build.linea.s11n.jackson

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.datetime.Instant
import java.math.BigInteger

val ethNumberAsHexSerialisersModule = SimpleModule().apply {
this.addSerializer(Instant::class.java, InstantISO8601Serializer)
this.addDeserializer(Instant::class.java, InstantISO8601Deserializer)
this.addSerializer(Int::class.java, IntToHexSerializer)
this.addSerializer(Integer::class.java, JIntegerToHexSerializer)
this.addSerializer(Long::class.java, LongToHexSerializer)
this.addSerializer(java.lang.Long::class.java, JLongToHexSerializer)
this.addSerializer(ULong::class.java, ULongToHexSerializer)
this.addSerializer(BigInteger::class.java, BigIntegerToHexSerializer)
}

val ethByteAsHexSerialisersModule = SimpleModule().apply {
this.addSerializer(Byte::class.java, ByteToHexSerializer)
this.addSerializer(UByte::class.java, UByteToHexSerializer)
this.addSerializer(ByteArray::class.java, ByteArrayToHexSerializer)
}

val ethByteAsHexDeserialisersModule = SimpleModule().apply {
this.addDeserializer(Byte::class.java, ByteToHexDeserializer)
this.addDeserializer(UByte::class.java, UByteToHexDeserializer)
this.addDeserializer(ByteArray::class.java, ByteArrayToHexDeserializer)
}

val ethApiObjectMapper: ObjectMapper = jacksonObjectMapper()
.registerModules(
ethNumberAsHexSerialisersModule,
ethByteAsHexSerialisersModule
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package build.linea.s11n.jackson

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import net.consensys.decodeHex
import net.consensys.encodeHex
import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class BytesSerDeTest {
private lateinit var objectMapper: ObjectMapper

private val jsonObj = """
{
"nullBytes": null,
"emptyBytes": "0x",
"someBytes": "0x01aaff04",
"listOfByteArray": ["0x01aaff04", "0x01aaff05"],
"nullUByte": null,
"someUByte": "0xaf",
"minUByte": "0x00",
"maxUByte": "0xff",
"nullByte": null,
"someByte": "0xaf",
"minByte": "0x80",
"maxByte": "0x7f"
}
""".trimIndent()
private val objWithBytesFields = SomeObject(
// ByteArray
nullBytes = null,
emptyBytes = byteArrayOf(),
someBytes = "0x01aaff04".decodeHex(),
listOfByteArray = listOf("0x01aaff04", "0x01aaff05").map { it.decodeHex() },

// UByte
nullUByte = null,
someUByte = 0xaf.toUByte(),
minUByte = UByte.MIN_VALUE,
maxUByte = UByte.MAX_VALUE,

// Byte
nullByte = null,
someByte = 0xaf.toByte(),
minByte = Byte.MIN_VALUE,
maxByte = Byte.MAX_VALUE
)

@BeforeEach
fun setUp() {
objectMapper = jacksonObjectMapper()
.registerModules(ethByteAsHexSerialisersModule)
.registerModules(ethByteAsHexDeserialisersModule)
}

@Test
fun bytesSerDe() {
assertThatJson(objectMapper.writeValueAsString(objWithBytesFields)).isEqualTo(jsonObj)
assertThat(objectMapper.readValue<SomeObject>(jsonObj)).isEqualTo(objWithBytesFields)
}

@Test
fun testBytes() {
val list1 = listOf("0x01aaff04", "0x01aaff05").map { it.decodeHex() }
val list2 = listOf("0x01aaff04", "0x01aaff05", "0x01aaff06").map { it.decodeHex() }
list1.zip(list2).also { println(it) }
println(list1.zip(list2).all { (arr1, arr2) -> arr1.contentEquals(arr2) })

println(list1 == list2)
println(list1 != list2)
}

private data class SomeObject(
// ByteArray
val nullBytes: ByteArray?,
val emptyBytes: ByteArray,
val someBytes: ByteArray,
val listOfByteArray: List<ByteArray>,

// UByte
val nullUByte: UByte?,
val someUByte: UByte,
val minUByte: UByte,
val maxUByte: UByte,
// Byte
val nullByte: Byte?,
val someByte: Byte,
val minByte: Byte,
val maxByte: Byte
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as SomeObject

if (nullBytes != null) {
if (other.nullBytes == null) return false
if (!nullBytes.contentEquals(other.nullBytes)) return false
} else if (other.nullBytes != null) return false
if (!emptyBytes.contentEquals(other.emptyBytes)) return false
if (!someBytes.contentEquals(other.someBytes)) return false
if (!contentEquals(listOfByteArray, other.listOfByteArray)) return false
if (nullUByte != other.nullUByte) return false
if (someUByte != other.someUByte) return false
if (minUByte != other.minUByte) return false
if (maxUByte != other.maxUByte) return false
if (nullByte != other.nullByte) return false
if (someByte != other.someByte) return false
if (minByte != other.minByte) return false
if (maxByte != other.maxByte) return false

return true
}

override fun hashCode(): Int {
var result = nullBytes?.contentHashCode() ?: 0
result = 31 * result + emptyBytes.contentHashCode()
result = 31 * result + someBytes.contentHashCode()
result = 31 * result + listOfByteArray.hashCode()
result = 31 * result + (nullUByte?.hashCode() ?: 0)
result = 31 * result + someUByte.hashCode()
result = 31 * result + minUByte.hashCode()
result = 31 * result + maxUByte.hashCode()
result = 31 * result + (nullByte ?: 0)
result = 31 * result + someByte
result = 31 * result + minByte
result = 31 * result + maxByte
return result
}

override fun toString(): String {
return "SomeObject(" +
"nullBytes=${nullBytes?.contentToString()}, " +
"emptyBytes=${emptyBytes.contentToString()}, " +
"someByte=${someBytes.contentToString()}, " +
"listOfByteArray=${listOfByteArray.joinToString(",", "[", "]") { it.encodeHex() }}, " +
"nullUByte=$nullUByte, " +
"someUByte=$someUByte, " +
"minUByte=$minUByte, " +
"maxUByte=$maxUByte, " +
"nullByte=$nullByte, " +
"someByte=$someByte, " +
"minByte=$minByte, " +
"maxByte=$maxByte" +
")"
}
}

companion object {
fun contentEquals(list1: List<ByteArray>, list2: List<ByteArray>): Boolean {
if (list1.size != list2.size) return false

return list1.zip(list2).all { (arr1, arr2) -> arr1.contentEquals(arr2) }
}
}
}
Loading

0 comments on commit 74529e6

Please sign in to comment.