Skip to content

Commit

Permalink
feat(api alignment): deserialization refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
DariusIMP committed Jul 29, 2024
1 parent d319a24 commit 9b17210
Show file tree
Hide file tree
Showing 3 changed files with 542 additions and 38 deletions.
108 changes: 96 additions & 12 deletions examples/src/main/kotlin/io.zenoh/ZBytes.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package io.zenoh

import io.zenoh.protocol.*
import java.nio.ByteBuffer
import java.nio.ByteOrder
import kotlin.reflect.typeOf

fun main() {

/***********************************************
* Standard serialization and deserialization. *
***********************************************/

/** Numeric: byte, short, int, float, double */
val intInput = 1234
var payload = ZBytes.from(intInput)
Expand Down Expand Up @@ -45,10 +52,7 @@ fun main() {

/** List serialization and deserialization.
*
* Supported types:
* - `List<ZBytes>`
* - `List<ByteArray>`
* - `List<String>`
* Supported types: String, ByteArray, ZBytes, Byte, Short, Int, Long, Float and Double.
*/
val inputList = listOf("sample1", "sample2", "sample3")
payload = ZBytes.serialize(inputList).getOrThrow()
Expand All @@ -65,20 +69,33 @@ fun main() {
val outputListByteArray = payload.deserialize<List<ByteArray>>().getOrThrow()
check(compareByteArrayLists(inputListByteArray, outputListByteArray))

/** Map serialization and deserialization.
/**
* Map serialization and deserialization.
*
* Supported types:
* - `Map<ZBytes, ZBytes>`
* - `Map<ByteArray, ByteArray>`
* - `Map<String, String>`
* Maps with the following Type combinations are supported: String, ByteArray, ZBytes, Byte, Short, Int, Long, Float and Double.
*/
val inputMap = mapOf("key1" to "value1", "key2" to "value2", "key3" to "value3")
payload = ZBytes.serialize(inputMap).getOrThrow()
val outputMap = payload.deserialize<Map<String, String>>().getOrThrow()
check(inputMap == outputMap)

val combinedInputMap = mapOf("key1" to ZBytes.from("zbytes1"), "key2" to ZBytes.from("zbytes2"))
payload = ZBytes.serialize(combinedInputMap).getOrThrow()
val combinedOutputMap = payload.deserialize<Map<String, ZBytes>>().getOrThrow()
check(combinedInputMap == combinedOutputMap)

/*********************************************
* Custom serialization and deserialization. *
*********************************************/

/**
* Custom serialization and deserialization.
* The examples below use [MyZBytes], an example class consisting that implements the [Serializable] interface.
*
* In order for the serialization and deserialization to be successful on a custom class,
* the class itself must override the `into(): ZBytes` function, but also the companion
* object must implement the [Serializable.From] interface.
*
* @see MyZBytes
*/
val inputMyZBytes = MyZBytes("example")
payload = ZBytes.serialize(inputMyZBytes).getOrThrow()
Expand All @@ -87,9 +104,7 @@ fun main() {

/** List of MyZBytes. */
val inputListMyZBytes = inputList.map { value -> MyZBytes(value) }
// Note that in order to perform the following serialization, MyZBytes must implement the interface IntoZBytes.
payload = ZBytes.serialize<List<MyZBytes>>(inputListMyZBytes).getOrThrow()
// Note that in order to perform the following deserialization, MyZBytes must implement the interface FromZBytes.Self (see below).
val outputListMyZBytes = payload.deserialize<List<MyZBytes>>().getOrThrow()
check(inputListMyZBytes == outputListMyZBytes)

Expand All @@ -99,6 +114,21 @@ fun main() {
val outputMapMyZBytes = payload.deserialize<Map<MyZBytes, MyZBytes>>().getOrThrow()
check(inputMapMyZBytes == outputMapMyZBytes)

/**
* Providing a map of deserializers.
*
* Alternatively, [ZBytes.deserialize] also accepts a deserializers parameter of type
* `Map<KType, KFunction1<ByteArray, Any>>`. That is, a map of types that is associated
* to a function receiving a ByteArray, that returns Any. This way, you can provide a series
* of deserializer functions that extend the deserialization mechanisms we provide by default.
*
* For example, let's say we have a custom map serializer, with its own deserializer:
*/
val fooMap = mapOf(Foo("foo1") to Foo("bar1"), Foo("foo2") to Foo("bar2"))
val fooMapSerialized = ZBytes.from(serializeFooMap(fooMap))
val deserializersMap = mapOf(typeOf<Map<Foo, Foo>>() to ::deserializeFooMap)
val deserializedFooMap = fooMapSerialized.deserialize<Map<Foo, Foo>>(deserializersMap).getOrThrow()
check(fooMap == deserializedFooMap)
}

class MyZBytes(val content: String) : Serializable {
Expand All @@ -125,6 +155,60 @@ class MyZBytes(val content: String) : Serializable {
}
}

/** Example class for the deserialization map examples. */
class Foo(val content: String) {

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Foo

return content == other.content
}

override fun hashCode(): Int {
return content.hashCode()
}
}

/** Example serializer and deserializer. */
private fun serializeFooMap(testMap: Map<Foo, Foo>): ByteArray {
return testMap.map {
val key = it.key.content.toByteArray()
val keyLength = ByteBuffer.allocate(Int.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(key.size).array()
val value = it.value.content.toByteArray()
val valueLength =
ByteBuffer.allocate(Int.SIZE_BYTES).order(ByteOrder.LITTLE_ENDIAN).putInt(value.size).array()
keyLength + key + valueLength + value
}.reduce { acc, bytes -> acc + bytes }
}

private fun deserializeFooMap(serializedMap: ByteArray): Map<Foo, Foo> {
var idx = 0
var sliceSize: Int
val decodedMap = mutableMapOf<Foo, Foo>()
while (idx < serializedMap.size) {
sliceSize = ByteBuffer.wrap(serializedMap.sliceArray(IntRange(idx, idx + Int.SIZE_BYTES - 1)))
.order(ByteOrder.LITTLE_ENDIAN).int
idx += Int.SIZE_BYTES

val key = serializedMap.sliceArray(IntRange(idx, idx + sliceSize - 1))
idx += sliceSize

sliceSize = ByteBuffer.wrap(serializedMap.sliceArray(IntRange(idx, idx + Int.SIZE_BYTES - 1))).order(
ByteOrder.LITTLE_ENDIAN
).int
idx += Int.SIZE_BYTES

val value = serializedMap.sliceArray(IntRange(idx, idx + sliceSize - 1))
idx += sliceSize

decodedMap[Foo(key.decodeToString())] = Foo(value.decodeToString())
}
return decodedMap
}

/** Utils for this example. */

private fun compareByteArrayLists(list1: List<ByteArray>, list2: List<ByteArray>): Boolean {
Expand Down
27 changes: 27 additions & 0 deletions zenoh-kotlin/src/commonMain/kotlin/io/zenoh/protocol/IntoZBytes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

package io.zenoh.protocol

import java.nio.ByteBuffer
import java.nio.ByteOrder

interface IntoZBytes {
fun into(): ZBytes
}
Expand All @@ -38,6 +41,30 @@ fun ByteArray.into(): ZBytes {
return ZBytes(this)
}

fun ByteArray.toByte(): Byte {
return ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN).get()
}

fun ByteArray.toShort(): Short {
return ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN).short
}

fun ByteArray.toInt(): Int {
return ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN).int
}

fun ByteArray.toLong(): Long {
return ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN).long
}

fun ByteArray.toFloat(): Float {
return ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN).float
}

fun ByteArray.toDouble(): Double {
return ByteBuffer.wrap(this).order(ByteOrder.LITTLE_ENDIAN).double
}

@Throws
fun Any?.into(): ZBytes {
return when (this) {
Expand Down
Loading

0 comments on commit 9b17210

Please sign in to comment.