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: 
chunkSizecorrespond to the numbers of fields before the structure is serialize with infinite Mark. - If applied on a Map or a List: 
chunkSizecorrespond to the number of elements before the list is serialize with infinite Mark. - If applied on a String or a ByteArray with 
@CborRawBytes:chunksizecorrespond 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.
 @Serializerfor 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