Skip to content

Commit 259966f

Browse files
committed
docs + apidump
1 parent f9dc353 commit 259966f

File tree

14 files changed

+188
-42
lines changed

14 files changed

+188
-42
lines changed

docs/formats.md

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ stable, these are currently experimental features of Kotlin Serialization.
1717
* [Tags and Labels](#tags-and-labels)
1818
* [Arrays](#arrays)
1919
* [Custom CBOR-specific Serializers](#custom-cbor-specific-serializers)
20+
* [CBOR Elements](#cbor-elements)
21+
* [Encoding from/to `CborElement`](#encoding-fromto-cborelement)
22+
* [Tagging `CborElement`s](#tagging-cborelements)
23+
* [Caution](#caution)
24+
* [Types of CBOR Elements](#types-of-cbor-elements)
2025
* [ProtoBuf (experimental)](#protobuf-experimental)
2126
* [Field numbers](#field-numbers)
2227
* [Integer types](#integer-types)
@@ -308,13 +313,125 @@ When annotated with `@CborArray`, serialization of the same object will produce
308313
```
309314
This may be used to encode COSE structures, see [RFC 9052 2. Basic COSE Structure](https://www.rfc-editor.org/rfc/rfc9052#section-2).
310315

311-
312316
### Custom CBOR-specific Serializers
313317
Cbor encoders and decoders implement the interfaces [CborEncoder](CborEncoder.kt) and [CborDecoder](CborDecoder.kt), respectively.
314318
These interfaces contain a single property, `cbor`, exposing the current CBOR serialization configuration.
315319
This enables custom cbor-specific serializers to reuse the current `Cbor` instance to produce embedded byte arrays or
316320
react to configuration settings such as `preferCborLabelsOverNames` or `useDefiniteLengthEncoding`, for example.
317321

322+
323+
### CBOR Elements
324+
325+
Aside from direct conversions between bytearray and CBOR objects, Kotlin serialization offers APIs that allow
326+
other ways of working with CBOR in the code. For example, you might need to tweak the data before it can parse
327+
or otherwise work with such unstructured data that it does not readily fit into the typesafe world of Kotlin
328+
serialization.
329+
330+
The main concept in this part of the library is [CborElement]. Read on to learn what you can do with it.
331+
332+
#### Encoding from/to `CborElement`
333+
334+
Bytes can be decoded into an instance of `CborElement` with the [Cbor.decodeFromByteArray] function by either manually
335+
specifying [CborElement.serializer()] or specifying [CborElement] as generic type parameter.
336+
It is also possible to encode arbitrary serializable structures to a `CborElement` through [Cbor.encodeToCborElement].
337+
338+
Since these operations use the same code paths as regular serialization (but with specialized serializers), the config flags
339+
behave as expected:
340+
341+
```kotlin
342+
fun main() {
343+
val element: CborElement = Cbor.decodeFromHexString("a165627974657343666f6f")
344+
println(element)
345+
}
346+
```
347+
348+
The above snippet will print the following diagnostic notation
349+
350+
```text
351+
CborMap(tags=[], content={CborString(tags=[], value=bytes)=CborByteString(tags=[], value=h'666f6f)})
352+
```
353+
354+
#### Tagging `CborElement`s
355+
356+
Every CborElement—whether it is used as a property, a value inside a collection, or even a complex key inside a map
357+
(which is perfectly legal in CBOR)—supports tags. Tags can be specified by passing them s varargs parameters upon
358+
CborElement creation.
359+
For example, take following structure (represented in diagnostic notation):
360+
361+
<!--- TEST -->
362+
363+
```hexdump
364+
bf # map(*)
365+
61 # text(1)
366+
61 # "a"
367+
cc # tag(12)
368+
1a 0fffffff # unsigned(268,435,455)
369+
d8 22 # base64 encoded text, tag(34)
370+
61 # text(1)
371+
62 # "b"
372+
# invalid length at 0 for base64
373+
20 # negative(-1)
374+
d8 38 # tag(56)
375+
61 # text(1)
376+
63 # "c"
377+
d8 4e # typed array of i32, little endian, twos-complement, tag(78)
378+
42 # bytes(2)
379+
cafe # "\xca\xfe"
380+
# invalid data length for typed array
381+
61 # text(1)
382+
64 # "d"
383+
d8 5a # tag(90)
384+
cc # tag(12)
385+
6b # text(11)
386+
48656c6c6f20576f726c64 # "Hello World"
387+
ff # break
388+
```
389+
390+
Decoding it results in the following CborElement (shown in manually formatted diagnostic notation):
391+
392+
```
393+
CborMap(tags=[], content={
394+
CborString(tags=[], value=a) = CborPositiveInt( tags=[12], value=268435455),
395+
CborString(tags=[34], value=b) = CborNegativeInt( tags=[], value=-1),
396+
CborString(tags=[56], value=c) = CborByteString( tags=[78], value=h'cafe),
397+
CborString(tags=[], value=d) = CborString( tags=[90, 12], value=Hello World)
398+
})
399+
```
400+
401+
##### Caution
402+
403+
Tags are properties of `CborElements`, and it is possible to mixing arbitrary serializable values with `CborElement`s that
404+
contain tags inside a serializable structure. It is also possible to annotate any [CborElement] property
405+
of a generic serializable class with `@ValueTags`.
406+
**This can lead to asymmetric behavior when serializing and deserializing such structures!**
407+
408+
#### Types of CBOR Elements
409+
410+
A [CborElement] class has three direct subtypes, closely following CBOR grammar:
411+
412+
* [CborPrimitive] represents primitive CBOR elements, such as string, integer, float boolean, and null.
413+
CBOR byte strings are also treated as primitives
414+
Each primitive has a [value][CborPrimitive.value]. Depending on the concrete type of the primitive, it maps
415+
to corresponding Kotlin Types such as `String`, `Int`, `Double`, etc.
416+
Note that Cbor discriminates between positive ("unsigned") and negative ("signed") integers!
417+
`CborPrimitive` is itself an umbrella type (a sealed class) for the following concrete primitives:
418+
* [CborNull] mapping to a Kotlin `null`
419+
* [CborBoolean] mapping to a Kotlin `Boolean`
420+
* [CborInt] which is an umbrella type (a sealed class) itself for the following concrete types
421+
(it is still possible to instantiate it as the `invoke` operator on its companion is overridden accordingly):
422+
* [CborPositiveInt] represents all `Long` numbers `≥0`
423+
* [CborNegativeInt] represents all `Long` numbers `<0`
424+
* [CborString] maps to a Kotlin `String`
425+
* [CborFloat] maps to Kotlin `Double`
426+
* [CborByteString] maps to a Kotlin `ByteArray` and is used to encode them as CBOR byte string (in contrast to a list
427+
of individual bytes)
428+
429+
* [CborList] represents a CBOR array. It is a Kotlin [List] of `CborElement` items.
430+
431+
* [CborMap] represents a CBOR map/object. It is a Kotlin [Map] from `CborElement` keys to `CborElement` values.
432+
This is typically the result of serializing an arbitrary
433+
434+
318435
## ProtoBuf (experimental)
319436

320437
[Protocol Buffers](https://developers.google.com/protocol-buffers) is a language-neutral binary format that normally
@@ -1673,5 +1790,19 @@ This chapter concludes [Kotlin Serialization Guide](serialization-guide.md).
16731790
[Cbor.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/decode-from-byte-array.html
16741791
[CborBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html
16751792
[ByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/index.html
1793+
[CborElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-element/index.html
1794+
[Cbor.encodeToCborElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/encode-to-cbor-element.html
1795+
[CborPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-primitive/index.html
1796+
[CborPrimitive.value]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-primitive/value.html
1797+
[CborNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-null/index.html
1798+
[CborBoolean]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-boolean/index.html
1799+
[CborInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-int/index.html
1800+
[CborPositiveInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-positive-int/index.html
1801+
[CborNegativeInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-negative-int/index.html
1802+
[CborString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-string/index.html
1803+
[CborFloat]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-float/index.html
1804+
[CborByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-byte-string/index.html
1805+
[CborList]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-list/index.html
1806+
[CborMap]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-map/index.html
16761807

16771808
<!--- END -->

docs/serialization-guide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ Once the project is set up, we can start serializing some classes.
154154
* <a name='tags-and-labels'></a>[Tags and Labels](formats.md#tags-and-labels)
155155
* <a name='arrays'></a>[Arrays](formats.md#arrays)
156156
* <a name='custom-cbor-specific-serializers'></a>[Custom CBOR-specific Serializers](formats.md#custom-cbor-specific-serializers)
157+
* <a name='cbor-elements'></a>[CBOR Elements](formats.md#cbor-elements)
158+
* <a name='encoding-fromto-cborelement'></a>[Encoding from/to `CborElement`](formats.md#encoding-fromto-cborelement)
159+
* <a name='tagging-cborelements'></a>[Tagging `CborElement`s](formats.md#tagging-cborelements)
160+
* <a name='caution'></a>[Caution](formats.md#caution)
161+
* <a name='types-of-cbor-elements'></a>[Types of CBOR Elements](formats.md#types-of-cbor-elements)
157162
* <a name='protobuf-experimental'></a>[ProtoBuf (experimental)](formats.md#protobuf-experimental)
158163
* <a name='field-numbers'></a>[Field numbers](formats.md#field-numbers)
159164
* <a name='integer-types'></a>[Integer types](formats.md#integer-types)

formats/cbor/api/kotlinx-serialization-cbor.api

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,6 @@ public final class kotlinx/serialization/cbor/CborDecoder$DefaultImpls {
100100
public static fun decodeSerializableValue (Lkotlinx/serialization/cbor/CborDecoder;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
101101
}
102102

103-
public final class kotlinx/serialization/cbor/CborDouble : kotlinx/serialization/cbor/CborPrimitive {
104-
public static final field Companion Lkotlinx/serialization/cbor/CborDouble$Companion;
105-
public synthetic fun <init> (D[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
106-
}
107-
108-
public final class kotlinx/serialization/cbor/CborDouble$Companion {
109-
public final fun serializer ()Lkotlinx/serialization/KSerializer;
110-
}
111-
112103
public abstract class kotlinx/serialization/cbor/CborElement {
113104
public static final field Companion Lkotlinx/serialization/cbor/CborElement$Companion;
114105
public synthetic fun <init> ([JILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -133,6 +124,15 @@ public final class kotlinx/serialization/cbor/CborEncoder$DefaultImpls {
133124
public static fun encodeSerializableValue (Lkotlinx/serialization/cbor/CborEncoder;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
134125
}
135126

127+
public final class kotlinx/serialization/cbor/CborFloat : kotlinx/serialization/cbor/CborPrimitive {
128+
public static final field Companion Lkotlinx/serialization/cbor/CborFloat$Companion;
129+
public synthetic fun <init> (D[JLkotlin/jvm/internal/DefaultConstructorMarker;)V
130+
}
131+
132+
public final class kotlinx/serialization/cbor/CborFloat$Companion {
133+
public final fun serializer ()Lkotlinx/serialization/KSerializer;
134+
}
135+
136136
public abstract class kotlinx/serialization/cbor/CborInt : kotlinx/serialization/cbor/CborPrimitive {
137137
public static final field Companion Lkotlinx/serialization/cbor/CborInt$Companion;
138138
public synthetic fun <init> ([JLjava/lang/Object;ILkotlin/jvm/internal/DefaultConstructorMarker;)V

formats/cbor/api/kotlinx-serialization-cbor.klib.api

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ final class kotlinx.serialization.cbor/CborConfiguration { // kotlinx.serializat
140140
final fun toString(): kotlin/String // kotlinx.serialization.cbor/CborConfiguration.toString|toString(){}[0]
141141
}
142142

143-
final class kotlinx.serialization.cbor/CborDouble : kotlinx.serialization.cbor/CborPrimitive<kotlin/Double> { // kotlinx.serialization.cbor/CborDouble|null[0]
144-
constructor <init>(kotlin/Double, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborDouble.<init>|<init>(kotlin.Double;kotlin.ULongArray...){}[0]
143+
final class kotlinx.serialization.cbor/CborFloat : kotlinx.serialization.cbor/CborPrimitive<kotlin/Double> { // kotlinx.serialization.cbor/CborFloat|null[0]
144+
constructor <init>(kotlin/Double, kotlin/ULongArray...) // kotlinx.serialization.cbor/CborFloat.<init>|<init>(kotlin.Double;kotlin.ULongArray...){}[0]
145145

146-
final object Companion { // kotlinx.serialization.cbor/CborDouble.Companion|null[0]
147-
final fun serializer(): kotlinx.serialization/KSerializer<kotlinx.serialization.cbor/CborDouble> // kotlinx.serialization.cbor/CborDouble.Companion.serializer|serializer(){}[0]
146+
final object Companion { // kotlinx.serialization.cbor/CborFloat.Companion|null[0]
147+
final fun serializer(): kotlinx.serialization/KSerializer<kotlinx.serialization.cbor/CborFloat> // kotlinx.serialization.cbor/CborFloat.Companion.serializer|serializer(){}[0]
148148
}
149149
}
150150

formats/cbor/commonMain/src/kotlinx/serialization/cbor/CborElement.kt

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,8 @@ public sealed class CborPrimitive<T : Any>(
8181
}
8282

8383
override fun toString(): String {
84-
return "CborPrimitive(" +
85-
"kind=${value::class.simpleName}, " +
86-
"tags=${tags.joinToString()}, " +
84+
return "${this::class.simpleName}(" +
85+
"tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
8786
"value=$value" +
8887
")"
8988
}
@@ -150,8 +149,8 @@ public class CborPositiveInt(
150149
/**
151150
* Class representing CBOR floating point value (major type 7).
152151
*/
153-
@Serializable(with = CborDoubleSerializer::class)
154-
public class CborDouble(
152+
@Serializable(with = CborFloatSerializer::class)
153+
public class CborFloat(
155154
value: Double,
156155
vararg tags: ULong
157156
) : CborPrimitive<Double>(value, tags)
@@ -196,9 +195,8 @@ public class CborByteString(
196195
}
197196

198197
override fun toString(): String {
199-
return "CborPrimitive(" +
200-
"kind=${value::class.simpleName}, " +
201-
"tags=${tags.joinToString()}, " +
198+
return "CborByteString(" +
199+
"tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
202200
"value=h'${value.toHexString()}" +
203201
")"
204202
}
@@ -229,7 +227,7 @@ public class CborMap(
229227

230228
override fun toString(): String {
231229
return "CborMap(" +
232-
"tags=${tags.joinToString()}, " +
230+
"tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
233231
"content=$content" +
234232
")"
235233
}
@@ -255,7 +253,7 @@ public class CborList(
255253

256254
override fun toString(): String {
257255
return "CborList(" +
258-
"tags=${tags.joinToString()}, " +
256+
"tags=${tags.joinToString(prefix = "[", postfix = "]")}, " +
259257
"content=$content" +
260258
")"
261259
}

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborElementSerializers.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ internal object CborElementSerializer : KSerializer<CborElement>, CborSerializer
2828
element("CborByteString", defer { CborByteStringSerializer.descriptor })
2929
element("CborMap", defer { CborMapSerializer.descriptor })
3030
element("CborList", defer { CborListSerializer.descriptor })
31-
element("CborDouble", defer { CborDoubleSerializer.descriptor })
31+
element("CborDouble", defer { CborFloatSerializer.descriptor })
3232
element("CborInt", defer { CborNegativeIntSerializer.descriptor })
3333
element("CborUInt", defer { CborPositiveIntSerializer.descriptor })
3434
}
@@ -64,7 +64,7 @@ internal object CborPrimitiveSerializer : KSerializer<CborPrimitive<*>>, CborSer
6464
is CborString -> encoder.encodeSerializableValue(CborStringSerializer, value)
6565
is CborBoolean -> encoder.encodeSerializableValue(CborBooleanSerializer, value)
6666
is CborByteString -> encoder.encodeSerializableValue(CborByteStringSerializer, value)
67-
is CborDouble -> encoder.encodeSerializableValue(CborDoubleSerializer, value)
67+
is CborFloat -> encoder.encodeSerializableValue(CborFloatSerializer, value)
6868
is CborNegativeInt -> encoder.encodeSerializableValue(CborNegativeIntSerializer, value)
6969
is CborPositiveInt -> encoder.encodeSerializableValue(CborPositiveIntSerializer, value)
7070
}
@@ -154,19 +154,19 @@ internal object CborPositiveIntSerializer : KSerializer<CborPositiveInt>, CborSe
154154
}
155155
}
156156

157-
internal object CborDoubleSerializer : KSerializer<CborDouble>, CborSerializer {
157+
internal object CborFloatSerializer : KSerializer<CborFloat>, CborSerializer {
158158
override val descriptor: SerialDescriptor =
159159
PrimitiveSerialDescriptor("kotlinx.serialization.cbor.CborDouble", PrimitiveKind.DOUBLE)
160160

161-
override fun serialize(encoder: Encoder, value: CborDouble) {
161+
override fun serialize(encoder: Encoder, value: CborFloat) {
162162
val cborEncoder = encoder.asCborEncoder()
163163
cborEncoder.encodeTags(value)
164164
encoder.encodeDouble(value.value)
165165
}
166166

167-
override fun deserialize(decoder: Decoder): CborDouble {
167+
override fun deserialize(decoder: Decoder): CborFloat {
168168
decoder.asCborDecoder()
169-
return CborDouble(decoder.decodeDouble())
169+
return CborFloat(decoder.decodeDouble())
170170
}
171171
}
172172

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/CborTreeReader.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ internal class CborTreeReader(
6969
CborNull(tags = tags)
7070
}
7171
// Half/Float32/Float64
72-
NEXT_HALF, NEXT_FLOAT, NEXT_DOUBLE -> CborDouble(parser.nextDouble(), tags = tags)
72+
NEXT_HALF, NEXT_FLOAT, NEXT_DOUBLE -> CborFloat(parser.nextDouble(), tags = tags)
7373
else -> throw CborDecodingException(
7474
"Invalid simple value or float type: ${parser.curByte.toString(16).uppercase()}"
7575
)

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Decoder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ internal class StructuredCborParser(internal val element: CborElement, private v
693693
override fun nextDouble(tags: ULongArray?): Double {
694694
processTags(tags)
695695
return when (layer.current) {
696-
is CborDouble -> (layer.current as CborDouble).value
696+
is CborFloat -> (layer.current as CborFloat).value
697697
else -> throw CborDecodingException("Expected double, got ${layer.current::class.simpleName}")
698698
}
699699
}

formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,11 @@ internal class StructuredCborWriter(cbor: Cbor) : CborWriter(cbor) {
331331
}
332332

333333
override fun encodeDouble(value: Double) {
334-
currentElement += CborDouble(value, tags = nextValueTags)
334+
currentElement += CborFloat(value, tags = nextValueTags)
335335
}
336336

337337
override fun encodeFloat(value: Float) {
338-
currentElement += CborDouble(value.toDouble(), tags = nextValueTags)
338+
currentElement += CborFloat(value.toDouble(), tags = nextValueTags)
339339
}
340340

341341
override fun encodeInt(value: Int) {

0 commit comments

Comments
 (0)