summaryrefslogtreecommitdiff
path: root/docs/runtime_usage.md
blob: 8928e0fa85438e342b858646523f59e52f088a27 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# Runtime library contents and usage

## Obtaining serializers

Serializers are represented at runtime as `KSerializer<T>`, which in turn, implements interfaces `KSerialSaver<T>` and `KSerialLoader<T>`, where `T` is class you serialize.
You don't need to call them by yourself; you just have to pass them properly to serialization format. You can write them on your own (see [custom serializers](custom_serializers.md)) or let the compiler plugin do the dirty work by marking class `@Serializable`.
To retrieve the generated serializer, plugin emits special function on companion object called `.serializer()`. (This synthetic function is relatively new and stable release of compiler may not able to see it in some cases; if you have some issues, try eap version of `1.2.20`).
If your class has generic type arguments, this function will have arguments for specifying serializers on type parameters, because it's impossible to serialize generic class statically in general case:

```kotlin
@Serializable
data class Data(val a: Int)

@Serializable
data class Box<T>(val boxed: T)

val dataSerial     : KSerializer<Data>      = Data.serializer()
val boxedDataSerial: KSerializer<Box<Data>> = Box.serializer(dataSerial)
```

Built-in types, like Int, and standard collections doesn't have that method. You can use corresponding serializers from `kotlinx.serialization.internal` package:

```kotlin
val i : KSerializer<Int>       = IntSerializer // object
val li: KSerializer<List<Int>> = ArrayListSerializer(IntSerializer) // generic, requires instantiation
```

For convenience, serializers have extension properties:

```kotlin
val li: KSerializer<List<Data>>       = Data.serializer().list
val mp: KSerializer<Map<String, Int>> = (StringSerializer to IntSerializer).map // extension on Pair of serializers
```

In following special case:
* Class explicitly marked `@Serializable`
* Class doesn't have generic type arguments

You can obtain serializer from KClass instance: `val d: KSerializer<MyData> = MyData::class.serializer()`. This approach is discouraged in general because of its implicitness, but maybe useful shorthand in some cases.

All external serializers (defined by user) are instantiated in a user-specific way. To learn how to write them, see [docs](custom_serializers.md).

## Serialization formats

Runtime library provides three ready-to use formats: JSON, CBOR and ProtoBuf.

### JSON

JSON format represented by `JSON` class from `kotlinx.serialization.json` package. It following parameters:

* nonstrict - allow JSON parser skip fields which are not present in class. By default is false.
* unquoted - means that all field names and other objects (where it's possible) would not be wrapped in quotes. Useful for debugging.
* indented - classic pretty-printed multiline JSON.
* indent - size of indent, applicable if parameter above is true.

You can also use one of predefined instances, like `JSON.plain`, `JSON.indented`, `JSON.nonstrict` or `JSON.unquoted`. API is duplicated in companion object, so `JSON.parse(...)` equals to `JSON.plain.parse(...)`

You can also specify desired behaviour for duplicating keys. By default it is `UpdateMode.OVERWRITE`. You can use `UpdateMode.UPDATE`, and by doing that you'll be able to merge two lists or maps with same key into one; but be aware that serializers for non-collection types are throwing `UpdateNotSupportedException` by default. To prohibit duplicated keys, you can use `UpdateMode.BANNED`.

JSON API:

```kotlin
fun <T> stringify(saver: KSerialSaver<T>, obj: T): String
inline fun <reified T : Any> stringify(obj: T): String = stringify(T::class.serializer(), obj)

fun <T> parse(loader: KSerialLoader<T>, str: String): T
inline fun <reified T : Any> parse(str: String): T = parse(T::class.serializer(), str)
```

`stringify` transforms object to string, `parse` parses. No surprises.

**Note**: because JSON doesn't support maps with keys other than
strings (and primitives), Kotlin maps with non-trivial key types are serialized as JSON lists.

**Caveat**: `T::class.serializer()` assumes that you use it on class defined as `@Serializable`,
so it wouldn't work with root-level collections or external serializers out of the box. It's always better to specify serializer [explicitly](#obtaining-serializers).

### CBOR

`CBOR` class provides following functions:

```kotlin
fun <T : Any> dump(saver: KSerialSaver<T>, obj: T): ByteArray // saves object to bytes
inline fun <reified T : Any> dump(obj: T): ByteArray // same as above, resolves serializer by itself
inline fun <reified T : Any> dumps(obj: T): String // dump object and then pretty-print bytes to string

fun <T : Any> load(loader: KSerialLoader<T>, raw: ByteArray): T // load object from bytes
inline fun <reified T : Any> load(raw: ByteArray): T // save as above
inline fun <reified T : Any> loads(hex: String): T // inverse operation for dumps
```

It has `UpdateMode.BANNED` by default.

**Note**: CBOR, unlike JSON, supports maps with non-trivial keys,
and Kotlin maps are serialized as CBOR maps, but some parsers (like `jackson-dataformat-cbor`) don't support this.

### Protobuf

Because protobuf relies on serial ids of fields, called 'tags', you have to provide this information, 
using serial annotation `@SerialId`:

```kotlin
@Serializable
data class KTestInt32(@SerialId(1) val a: Int)
```

This class is equivalent to the following proto definition:

```proto
message Int32 {
    required int32 a = 1;
}
```

Note that we are using proto2 semantics, where all fields are explicitly required or optional.

Number format is set via `@ProtoType` annotation. `ProtoNumberType.DEFAULT` is default varint encoding (`intXX`), `SIGNED`
is signed ZigZag representation (`sintXX`), and `FIXED` is `fixedXX` type. `uintXX` and `sfixedXX` are not supported yet.

Repeated fields represented as lists. Because format spec says that if the list is empty, there will be no elements in the stream with such tag,
you must explicitly mark any field of list type with `@Optional` annotation with default ` = emptyList()`. Same for maps. Update mode for Protobuf is set to `UPDATE` and can't be changed, thus allowing merging several scattered lists into one.

Other known issues and limitations:

* Packed repeated fields are not supported

More examples of mappings from proto definitions to Koltin classes can be found in test data:
[here](../runtime/jvm/src/test/proto/test_data.proto) and [here](../runtime/jvm/src/test/kotlin/kotlinx/serialization/formats/RandomTests.kt#L47)

## Useful classes

### Mapper

`Mapper` allows you to serialize/deserialize object to/from map:

```kotlin
@Serializable
data class Data(val first: Int, val second: String)

val map: Map<String, Any> = Mapper.map(Data(42, "foo")) // mapOf("first" to 42, "second" to "foo")
```

To get your object back, use `unmap` function. To support objects with nullable values, use `mapNullable` and `unmapNullable`

### Dynamic object parser (JS only)

Allows you to convert JS objects of `dynamic` types into fair correct Kotlin objects.

```kotlin
@Serializable
data class Data(val a: Int)

@Serializable
data class DataWrapper(val s: String, val d: Data?)

val dyn = js("""{s:"foo", d:{a:42}}""")
val parsed = DynamicObjectParser().parse<DataWrapper>(dyn)
parsed == DataWrapper("foo", Data(42)) // true
```

> This feature is still in development and may have some bugs. Parser does not support kotlin maps with keys other than `String`.