OBOR
A JVM CBOR serializer/deserializer implementation with kotlinx-serialization. (I believe it can be ported to JavaScript or Kotlin Native with little efforts since the majority of the code don't use JVM SDK)
Usage
From maven central :
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
implementation "net.orandja.obor:obor:0.2.0"
}
It uses conventionnal encode/decode kotlinx-serialization functions like JSON. You can take a look on how it works here.
Example :
@Serializable
data class Example(val foo: String, val bar: Int)
fun main() {
val example = Example("Hello World !", 42)
// Encode example into ByteArray
val encode = Cbor.encodeToByteArray(Example.serializer(), example)
println(encode.joinToString("", "0x") { "%02X".format(it) })
// Decode example from ByteArray
val decode = Cbor.decodeFromByteArray(Example.serializer(), encode)
println(decode == example)
}
This prints :
0xA263666F6F6D48656C6C6F20576F726C64202163626172182A
true
The bytes translate to :
A2 # map(2)
63 # text(3)
666F6F # "foo"
6D # text(13)
48656C6C6F20576F726C642021 # "Hello World !"
63 # text(3)
626172 # "bar"
18 2A # unsigned(42)
Annotations
CborRawBytes
Annotate a ByteArray
, Array<Byte>
or List<Byte>
to encode it as major type 2 byte string. It is not necessary to annotate a field with @CborRawBytes
to decode Major 2.
@Serializable
class Data(
@CborRawBytes
val array: ByteArray
)
CborInfinite(chunkSize: Int)
chunkSize
define the number of element before the structure become infinite. chunkSize
of 0
means its always infinite. A negative value is the same as the default implementation: always try to write the size.
- If applied to a class or an object:
chunkSize
correspond to the numbers of fields before the structure is serialize with infinite Mark. - If applied on a Map or a List:
chunkSize
correspond to the number of elements before the list is serialize with infinite Mark. - If applied on a String or a ByteArray with
@CborRawBytes
:chunksize
correspond to the length in which the element is chunked.
@Serializable
@CborInfinite(0) // Data will be encoded with [Major 5 (MAP) infinite]
// in contrast @CborInfinite(2) will encode [Major 5 (MAP) size] since there are only 2 elements
class Data(
@CborInfinite(10) // if list.size > 10 then [Major 4 (LIST) infinite] else [Major 4 (LIST) size]
val array: List<String>,
@CborInfinite(10) // if string.length > 10 then [Major 3 (TEXT) infinite] else [Major 3 (TEXT) size]
val string: String
)
CborTag(tagNumber: Long, require: Boolean)
A class with this annotation will be serialized with the corresponding CBOR Tag. It can also be applied to field. If require == true then the tag is require during deserialization.
Skipped fields
If a field key isn't present in the class or object declaration but is inside a CBOR message it is ommited by the decoder.
@Serializable
data class Data1(val s: String, val i: Int)
@Serializable
data class Data2(val i: Int)
val data1 = Data1("", 42)
val twoFields = Cbor.encodeToByteArray(Data1.serializer(), data1)
val data2 = Cbor.decodeFromByteArray(Data2.serializer(), twoFields)
assert(data2.i == data1.i)
Iterable serializer
You can encode any Iterable<T>
as an infinite Cbor Array (Major 5) with the IterableSerializer
@Serializable
class Data(
@Serializable(IterableSerializer::class)
val iterable : Iterable<String>
)
fun main() {
val encoded = Cbor.encodeToByteArray(Data.serializer(), Data(listOf("Hello", "World")))
println(encoded.joinToString("") { "%02X".format(it) })
}
Output :
A1 # map(1)
68 # text(8)
6974657261626C65 # "iterable"
9F # array(*)
65 # text(5)
48656C6C6F # "Hello"
65 # text(5)
576F726C64 # "World"
FF # primitive(*)
Streams
Alongside encodeToByteArray
and decodeFromByteArray
you can use decodeFromInputStream
andencodeToOutputStream
the same way.
@Serializable
data class Example(val foo: String, val bar: Int)
val cbor = Cbor()
fun main() {
val example = Example("Hello World !", 42)
val output = ByteArrayOutputStream()
// Encode example into OutputStream
val encode = cbor.encodeToOutputStream(Example.serializer(), example, output)
println(output.toByteArray().joinToString("", "0x") { "%02X".format(it) })
val input = ByteArrayInputStream(output.toByteArray())
// Decode example from InputStream
val decode = cbor.decodeFromInputStream(Example.serializer(), input)
println(decode == example)
}
Todos
Misc
- Better Kotlin documentation.
- Deserialization exceptions.
@Serializer
for Unsigned types.- Documentation on multiple md files.
Annotations
Float16
Annotate a Float to transform it into a IEEE 754 Half-Precision Float.
CborRequire
An unskippable field.
CborSkip
Do not write this field.
Tests
- Major Byte (3)
- Major Text (4)
- Major Array (5)
- Major Map (6)
- Major Tag (7)
- Primitives
- Skip values
- Annotations