Deprecate @Optional, introduce @Required
diff --git a/docs/examples.md b/docs/examples.md
index 3dc38e1..7ab7e91 100644
--- a/docs/examples.md
+++ b/docs/examples.md
@@ -10,11 +10,11 @@
@Serializable
data class Data(val a: Int, val b: Int)
val data = Data(1, 2)
-
+
// Serialize with internal serializer for Data class
assertEquals("{a:1,b:2}", Json.unquoted.stringify(data))
assertEquals(data, Json.parse<Data>("{a:1,b:2}"))
-
+
// Serialize with external serializer for Data class
@Serializer(forClass=Data::class)
object ExtDataSerializer
@@ -22,58 +22,98 @@
assertEquals(data, Json.parse(ExtDataSerializer, "{a:1,b:2}"))
```
- * In case of usage of **internal** serialization (`@Serializable` annotation on class), both body `val`s and `var`s are supported with any visibility levels.
-
+ * In case of usage of **internal** serialization
+ (`@Serializable` annotation on class), both body `val`s and `var`s are supported with any visibility levels.
+
```kotlin
@Serializable
class Data(val a: Int) {
private val b: String = "42"
-
+
override fun equals(other: Any?) = /*...*/
}
-
+
assertEquals("{a:1, b:42}", Json.unquoted.stringify(Data(1)))
assertEquals(Data(1), Json.unquoted.parse<Data>("{a:1, b:42}"))
```
-* Important note: In this case, body properties initializers and setters are not called. So, following approach would not work:
+ * Property will be considered _optional_ if it has default value.
+
+ ```kotlin
+ @Serializable
+ data class Data(val a: Int, val b: Int = 42)
+
+ // Serialization and deserialization with internal serializer
+ assertEquals("{a:0,b:42}",Json.unquoted.stringify(Data(0)))
+ assertEquals(Json.unquoted.parse<Data>("{a:0,b:43}"),Data(b = 43))
+ assertEquals(Json.unquoted.parse<Data>("{a:0,b:42}"),Data(0))
+ assertEquals(Json.unquoted.parse<Data>("{a:0}"),Data(0))
+
+ // This will throw SerializationException, because 'a' is missing.
+ Json.unquoted.parse<Data>("{b:0}")
+ ```
+
+ > Tip: you can omit default values during serialization with
+ `Json(encodeDefaults = false)` (see [here](runtime_usage#json)).
+
+
+ * By default, only properties which have
+ [backing fields](https://kotlinlang.org/docs/reference/properties.html#backing-fields)
+ will be serialized and restored back.
+
+ ```kotlin
+ @Serializable
+ data class Data(val a: Int) {
+ private val b: String
+ get() = "42"
+ }
+
+ // b is not in serialized form!
+ assertEquals("{a:1}", Json.unquoted.stringify(Data(1)))
+ ```
+
+ You should be careful with this, especially when you have hierarchy of serializable classes with several overrides.
+
+ * Moreover, if you have several properties with the same name and different backing fields
+ (e.g. `open/override` pair), a compiler exception will be thrown. To resolve such conflicts, use `@SerialName` (see [below](#Annotations)).
+
+ * Important note: In this case, body properties initializers and setters are not called. So, following approach would not work:
```kotlin
@Serializable
class Data(val a: String = "42") {
val b: String = computeWithSideEffects()
-
+
private fun computeWithSideEffects(): String {
println("I'm a side effect")
- return "b"
+ return "b"
}
}
-
+
// prints nothing.
val data = Json.unquoted.parse<Data>("{a: 100500, b: 10}")
```
-* Initializers are called iff (if and only if) property is `@Transient` or `@Optional` and was not read (see below).
-
+* Initializers are called iff (if and only if) property is `@Transient` or optional and was not read (see below).
+
```kotlin
@Serializable
class Data(val a: String = "42") {
- @Optional
val b: String = computeWithSideEffects()
-
+
private fun computeWithSideEffects(): String {
println("I'm a side effect")
- return "b"
+ return "b"
}
}
-
+
// prints "I'm a side effect" once.
val data = Json.unquoted.parse<Data>("{a: 100500, b: 10}")
val data = Json.unquoted.parse<Data>("{a: 100500}")
```
* *Common pattern*: Validation.
-
+
Such classes are not serializable, because they have constructor parameters which are not properties:
```kotlin
@@ -95,7 +135,7 @@
* **External** deserialization (annotation `@Serializer(forClass=...)`) has more limitations: it supports only primary constructor's vals/vars and class body `var` properties with visibility higher than protected. Body `val` properties and all private properties are unseen for external serializer/deserializer.
It also invokes all setters on body `var`s and all initialization expressions with init blocks.
-
+
It isn't supported yet in JavaScript.
```kotlin
@@ -105,23 +145,23 @@
val unseen = 42
override fun equals(other: Any?) = /*..*/
}
-
+
val data = Data().apply {
a = 1
b = 2
}
-
+
// Serialize with external serializer for Data class
@Serializer(forClass=Data::class)
object ExtDataSerializer
-
+
assertEquals("{a:1,b:2}", Json.unquoted.stringify(ExtDataSerializer, data))
assertEquals(data, Json.parse(ExtDataSerializer, "{a:1,b:2}"))
```
* Having both` @Serialiable class A` and `@Serializer(forClass=A::class)` is possible. In this case, object marked as serializer will try to deserialize class A internally, and some *strange effects* may happen. But it's not exactly.
-## Annotations
+## Annotations
* `@SerialName` annotation for overriding property name with custom name in formats with name support, like JSON.
@@ -136,20 +176,20 @@
assertEquals("{value1: a, value2: 42}", Json.unquoted.stringify(Names("a", 42)))
```
-
+
> Starting from 0.6, `@SerialName` can be used on classes, too.
-* `@Optional` annotation for supported properties. Note: `@Optional` constructor parameters require default values, but properties with default values without annotation are treated as required.
-
+* `@Required` annotation for supported properties. It makes property with default value
+still be mandatory and always present in serialized form.
+
```kotlin
@Serializable
- class Data(val a: Int = 0, @Optional val b: Int = 42) {
- @Optional
+ class Data(@Required val a: Int = 0, val b: Int = 42) {
var c = "Hello"
-
+
override fun equals(other: Any?) = /*...*/
}
-
+
// Serialization and deserialization with internal serializer
// External serializer also supported
assertEquals("{a:0,b:42,c:Hello}",Json.unquoted.stringify(Data()))
@@ -157,71 +197,69 @@
assertEquals(Json.unquoted.parse<Data>("{a:0,b:42,c:Hello}"),Data())
assertEquals(Json.unquoted.parse<Data>("{a:0,c:Hello}"),Data())
assertEquals(Json.unquoted.parse<Data>("{a:0}"),Data())
-
+
// This will throw SerializationException, because 'a' is missing.
Json.unquoted.parse<Data>("{b:0}")
```
* `@Transient` annotation for supported properties. This annotation excludes marked properties from process of serialization or deserialization. Requires default value. *Don't confuse with `kotlin.jvm.Transient`!*
-
+
```kotlin
@Serializable
class Data(val a: Int = 0, @Transient val b: Int = 42) {
- @Optional
var c = "Hello"
-
+
@Transient
var d = "World"
-
+
override fun equals(other: Any?) = /*...*/
}
-
+
// Serialization and deserialization with internal serializer
// External serializer also supported
assertEquals("{a:0,c:Hello}",Json.unquoted.stringify(Data()))
assertEquals(Json.unquoted.parse<Data>("{a:0,c:Hello}"),Data())
assertEquals(Json.unquoted.parse<Data>("{a:0}"),Data())
-
-
- // This will throw SerializationException, because
+
+
+ // This will throw SerializationException, because
// property 'b' is unknown to deserializer.
Json.unquoted.parse<Data>("{a:0,b:100500,c:Hello}")
```
-* Initializing `@Transient` or `@Optional` fields in init blocks is not supported.
-
+* Initializing `@Transient` or optional fields in init blocks is not supported.
+
```kotlin
// This class is not serializable.
class Data(val a: String = "42") {
- @Optional
- val b: String
-
+ val b: String
+
init {
b = "b"
}
}
```
-* Delegates are not supported. But you can mark them as `@Transient` and they would be instantiated as usual. So this code works fine:
-
+* Delegates are not supported and they're by default `@Transient` (since they do not have backing field), so this example works fine:
+
```kotlin
@Serializable
data class WithDelegates(val myMap: Map<String, String>) {
-
- @Transient
+
+ // implicit @Transient
val prop by myMap
}
-
+
assertEquals("value", Json.unquoted.parse<WithDelegates>("{myMap:{prop:value}}").prop)
```
-
+
## Nesting
* Nested values are recursively serialized, enums, primitive types, arrays, lists and maps are supported, plus other serializable classes.
-
+
```kotlin
enum class TintEnum { LIGHT, DARK }
-
+
@Serializable
data class Data(
val a: String,
@@ -229,11 +267,11 @@
val c: Map<String, TintEnum>
)
val data = Data("Str", listOf(1, 2), mapOf("lt" to TintEnum.LIGHT, "dk" to TintEnum.DARK))
-
+
// Serialize with internal serializer for Data class
assertEquals("{a:Str,b:[1,2],c:{lt:LIGHT,dk:DARK}}", Json.unquoted.stringify(data))
assertEquals(data, Json.parse<Data>("{a:Str,b:[1,2],c:{lt:LIGHT,dk:DARK}}"))
-
+
// Serialize with external serializer for Data class
@Serializer(forClass=Data::class)
object ExtDataSerializer
diff --git a/docs/runtime_usage.md b/docs/runtime_usage.md
index d8d838b..6dfa5c2 100644
--- a/docs/runtime_usage.md
+++ b/docs/runtime_usage.md
@@ -52,7 +52,7 @@
* 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.
-* encodeDefaults - set this to false to omit writing @Optional properties if they are equal to theirs default values.
+* encodeDefaults - set this to false to omit writing optional properties if they are equal to theirs default values.
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(...)`
@@ -121,7 +121,7 @@
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.
+you must explicitly mark any field of list type 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:
diff --git a/runtime/common/src/main/kotlin/kotlinx/serialization/Annotations.kt b/runtime/common/src/main/kotlin/kotlinx/serialization/Annotations.kt
index d72bfdb..6bbd0b1 100644
--- a/runtime/common/src/main/kotlin/kotlinx/serialization/Annotations.kt
+++ b/runtime/common/src/main/kotlin/kotlinx/serialization/Annotations.kt
@@ -46,8 +46,12 @@
* Optional properties must have default values.
*/
@Target(AnnotationTarget.PROPERTY)
+@Deprecated("All properties with default values are considered optional now")
annotation class Optional
+@Target(AnnotationTarget.PROPERTY)
+annotation class Required
+
/**
* Marks this property invisible for whole serialization framework.
* Transient properties must have default values.
diff --git a/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonOptionalTests.kt b/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonOptionalTests.kt
index f18ff4a..9806fe9 100644
--- a/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonOptionalTests.kt
+++ b/runtime/common/src/test/kotlin/kotlinx/serialization/json/JsonOptionalTests.kt
@@ -11,9 +11,7 @@
@Suppress("EqualsOrHashCode")
@Serializable
- internal class Data(val a: Int, @Optional val b: Int = 42) {
- constructor(): this(0)
-
+ internal class Data(@Required val a: Int = 0, @Optional val b: Int = 42) {
@Optional
var c = "Hello"
@@ -35,7 +33,7 @@
@Test
fun testAll() = parametrizedTest { useStreaming ->
assertEquals("{a:0,b:42,c:Hello}", unquoted.stringify(Data(), useStreaming))
- assertEquals(unquoted.parse("{a:0,b:43,c:Hello}", useStreaming), Data(a = 0, b = 43))
+ assertEquals(unquoted.parse("{a:0,b:43,c:Hello}", useStreaming), Data(b = 43))
assertEquals(unquoted.parse("{a:0,b:42,c:Hello}", useStreaming), Data())
}