Merge remote-tracking branch 'origin/master' into dev
diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt
index e8c41fc..2743b00 100644
--- a/buildSrc/src/main/kotlin/Java9Modularity.kt
+++ b/buildSrc/src/main/kotlin/Java9Modularity.kt
@@ -18,9 +18,13 @@
 import org.jetbrains.kotlin.gradle.plugin.mpp.*
 import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.*
 import org.jetbrains.kotlin.gradle.targets.jvm.*
+import org.jetbrains.kotlin.gradle.tasks.*
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
+import org.jetbrains.kotlin.tooling.core.*
 import java.io.*
+import kotlin.reflect.*
+import kotlin.reflect.full.*
 
 object Java9Modularity {
 
@@ -108,12 +112,14 @@
         compileTask: KotlinCompile,
         sourceFile: File
     ): TaskProvider<out KotlinJvmCompile> {
-        apply<KotlinBaseApiPlugin>()
+        apply<KotlinApiPlugin>()
         val verifyModuleTaskName = "verify${compileTask.name.removePrefix("compile").capitalize()}Module"
         // work-around for https://youtrack.jetbrains.com/issue/KT-60542
-        val verifyModuleTask = plugins
-            .findPlugin(KotlinBaseApiPlugin::class)!!
-            .registerKotlinJvmCompileTask(verifyModuleTaskName)
+        val kotlinApiPlugin = plugins.getPlugin(KotlinApiPlugin::class)
+        val verifyModuleTask = kotlinApiPlugin.registerKotlinJvmCompileTask(
+            verifyModuleTaskName,
+            compileTask.compilerOptions.moduleName.get()
+        )
         verifyModuleTask {
             group = VERIFICATION_GROUP
             description = "Verify Kotlin sources for JPMS problems"
@@ -126,13 +132,14 @@
             source(sourceFile)
             destinationDirectory.set(temporaryDir)
             multiPlatformEnabled.set(compileTask.multiPlatformEnabled)
-            kotlinOptions {
-                moduleName = compileTask.kotlinOptions.moduleName
-                jvmTarget = "9"
+            compilerOptions {
+                jvmTarget.set(JvmTarget.JVM_9)
                 // To support LV override when set in aggregate builds
-                languageVersion = compileTask.kotlinOptions.languageVersion
-                freeCompilerArgs += listOf("-Xjdk-release=9",  "-Xsuppress-version-warnings", "-Xexpect-actual-classes")
-                options.optIn.addAll(compileTask.kotlinOptions.options.optIn)
+                languageVersion.set(compileTask.compilerOptions.languageVersion)
+                freeCompilerArgs.addAll(
+                    listOf("-Xjdk-release=9",  "-Xsuppress-version-warnings", "-Xexpect-actual-classes")
+                )
+                optIn.addAll(compileTask.kotlinOptions.options.optIn)
             }
             // work-around for https://youtrack.jetbrains.com/issue/KT-60583
             inputs.files(
@@ -145,14 +152,24 @@
                 }
             ).withPropertyName("moduleInfosOfLibraries")
             this as KotlinCompile
-            // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
-            @Suppress("DEPRECATION")
-            ownModuleName.set(compileTask.kotlinOptions.moduleName)
-            // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
-            @Suppress("INVISIBLE_MEMBER")
-            commonSourceSet.from(compileTask.commonSourceSet)
+            val kotlinPluginVersion = KotlinToolingVersion(kotlinApiPlugin.pluginVersion)
+            if (kotlinPluginVersion <= KotlinToolingVersion("1.9.255")) {
+                // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
+                @Suppress("UNCHECKED_CAST")
+                val ownModuleNameProp = (this::class.superclasses.first() as KClass<AbstractKotlinCompile<*>>)
+                    .declaredMemberProperties
+                    .find { it.name == "ownModuleName" }
+                    ?.get(this) as? Property<String>
+                ownModuleNameProp?.set(compileTask.kotlinOptions.moduleName)
+            }
+
+            val taskKotlinLanguageVersion = compilerOptions.languageVersion.orElse(KotlinVersion.DEFAULT)
             @OptIn(InternalKotlinGradlePluginApi::class)
-            apply {
+            if (taskKotlinLanguageVersion.get() < KotlinVersion.KOTLIN_2_0) {
+                // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
+                @Suppress("INVISIBLE_MEMBER")
+                commonSourceSet.from(compileTask.commonSourceSet)
+            } else {
                 multiplatformStructure.refinesEdges.set(compileTask.multiplatformStructure.refinesEdges)
                 multiplatformStructure.fragments.set(compileTask.multiplatformStructure.fragments)
             }
diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api
index 4b46dcc..720e584 100644
--- a/core/api/kotlinx-serialization-core.api
+++ b/core/api/kotlinx-serialization-core.api
@@ -44,6 +44,9 @@
 	public abstract fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
 }
 
+public abstract interface annotation class kotlinx/serialization/KeepGeneratedSerializer : java/lang/annotation/Annotation {
+}
+
 public abstract interface annotation class kotlinx/serialization/MetaSerializable : java/lang/annotation/Annotation {
 }
 
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index 67104dc..081ee82 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -325,6 +325,23 @@
 public annotation class Polymorphic
 
 /**
+ * Instructs the serialization plugin to keep automatically generated implementation of [KSerializer]
+ * for the current class if a custom serializer is specified at the same time `@Serializable(with=SomeSerializer::class)`.
+ *
+ * Automatically generated serializer is available via `generatedSerializer()` function in companion object of serializable class.
+ *
+ * Generated serializers allow to use custom serializers on classes from which other serializable classes are inherited.
+ *
+ * Used only with the [Serializable] annotation.
+ *
+ * A compiler version `2.0.0` and higher is required.
+ */
+@InternalSerializationApi
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+public annotation class KeepGeneratedSerializer
+
+/**
  * Marks declarations that are still **experimental** in kotlinx.serialization, which means that the design of the
  * corresponding declarations has open issues which may (or may not) lead to their changes in the future.
  * Roughly speaking, there is a chance that those declarations will be deprecated in the near future or
diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt
index 1892f6a..2489be2 100644
--- a/core/commonMain/src/kotlinx/serialization/Serializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt
@@ -187,8 +187,7 @@
 ): KSerializer<Any?>? {
     val rootClass = type.kclass()
     val isNullable = type.isMarkedNullable
-    val typeArguments = type.arguments
-        .map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } }
+    val typeArguments = type.arguments.map(KTypeProjection::typeOrThrow)
 
     val cachedSerializer = if (typeArguments.isEmpty()) {
         findCachedSerializer(rootClass, isNullable)
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
index c16945c..ef313cc 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
@@ -102,16 +102,22 @@
 internal fun KType.kclass() = when (val t = classifier) {
     is KClass<*> -> t
     is KTypeParameter -> {
-        error(
+        // If you are going to change this error message, please also actualize the message in the compiler intrinsics here:
+        // Kotlin/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt#argumentTypeOrGenerateException
+        throw IllegalArgumentException(
             "Captured type parameter $t from generic non-reified function. " +
-                    "Such functionality cannot be supported as $t is erased, either specify serializer explicitly or make " +
-                    "calling function inline with reified $t"
+                    "Such functionality cannot be supported because $t is erased, either specify serializer explicitly or make " +
+                    "calling function inline with reified $t."
         )
     }
 
-    else -> error("Only KClass supported as classifier, got $t")
+    else ->  throw IllegalArgumentException("Only KClass supported as classifier, got $t")
 } as KClass<Any>
 
+// If you are going to change this error message, please also actualize the message in the compiler intrinsics here:
+// Kotlin/plugins/kotlinx-serialization/kotlinx-serialization.backend/src/org/jetbrains/kotlinx/serialization/compiler/backend/ir/SerializationJvmIrIntrinsicSupport.kt#argumentTypeOrGenerateException
+internal fun KTypeProjection.typeOrThrow(): KType = requireNotNull(type) { "Star projections in type arguments are not allowed, but had $type" }
+
 /**
  * Constructs KSerializer<D<T0, T1, ...>> by given KSerializer<T0>, KSerializer<T1>, ...
  * via reflection (on JVM) or compiler+plugin intrinsic `SerializerFactory` (on Native)
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
index f01f952..8a9126d 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
@@ -8,7 +8,6 @@
 import kotlinx.serialization.internal.*
 import kotlin.js.*
 import kotlin.jvm.*
-import kotlin.native.concurrent.*
 import kotlin.reflect.*
 
 /**
diff --git a/docs/basic-serialization.md b/docs/basic-serialization.md
index 3853376..96e7098 100644
--- a/docs/basic-serialization.md
+++ b/docs/basic-serialization.md
@@ -534,7 +534,7 @@
 
 ```text
 Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language
-Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls to default values.
+Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value.
 ```
 
 <!--- TEST LINES_START -->
diff --git a/docs/json.md b/docs/json.md
index d764ce5..c637eb1 100644
--- a/docs/json.md
+++ b/docs/json.md
@@ -20,6 +20,7 @@
   * [Allowing structured map keys](#allowing-structured-map-keys)
   * [Allowing special floating-point values](#allowing-special-floating-point-values)
   * [Class discriminator for polymorphism](#class-discriminator-for-polymorphism)
+  * [Class discriminator output mode](#class-discriminator-output-mode)
   * [Decoding enums in a case-insensitive manner](#decoding-enums-in-a-case-insensitive-manner)
   * [Global naming strategy](#global-naming-strategy)
 * [Json elements](#json-elements)
@@ -94,7 +95,7 @@
 ### Lenient parsing
 
 By default, [Json] parser enforces various JSON restrictions to be as specification-compliant as possible
-(see [RFC-4627]). Particularly, keys must be quoted, while literals must be unquoted. Those restrictions can be relaxed with
+(see [RFC-4627]). Particularly, keys and string literals must be quoted. Those restrictions can be relaxed with
 the [isLenient][JsonBuilder.isLenient] property. With `isLenient = true`, you can parse quite freely-formatted data:
 
 ```kotlin
@@ -470,6 +471,45 @@
 
 <!--- TEST -->
 
+### Class discriminator output mode
+
+Class discriminator provides information for serializing and deserializing [polymorphic class hierarchies](polymorphism.md#sealed-classes).
+As shown above, it is only added for polymorphic classes by default.
+In case you want to encode more or less information for various third party APIs about types in the output, it is possible to control
+addition of the class discriminator with the [JsonBuilder.classDiscriminatorMode] property.
+
+For example, [ClassDiscriminatorMode.NONE] does not add class discriminator at all, in case the receiving party is not interested in Kotlin types:
+
+```kotlin
+val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE }
+
+@Serializable
+sealed class Project {
+    abstract val name: String
+}
+
+@Serializable
+class OwnedProject(override val name: String, val owner: String) : Project()
+
+fun main() {
+    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
+    println(format.encodeToString(data))
+}
+```
+
+> You can get the full code [here](../guide/example/example-json-12.kt).
+
+Note that it would be impossible to deserialize this output back with kotlinx.serialization.
+
+```text
+{"name":"kotlinx.coroutines","owner":"kotlin"}
+```
+
+Two other available values are [ClassDiscriminatorMode.POLYMORPHIC] (default behavior) and [ClassDiscriminatorMode.ALL_JSON_OBJECTS] (adds discriminator whenever possible).
+Consult their documentation for details.
+
+<!--- TEST -->
+
 ### Decoding enums in a case-insensitive manner
 
 [Kotlin's naming policy recommends](https://kotlinlang.org/docs/coding-conventions.html#property-names) naming enum values
@@ -491,7 +531,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-12.kt).
+> You can get the full code [here](../guide/example/example-json-13.kt).
 
 It affects serial names as well as alternative names specified with [JsonNames] annotation, so both values are successfully decoded:
 
@@ -523,7 +563,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-13.kt).
+> You can get the full code [here](../guide/example/example-json-14.kt).
 
 As you can see, both serialization and deserialization work as if all serial names are transformed from camel case to snake case:
 
@@ -575,7 +615,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-14.kt).
+> You can get the full code [here](../guide/example/example-json-15.kt).
 
 A `JsonElement` prints itself as a valid JSON:
 
@@ -618,7 +658,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-15.kt).
+> You can get the full code [here](../guide/example/example-json-16.kt).
 
 The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`:
 
@@ -658,7 +698,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-16.kt).
+> You can get the full code [here](../guide/example/example-json-17.kt).
 
 As a result, you get a proper JSON string:
 
@@ -687,7 +727,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-17.kt).
+> You can get the full code [here](../guide/example/example-json-18.kt).
 
 The result is exactly what you would expect:
 
@@ -733,7 +773,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-18.kt).
+> You can get the full code [here](../guide/example/example-json-19.kt).
 
 Even though `pi` was defined as a number with 30 decimal places, the resulting JSON does not reflect this. 
 The [Double] value is truncated to 15 decimal places, and the String is wrapped in quotes - which is not a JSON number.
@@ -773,7 +813,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-19.kt).
+> You can get the full code [here](../guide/example/example-json-20.kt).
 
 `pi_literal` now accurately matches the value defined.
 
@@ -813,7 +853,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-20.kt).
+> You can get the full code [here](../guide/example/example-json-21.kt).
 
 The exact value of `pi` is decoded, with all 30 decimal places of precision that were in the source JSON.
 
@@ -835,7 +875,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-21.kt).
+> You can get the full code [here](../guide/example/example-json-22.kt).
 
 ```text
 Exception in thread "main" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive
@@ -911,7 +951,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-22.kt).
+> You can get the full code [here](../guide/example/example-json-23.kt).
 
 The output shows that both cases are correctly deserialized into a Kotlin [List].
 
@@ -963,7 +1003,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-23.kt).
+> You can get the full code [here](../guide/example/example-json-24.kt).
 
 You end up with a single JSON object, not an array with one element:
 
@@ -1008,7 +1048,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-24.kt).
+> You can get the full code [here](../guide/example/example-json-25.kt).
 
 See the effect of the custom serializer:
 
@@ -1081,7 +1121,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-25.kt).
+> You can get the full code [here](../guide/example/example-json-26.kt).
 
 No class discriminator is added in the JSON output:
 
@@ -1177,7 +1217,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-26.kt).
+> You can get the full code [here](../guide/example/example-json-27.kt).
 
 This gives you fine-grained control on the representation of the `Response` class in the JSON output:
 
@@ -1242,7 +1282,7 @@
 }
 ```
 
-> You can get the full code [here](../guide/example/example-json-27.kt).
+> You can get the full code [here](../guide/example/example-json-28.kt).
 
 ```text
 UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"})
@@ -1296,6 +1336,10 @@
 [JsonBuilder.allowSpecialFloatingPointValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html
 [JsonBuilder.classDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html
 [JsonClassDiscriminator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/index.html
+[JsonBuilder.classDiscriminatorMode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator-mode.html
+[ClassDiscriminatorMode.NONE]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-n-o-n-e/index.html
+[ClassDiscriminatorMode.POLYMORPHIC]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-p-o-l-y-m-o-r-p-h-i-c/index.html
+[ClassDiscriminatorMode.ALL_JSON_OBJECTS]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-class-discriminator-mode/-a-l-l_-j-s-o-n_-o-b-j-e-c-t-s/index.html
 [JsonBuilder.decodeEnumsCaseInsensitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/decode-enums-case-insensitive.html
 [JsonBuilder.namingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/naming-strategy.html
 [JsonNamingStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/index.html
diff --git a/docs/polymorphism.md b/docs/polymorphism.md
index b7ea31f..2661e04 100644
--- a/docs/polymorphism.md
+++ b/docs/polymorphism.md
@@ -542,7 +542,7 @@
 
 > You can get the full code [here](../guide/example/example-poly-13.kt).
 
-However, the `Any` is a class and it is not serializable:
+However, `Any` is a class and it is not serializable:
 
 ```text 
 Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.
diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md
index 68ede14..50cb130 100644
--- a/docs/serialization-guide.md
+++ b/docs/serialization-guide.md
@@ -120,6 +120,7 @@
   * <a name='allowing-structured-map-keys'></a>[Allowing structured map keys](json.md#allowing-structured-map-keys)
   * <a name='allowing-special-floating-point-values'></a>[Allowing special floating-point values](json.md#allowing-special-floating-point-values)
   * <a name='class-discriminator-for-polymorphism'></a>[Class discriminator for polymorphism](json.md#class-discriminator-for-polymorphism)
+  * <a name='class-discriminator-output-mode'></a>[Class discriminator output mode](json.md#class-discriminator-output-mode)
   * <a name='decoding-enums-in-a-case-insensitive-manner'></a>[Decoding enums in a case-insensitive manner](json.md#decoding-enums-in-a-case-insensitive-manner)
   * <a name='global-naming-strategy'></a>[Global naming strategy](json.md#global-naming-strategy)
 * <a name='json-elements'></a>[Json elements](json.md#json-elements)
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt
index f2f2779..163e562 100644
--- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt
@@ -109,11 +109,13 @@
 
         private fun getTaggedNumber(tag: T) = validateAndCast<Number>(tag)
 
+        @Suppress("UNCHECKED_CAST")
+        protected fun <E> decodeDuration(tag: T): E =
+            getValueFromTaggedConfig(tag, ::decodeDurationImpl) as E
+
         @SuppressAnimalSniffer
-        protected fun <E> decodeDuration(tag: T): E {
-            @Suppress("UNCHECKED_CAST")
-            return getValueFromTaggedConfig(tag) { conf, path -> conf.decodeJavaDuration(path).toKotlinDuration() } as E
-        }
+        private fun decodeDurationImpl(conf: Config, path: String): Duration =
+            conf.decodeJavaDuration(path).toKotlinDuration()
 
         override fun decodeTaggedString(tag: T) = validateAndCast<String>(tag)
 
@@ -145,7 +147,7 @@
 
     }
 
-    private inner class ConfigReader(val conf: Config) : ConfigConverter<String>() {
+    private inner class ConfigReader(val conf: Config, private val isPolymorphic: Boolean = false) : ConfigConverter<String>() {
         private var ind = -1
 
         override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
@@ -161,8 +163,10 @@
         private fun composeName(parentName: String, childName: String) =
             if (parentName.isEmpty()) childName else "$parentName.$childName"
 
-        override fun SerialDescriptor.getTag(index: Int): String =
-            composeName(currentTagOrNull.orEmpty(), getConventionElementName(index, useConfigNamingConvention))
+        override fun SerialDescriptor.getTag(index: Int): String {
+            val conventionName = getConventionElementName(index, useConfigNamingConvention)
+            return if (!isPolymorphic) composeName(currentTagOrNull.orEmpty(), conventionName) else conventionName
+        }
 
         override fun decodeNotNullMark(): Boolean {
             // Tag might be null for top-level deserialization
@@ -206,6 +210,27 @@
         }
     }
 
+    private inner class PolymorphConfigReader(private val conf: Config) : ConfigConverter<String>() {
+        private var ind = -1
+
+        override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
+            when {
+                descriptor.kind.objLike -> ConfigReader(conf, isPolymorphic = true)
+                else -> this
+            }
+
+        override fun SerialDescriptor.getTag(index: Int): String = getElementName(index)
+
+        override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
+            ind++
+            return if (ind >= descriptor.elementsCount) DECODE_DONE else ind
+        }
+
+        override fun <E> getValueFromTaggedConfig(tag: String, valueResolver: (Config, String) -> E): E {
+            return valueResolver(conf, tag)
+        }
+    }
+
     private inner class ListConfigReader(private val list: ConfigList) : ConfigConverter<Int>() {
         private var ind = -1
 
@@ -216,6 +241,7 @@
 
         override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
             when {
+                descriptor.kind is PolymorphicKind -> PolymorphConfigReader((list[currentTag] as ConfigObject).toConfig())
                 descriptor.kind.listLike -> ListConfigReader(list[currentTag] as ConfigList)
                 descriptor.kind.objLike -> ConfigReader((list[currentTag] as ConfigObject).toConfig())
                 descriptor.kind == StructureKind.MAP -> MapConfigReader(list[currentTag] as ConfigObject)
@@ -256,6 +282,7 @@
 
         override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder =
             when {
+                descriptor.kind is PolymorphicKind -> PolymorphConfigReader((values[currentTag / 2] as ConfigObject).toConfig())
                 descriptor.kind.listLike -> ListConfigReader(values[currentTag / 2] as ConfigList)
                 descriptor.kind.objLike -> ConfigReader((values[currentTag / 2] as ConfigObject).toConfig())
                 descriptor.kind == StructureKind.MAP -> MapConfigReader(values[currentTag / 2] as ConfigObject)
diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt
index db038e7..1dbc1f9 100644
--- a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt
+++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt
@@ -24,6 +24,12 @@
     }
 
     @Serializable
+    data class SealedCollectionContainer(val sealed: Collection<Sealed>)
+
+    @Serializable
+    data class SealedMapContainer(val sealed: Map<String, Sealed>)
+
+    @Serializable
     data class CompositeClass(var sealed: Sealed)
 
 
@@ -102,4 +108,46 @@
             serializer = Sealed.serializer(),
         )
     }
+
+    @Test
+    fun testCollectionContainer() {
+        objectHocon.assertStringFormAndRestored(
+            expected = """
+                sealed = [ 
+                    { type = annotated_type_child, my_type = override, intField = 3 }
+                    { type = object }
+                    { type = data_class, name = testDataClass, intField = 1 }
+                ]
+            """.trimIndent(),
+            original = SealedCollectionContainer(
+                listOf(
+                    Sealed.AnnotatedTypeChild(type = "override"),
+                    Sealed.ObjectChild,
+                    Sealed.DataClassChild(name = "testDataClass"),
+                )
+            ),
+            serializer = SealedCollectionContainer.serializer(),
+        )
+    }
+
+    @Test
+    fun testMapContainer() {
+        objectHocon.assertStringFormAndRestored(
+            expected = """
+                sealed = { 
+                    "annotated_type_child" = { type = annotated_type_child, my_type = override, intField = 3 }
+                    "object" = { type = object }
+                    "data_class" = { type = data_class, name = testDataClass, intField = 1 }
+                }
+            """.trimIndent(),
+            original = SealedMapContainer(
+                mapOf(
+                    "annotated_type_child" to Sealed.AnnotatedTypeChild(type = "override"),
+                    "object" to Sealed.ObjectChild,
+                    "data_class" to Sealed.DataClassChild(name = "testDataClass"),
+                )
+            ),
+            serializer = SealedMapContainer.serializer(),
+        )
+    }
 }
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
index 28a4d12..9e2cf1d 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
@@ -103,6 +103,52 @@
         }
     }
 
+    @Test
+    fun testKebabCaseStrategy() {
+        fun apply(name: String) =
+            JsonNamingStrategy.KebabCase.serialNameForJson(String.serializer().descriptor, 0, name)
+
+        val cases = mapOf<String, String>(
+            "" to "",
+            "_" to "_",
+            "-" to "-",
+            "___" to "___",
+            "---" to "---",
+            "a" to "a",
+            "A" to "a",
+            "-1" to "-1",
+            "-a" to "-a",
+            "-A" to "-a",
+            "property" to "property",
+            "twoWords" to "two-words",
+            "threeDistinctWords" to "three-distinct-words",
+            "ThreeDistinctWords" to "three-distinct-words",
+            "Oneword" to "oneword",
+            "camel-Case-WithDashes" to "camel-case-with-dashes",
+            "_many----dashes--" to "_many----dashes--",
+            "URLmapping" to "ur-lmapping",
+            "URLMapping" to "url-mapping",
+            "IOStream" to "io-stream",
+            "IOstream" to "i-ostream",
+            "myIo2Stream" to "my-io2-stream",
+            "myIO2Stream" to "my-io2-stream",
+            "myIO2stream" to "my-io2stream",
+            "myIO2streamMax" to "my-io2stream-max",
+            "InURLBetween" to "in-url-between",
+            "myHTTP2APIKey" to "my-http2-api-key",
+            "myHTTP2fastApiKey" to "my-http2fast-api-key",
+            "myHTTP23APIKey" to "my-http23-api-key",
+            "myHttp23ApiKey" to "my-http23-api-key",
+            "theWWW" to "the-www",
+            "theWWW-URL-xxx" to "the-www-url-xxx",
+            "hasDigit123AndPostfix" to "has-digit123-and-postfix"
+        )
+
+        cases.forEach { (input, expected) ->
+            assertEquals(expected, apply(input))
+        }
+    }
+
     @Serializable
     data class DontUseOriginal(val testCase: String)
 
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
index ecb946c..3d7c332 100644
--- a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
@@ -29,6 +29,16 @@
         val enum: SampleEnum?
     )
 
+    @Serializable
+    class Uncoercable(
+        val s: String
+    )
+
+    @Serializable
+    class UncoercableEnum(
+        val e: SampleEnum
+    )
+
     val json = Json {
         coerceInputValues = true
         isLenient = true
@@ -112,4 +122,24 @@
         decoded = decodeFromString<NullableEnumHolder>("""{"enum": OptionA}""")
         assertEquals(SampleEnum.OptionA, decoded.enum)
     }
+
+    @Test
+    fun propertiesWithoutDefaultValuesDoNotChangeErrorMsg() {
+        val json2 = Json(json) { coerceInputValues = false }
+        parametrizedTest { mode ->
+            val e1 = assertFailsWith<SerializationException>() { json.decodeFromString<Uncoercable>("""{"s":null}""", mode) }
+            val e2 = assertFailsWith<SerializationException>() { json2.decodeFromString<Uncoercable>("""{"s":null}""", mode) }
+            assertEquals(e2.message, e1.message)
+        }
+    }
+
+    @Test
+    fun propertiesWithoutDefaultValuesDoNotChangeErrorMsgEnum() {
+        val json2 = Json(json) { coerceInputValues = false }
+        parametrizedTest { mode ->
+            val e1 = assertFailsWith<SerializationException> { json.decodeFromString<UncoercableEnum>("""{"e":"UNEXPECTED"}""", mode) }
+            val e2 = assertFailsWith<SerializationException> { json2.decodeFromString<UncoercableEnum>("""{"e":"UNEXPECTED"}""", mode) }
+            assertEquals(e2.message, e1.message)
+        }
+    }
 }
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonPrettyPrintTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonPrettyPrintTest.kt
new file mode 100644
index 0000000..8283e25
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonPrettyPrintTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlin.test.*
+
+class JsonPrettyPrintTest : JsonTestBase() {
+    val fmt = Json(default) { prettyPrint = true; encodeDefaults = true }
+
+    @Serializable
+    class Empty
+
+    @Serializable
+    class A(val empty: Empty = Empty())
+
+    @Serializable
+    class B(val prefix: String = "a", val empty: Empty = Empty(), val postfix: String = "b")
+
+    @Serializable
+    class Recursive(val rec: Recursive?, val empty: Empty = Empty())
+
+    @Serializable
+    class WithListRec(val rec: WithListRec?, val l: List<Int> = listOf())
+
+    @Serializable
+    class WithDefaults(val x: String = "x", val y: Int = 0)
+
+    @Test
+    fun testTopLevel() = parametrizedTest { mode ->
+        assertEquals("{}", fmt.encodeToString(Empty(), mode))
+    }
+
+    @Test
+    fun testWithDefaults() = parametrizedTest { mode ->
+        val dropDefaults = Json(fmt) { encodeDefaults = false }
+        val s = "{\n    \"boxed\": {}\n}"
+        assertEquals(s, dropDefaults.encodeToString(Box(WithDefaults()), mode))
+    }
+
+    @Test
+    fun testPlain() = parametrizedTest { mode ->
+        val s = """{
+        |    "empty": {}
+        |}""".trimMargin()
+        assertEquals(s, fmt.encodeToString(A(), mode))
+    }
+
+    @Test
+    fun testInside() = parametrizedTest { mode ->
+        val s = """{
+        |    "prefix": "a",
+        |    "empty": {},
+        |    "postfix": "b"
+        |}""".trimMargin()
+        assertEquals(s, fmt.encodeToString(B(), mode))
+    }
+
+    @Test
+    fun testRecursive() = parametrizedTest { mode ->
+        val obj = Recursive(Recursive(null))
+        val s = "{\n    \"rec\": {\n        \"rec\": null,\n        \"empty\": {}\n    },\n    \"empty\": {}\n}"
+        assertEquals(s, fmt.encodeToString(obj, mode))
+    }
+
+    @Test
+    fun test() = parametrizedTest { mode ->
+        val obj = WithListRec(WithListRec(null), listOf(1, 2, 3))
+        val s =
+            "{\n    \"rec\": {\n        \"rec\": null,\n        \"l\": []\n    },\n    \"l\": [\n        1,\n        2,\n        3\n    ]\n}"
+        assertEquals(s, fmt.encodeToString(obj, mode))
+    }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeBaseTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeBaseTest.kt
new file mode 100644
index 0000000..8fcd549
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeBaseTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.polymorphic
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import kotlin.test.*
+
+abstract class JsonClassDiscriminatorModeBaseTest(
+    val discriminator: ClassDiscriminatorMode,
+    val deserializeBack: Boolean = true
+) : JsonTestBase() {
+
+    @Serializable
+    sealed class SealedBase
+
+    @Serializable
+    @SerialName("container")
+    data class SealedContainer(val i: Inner): SealedBase()
+
+    @Serializable
+    @SerialName("inner")
+    data class Inner(val x: String, val e: SampleEnum = SampleEnum.OptionB)
+
+    @Serializable
+    @SerialName("outer")
+    data class Outer(val inn: Inner, val lst: List<Inner>, val lss: List<String>)
+
+    data class ContextualType(val text: String)
+
+    object CtxSerializer : KSerializer<ContextualType> {
+        override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CtxSerializer") {
+            element("a", String.serializer().descriptor)
+            element("b", String.serializer().descriptor)
+        }
+
+        override fun serialize(encoder: Encoder, value: ContextualType) {
+            encoder.encodeStructure(descriptor) {
+                encodeStringElement(descriptor, 0, value.text.substringBefore("#"))
+                encodeStringElement(descriptor, 1, value.text.substringAfter("#"))
+            }
+        }
+
+        override fun deserialize(decoder: Decoder): ContextualType {
+            lateinit var a: String
+            lateinit var b: String
+            decoder.decodeStructure(descriptor) {
+                while (true) {
+                    when (decodeElementIndex(descriptor)) {
+                        0 -> a = decodeStringElement(descriptor, 0)
+                        1 -> b = decodeStringElement(descriptor, 1)
+                        else -> break
+                    }
+                }
+            }
+            return ContextualType("$a#$b")
+        }
+    }
+
+    @Serializable
+    @SerialName("withContextual")
+    data class WithContextual(@Contextual val ctx: ContextualType, val i: Inner)
+
+    val ctxModule = serializersModuleOf(CtxSerializer)
+
+    val json = Json(default) {
+        ignoreUnknownKeys = true
+        serializersModule = polymorphicTestModule + ctxModule
+        encodeDefaults = true
+        classDiscriminatorMode = discriminator
+    }
+
+    @Serializable
+    @SerialName("mixed")
+    data class MixedPolyAndRegular(val sb: SealedBase, val sc: SealedContainer, val i: Inner)
+
+    private inline fun <reified T> doTest(expected: String, obj: T) {
+        parametrizedTest { mode ->
+            val serialized = json.encodeToString(serializer<T>(), obj, mode)
+            assertEquals(expected, serialized, "Failed with mode = $mode")
+            if (deserializeBack) {
+                val deserialized: T = json.decodeFromString(serializer(), serialized, mode)
+                assertEquals(obj, deserialized, "Failed with mode = $mode")
+            }
+        }
+    }
+
+    fun testMixed(expected: String) {
+        val i = Inner("in", SampleEnum.OptionC)
+        val o = MixedPolyAndRegular(SealedContainer(i), SealedContainer(i), i)
+        doTest(expected, o)
+    }
+
+    fun testIncludeNonPolymorphic(expected: String) {
+        val o = Outer(Inner("X"), listOf(Inner("a"), Inner("b")), listOf("foo"))
+        doTest(expected, o)
+    }
+
+    fun testIncludePolymorphic(expected: String) {
+        val o = OuterNullableBox(OuterNullableImpl(InnerImpl(42), null), InnerImpl2(239))
+        doTest(expected, o)
+    }
+
+    fun testIncludeSealed(expected: String) {
+        val b = Box<SealedBase>(SealedContainer(Inner("x", SampleEnum.OptionC)))
+        doTest(expected, b)
+    }
+
+    fun testContextual(expected: String) {
+        val c = WithContextual(ContextualType("c#d"), Inner("x"))
+        doTest(expected, c)
+    }
+
+    @Serializable
+    @JsonClassDiscriminator("message_type")
+    sealed class Base
+
+    @Serializable // Class discriminator is inherited from Base
+    sealed class ErrorClass : Base()
+
+    @Serializable
+    @SerialName("ErrorClassImpl")
+    data class ErrorClassImpl(val msg: String) : ErrorClass()
+
+    @Serializable
+    @SerialName("Cont")
+    data class Cont(val ec: ErrorClass, val eci: ErrorClassImpl)
+
+    fun testCustomDiscriminator(expected: String) {
+        val c = Cont(ErrorClassImpl("a"), ErrorClassImpl("b"))
+        doTest(expected, c)
+    }
+
+    fun testTopLevelPolyImpl(expectedOpen: String, expectedSealed: String) {
+        assertEquals(expectedOpen, json.encodeToString(InnerImpl(42)))
+        assertEquals(expectedSealed, json.encodeToString(SealedContainer(Inner("x"))))
+    }
+
+    @Serializable
+    @SerialName("NullableMixed")
+    data class NullableMixed(val sb: SealedBase?, val sc: SealedContainer?)
+
+    fun testNullable(expected: String) {
+        val nm = NullableMixed(null, null)
+       doTest(expected, nm)
+    }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeTest.kt
new file mode 100644
index 0000000..b2f4713
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.polymorphic
+
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+class ClassDiscriminatorModeAllObjectsTest :
+    JsonClassDiscriminatorModeBaseTest(ClassDiscriminatorMode.ALL_JSON_OBJECTS) {
+    @Test
+    fun testIncludeNonPolymorphic() = testIncludeNonPolymorphic("""{"type":"outer","inn":{"type":"inner","x":"X","e":"OptionB"},"lst":[{"type":"inner","x":"a","e":"OptionB"},{"type":"inner","x":"b","e":"OptionB"}],"lss":["foo"]}""")
+
+    @Test
+    fun testIncludePolymorphic() {
+        val s = """{"type":"kotlinx.serialization.json.polymorphic.OuterNullableBox","outerBase":{"type":"kotlinx.serialization.json.polymorphic.OuterNullableImpl","""+
+            """"base":{"type":"kotlinx.serialization.json.polymorphic.InnerImpl","field":42,"str":"default","nullable":null},"base2":null},"innerBase":{"type":"kotlinx.serialization.json.polymorphic.InnerImpl2","field":239}}"""
+        testIncludePolymorphic(s)
+    }
+
+    @Test
+    fun testIncludeSealed() {
+        testIncludeSealed("""{"type":"kotlinx.serialization.Box","boxed":{"type":"container","i":{"type":"inner","x":"x","e":"OptionC"}}}""")
+    }
+
+    @Test
+    fun testIncludeMixed() = testMixed("""{"type":"mixed","sb":{"type":"container","i":{"type":"inner","x":"in","e":"OptionC"}},"sc":{"type":"container","i":{"type":"inner","x":"in","e":"OptionC"}},"i":{"type":"inner","x":"in","e":"OptionC"}}""")
+
+    @Test
+    fun testIncludeCtx() =
+        testContextual("""{"type":"withContextual","ctx":{"type":"CtxSerializer","a":"c","b":"d"},"i":{"type":"inner","x":"x","e":"OptionB"}}""")
+
+    @Test
+    fun testIncludeCustomDiscriminator() =
+        testCustomDiscriminator("""{"type":"Cont","ec":{"message_type":"ErrorClassImpl","msg":"a"},"eci":{"message_type":"ErrorClassImpl","msg":"b"}}""")
+
+    @Test
+    fun testTopLevelPolyImpl() = testTopLevelPolyImpl(
+        """{"type":"kotlinx.serialization.json.polymorphic.InnerImpl","field":42,"str":"default","nullable":null}""",
+        """{"type":"container","i":{"type":"inner","x":"x","e":"OptionB"}}"""
+    )
+
+    @Test
+    fun testNullable() = testNullable("""{"type":"NullableMixed","sb":null,"sc":null}""")
+
+}
+
+class ClassDiscriminatorModeNoneTest :
+    JsonClassDiscriminatorModeBaseTest(ClassDiscriminatorMode.NONE, deserializeBack = false) {
+    @Test
+    fun testIncludeNonPolymorphic() = testIncludeNonPolymorphic("""{"inn":{"x":"X","e":"OptionB"},"lst":[{"x":"a","e":"OptionB"},{"x":"b","e":"OptionB"}],"lss":["foo"]}""")
+
+    @Test
+    fun testIncludePolymorphic() {
+        val s = """{"outerBase":{"base":{"field":42,"str":"default","nullable":null},"base2":null},"innerBase":{"field":239}}"""
+        testIncludePolymorphic(s)
+    }
+
+    @Test
+    fun testIncludeSealed() {
+        testIncludeSealed("""{"boxed":{"i":{"x":"x","e":"OptionC"}}}""")
+    }
+
+    @Test
+    fun testIncludeMixed() = testMixed("""{"sb":{"i":{"x":"in","e":"OptionC"}},"sc":{"i":{"x":"in","e":"OptionC"}},"i":{"x":"in","e":"OptionC"}}""")
+
+    @Test
+    fun testIncludeCtx() =
+        testContextual("""{"ctx":{"a":"c","b":"d"},"i":{"x":"x","e":"OptionB"}}""")
+
+    @Test
+    fun testIncludeCustomDiscriminator() = testCustomDiscriminator("""{"ec":{"msg":"a"},"eci":{"msg":"b"}}""")
+
+    @Test
+    fun testTopLevelPolyImpl() = testTopLevelPolyImpl(
+        """{"field":42,"str":"default","nullable":null}""",
+        """{"i":{"x":"x","e":"OptionB"}}"""
+    )
+
+    @Test
+    fun testNullable() = testNullable("""{"sb":null,"sc":null}""")
+}
+
diff --git a/formats/json/api/kotlinx-serialization-json.api b/formats/json/api/kotlinx-serialization-json.api
index 649cce0..6874d74 100644
--- a/formats/json/api/kotlinx-serialization-json.api
+++ b/formats/json/api/kotlinx-serialization-json.api
@@ -1,3 +1,12 @@
+public final class kotlinx/serialization/json/ClassDiscriminatorMode : java/lang/Enum {
+	public static final field ALL_JSON_OBJECTS Lkotlinx/serialization/json/ClassDiscriminatorMode;
+	public static final field NONE Lkotlinx/serialization/json/ClassDiscriminatorMode;
+	public static final field POLYMORPHIC Lkotlinx/serialization/json/ClassDiscriminatorMode;
+	public static fun getEntries ()Lkotlin/enums/EnumEntries;
+	public static fun valueOf (Ljava/lang/String;)Lkotlinx/serialization/json/ClassDiscriminatorMode;
+	public static fun values ()[Lkotlinx/serialization/json/ClassDiscriminatorMode;
+}
+
 public final class kotlinx/serialization/json/DecodeSequenceMode : java/lang/Enum {
 	public static final field ARRAY_WRAPPED Lkotlinx/serialization/json/DecodeSequenceMode;
 	public static final field AUTO_DETECT Lkotlinx/serialization/json/DecodeSequenceMode;
@@ -89,6 +98,7 @@
 	public final fun getAllowStructuredMapKeys ()Z
 	public final fun getAllowTrailingComma ()Z
 	public final fun getClassDiscriminator ()Ljava/lang/String;
+	public final fun getClassDiscriminatorMode ()Lkotlinx/serialization/json/ClassDiscriminatorMode;
 	public final fun getCoerceInputValues ()Z
 	public final fun getDecodeEnumsCaseInsensitive ()Z
 	public final fun getEncodeDefaults ()Z
@@ -105,6 +115,7 @@
 	public final fun setAllowStructuredMapKeys (Z)V
 	public final fun setAllowTrailingComma (Z)V
 	public final fun setClassDiscriminator (Ljava/lang/String;)V
+	public final fun setClassDiscriminatorMode (Lkotlinx/serialization/json/ClassDiscriminatorMode;)V
 	public final fun setCoerceInputValues (Z)V
 	public final fun setDecodeEnumsCaseInsensitive (Z)V
 	public final fun setEncodeDefaults (Z)V
@@ -134,6 +145,7 @@
 	public final fun getAllowStructuredMapKeys ()Z
 	public final fun getAllowTrailingComma ()Z
 	public final fun getClassDiscriminator ()Ljava/lang/String;
+	public final fun getClassDiscriminatorMode ()Lkotlinx/serialization/json/ClassDiscriminatorMode;
 	public final fun getCoerceInputValues ()Z
 	public final fun getDecodeEnumsCaseInsensitive ()Z
 	public final fun getEncodeDefaults ()Z
@@ -145,6 +157,7 @@
 	public final fun getUseAlternativeNames ()Z
 	public final fun getUseArrayPolymorphism ()Z
 	public final fun isLenient ()Z
+	public final fun setClassDiscriminatorMode (Lkotlinx/serialization/json/ClassDiscriminatorMode;)V
 	public fun toString ()Ljava/lang/String;
 }
 
@@ -266,6 +279,7 @@
 }
 
 public final class kotlinx/serialization/json/JsonNamingStrategy$Builtins {
+	public final fun getKebabCase ()Lkotlinx/serialization/json/JsonNamingStrategy;
 	public final fun getSnakeCase ()Lkotlinx/serialization/json/JsonNamingStrategy;
 }
 
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
index 26c376e..2a144fe 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
@@ -254,11 +254,10 @@
 
     /**
      * Removes JSON specification restriction (RFC-4627) and makes parser
-     * more liberal to the malformed input. In lenient mode quoted boolean literals,
-     * and unquoted string literals are allowed.
+     * more liberal to the malformed input. In lenient mode, unquoted JSON keys and string values are allowed.
      *
      * Its relaxations can be expanded in the future, so that lenient parser becomes even more
-     * permissive to invalid value in the input, replacing them with defaults.
+     * permissive to invalid values in the input.
      *
      * `false` by default.
      */
@@ -287,7 +286,7 @@
     public var prettyPrintIndent: String = json.configuration.prettyPrintIndent
 
     /**
-     * Enables coercing incorrect JSON values to the default property value in the following cases:
+     * Enables coercing incorrect JSON values to the default property value (if exists) in the following cases:
      *   1. JSON value is `null` but the property type is non-nullable.
      *   2. Property type is an enum type, but JSON value contains unknown enum member.
      *
@@ -299,6 +298,8 @@
      * Switches polymorphic serialization to the default array format.
      * This is an option for legacy JSON format and should not be generally used.
      * `false` by default.
+     *
+     * This option can only be used if [classDiscriminatorMode] in a default [ClassDiscriminatorMode.POLYMORPHIC] state.
      */
     public var useArrayPolymorphism: Boolean = json.configuration.useArrayPolymorphism
 
@@ -308,6 +309,16 @@
      */
     public var classDiscriminator: String = json.configuration.classDiscriminator
 
+
+    /**
+     * Defines which classes and objects should have class discriminator added to the output.
+     * [ClassDiscriminatorMode.POLYMORPHIC] by default.
+     *
+     * Other modes are generally intended to produce JSON for consumption by third-party libraries,
+     * therefore, this setting does not affect the deserialization process.
+     */
+    public var classDiscriminatorMode: ClassDiscriminatorMode = json.configuration.classDiscriminatorMode
+
     /**
      * Removes JSON specification restriction on
      * special floating-point values such as `NaN` and `Infinity` and enables their serialization and deserialization.
@@ -385,8 +396,13 @@
 
     @OptIn(ExperimentalSerializationApi::class)
     internal fun build(): JsonConfiguration {
-        if (useArrayPolymorphism) require(classDiscriminator == defaultDiscriminator) {
-            "Class discriminator should not be specified when array polymorphism is specified"
+        if (useArrayPolymorphism) {
+            require(classDiscriminator == defaultDiscriminator) {
+                "Class discriminator should not be specified when array polymorphism is specified"
+            }
+            require(classDiscriminatorMode == ClassDiscriminatorMode.POLYMORPHIC) {
+                "useArrayPolymorphism option can only be used if classDiscriminatorMode in a default POLYMORPHIC state."
+            }
         }
 
         if (!prettyPrint) {
@@ -406,7 +422,7 @@
             allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
             coerceInputValues, useArrayPolymorphism,
             classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
-            namingStrategy, decodeEnumsCaseInsensitive, allowTrailingComma
+            namingStrategy, decodeEnumsCaseInsensitive, allowTrailingComma, classDiscriminatorMode
         )
     }
 }
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
index 053f4cd..1fa1644 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
@@ -1,6 +1,8 @@
 package kotlinx.serialization.json
 
 import kotlinx.serialization.*
+import kotlinx.serialization.modules.*
+import kotlinx.serialization.descriptors.*
 
 /**
  * Configuration of the current [Json] instance available through [Json.configuration]
@@ -35,6 +37,8 @@
     public val decodeEnumsCaseInsensitive: Boolean = false,
     @ExperimentalSerializationApi
     public val allowTrailingComma: Boolean = false,
+    @ExperimentalSerializationApi
+    public var classDiscriminatorMode: ClassDiscriminatorMode = ClassDiscriminatorMode.POLYMORPHIC,
 ) {
 
     /** @suppress Dokka **/
@@ -43,7 +47,88 @@
         return "JsonConfiguration(encodeDefaults=$encodeDefaults, ignoreUnknownKeys=$ignoreUnknownKeys, isLenient=$isLenient, " +
                 "allowStructuredMapKeys=$allowStructuredMapKeys, prettyPrint=$prettyPrint, explicitNulls=$explicitNulls, " +
                 "prettyPrintIndent='$prettyPrintIndent', coerceInputValues=$coerceInputValues, useArrayPolymorphism=$useArrayPolymorphism, " +
-                "classDiscriminator='$classDiscriminator', allowSpecialFloatingPointValues=$allowSpecialFloatingPointValues, useAlternativeNames=$useAlternativeNames, " +
-                "namingStrategy=$namingStrategy, decodeEnumsCaseInsensitive=$decodeEnumsCaseInsensitive, allowTrailingComma=$allowTrailingComma)"
+                "classDiscriminator='$classDiscriminator', allowSpecialFloatingPointValues=$allowSpecialFloatingPointValues, " +
+                "useAlternativeNames=$useAlternativeNames, namingStrategy=$namingStrategy, decodeEnumsCaseInsensitive=$decodeEnumsCaseInsensitive, " +
+                "allowTrailingComma=$allowTrailingComma, classDiscriminatorMode=$classDiscriminatorMode)"
     }
 }
+
+/**
+ * Defines which classes and objects should have their serial name included in the json as so-called class discriminator.
+ *
+ * Class discriminator is a JSON field added by kotlinx.serialization that has [JsonBuilder.classDiscriminator] as a key (`type` by default),
+ * and class' serial name as a value (fully-qualified name by default, can be changed with [SerialName] annotation).
+ *
+ * Class discriminator is important for serializing and deserializing [polymorphic class hierarchies](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#sealed-classes).
+ * Default [ClassDiscriminatorMode.POLYMORPHIC] mode adds discriminator only to polymorphic classes.
+ * This behavior can be changed to match various JSON schemas.
+ *
+ * @see JsonBuilder.classDiscriminator
+ * @see JsonBuilder.classDiscriminatorMode
+ * @see Polymorphic
+ * @see PolymorphicSerializer
+ */
+public enum class ClassDiscriminatorMode {
+    /**
+     * Never include class discriminator in the output.
+     *
+     * This mode is generally intended to produce JSON for consumption by third-party libraries.
+     * kotlinx.serialization is unable to deserialize [polymorphic classes][POLYMORPHIC] without class discriminators,
+     * so it is impossible to deserialize JSON produced in this mode if a data model has polymorphic classes.
+     */
+    NONE,
+
+    /**
+     * Include class discriminators whenever possible.
+     *
+     * Given that class discriminator is added as a JSON field, adding class discriminator is possible
+     * when the resulting JSON is a json object — i.e., for Kotlin classes, `object`s, and interfaces.
+     * More specifically, discriminator is added to the output of serializers which descriptors
+     * have a [kind][SerialDescriptor.kind] of either [StructureKind.CLASS] or [StructureKind.OBJECT].
+     *
+     * This mode is generally intended to produce JSON for consumption by third-party libraries.
+     * Given that [JsonBuilder.classDiscriminatorMode] does not affect deserialization, kotlinx.serialization
+     * does not expect every object to have discriminator, which may trigger deserialization errors.
+     * If you experience such problems, refrain from using [ALL_JSON_OBJECTS] or use [JsonBuilder.ignoreUnknownKeys].
+     *
+     * In the example:
+     * ```
+     * @Serializable class Plain(val p: String)
+     * @Serializable sealed class Base
+     * @Serializable object Impl: Base()
+     *
+     * @Serializable class All(val p: Plain, val b: Base, val i: Impl)
+     * ```
+     * setting [JsonBuilder.classDiscriminatorMode] to [ClassDiscriminatorMode.ALL_JSON_OBJECTS] adds
+     * class discriminator to `All.p`, `All.b`, `All.i`, and to `All` object itself.
+     */
+    ALL_JSON_OBJECTS,
+
+    /**
+     * Include class discriminators for polymorphic classes.
+     *
+     * Sealed classes, abstract classes, and interfaces are polymorphic classes by definition.
+     * Open classes can be polymorphic if they are serializable with [PolymorphicSerializer]
+     * and properly registered in the [SerializersModule].
+     * See [kotlinx.serialization polymorphism guide](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#sealed-classes) for details.
+     *
+     * Note that implementations of polymorphic classes (e.g., sealed class inheritors) are not polymorphic classes from kotlinx.serialization standpoint.
+     * This means that this mode adds class discriminators only if a statically known type of the property is a base class or interface.
+     *
+     * In the example:
+     * ```
+     * @Serializable class Plain(val p: String)
+     * @Serializable sealed class Base
+     * @Serializable object Impl: Base()
+     *
+     * @Serializable class All(val p: Plain, val b: Base, val i: Impl)
+     * ```
+     * setting [JsonBuilder.classDiscriminatorMode] to [ClassDiscriminatorMode.POLYMORPHIC] adds
+     * class discriminator to `All.b`, but leaves `All.p` and `All.i` intact.
+     *
+     * @see SerializersModule
+     * @see SerializersModuleBuilder
+     * @see PolymorphicModuleBuilder
+     */
+    POLYMORPHIC,
+}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
index 64b4e0b..b737fd6 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
@@ -95,39 +95,83 @@
          */
         @ExperimentalSerializationApi
         public val SnakeCase: JsonNamingStrategy = object : JsonNamingStrategy {
-            override fun serialNameForJson(descriptor: SerialDescriptor, elementIndex: Int, serialName: String): String =
-                buildString(serialName.length * 2) {
-                    var bufferedChar: Char? = null
-                    var previousUpperCharsCount = 0
-
-                    serialName.forEach { c ->
-                        if (c.isUpperCase()) {
-                            if (previousUpperCharsCount == 0 && isNotEmpty() && last() != '_')
-                                append('_')
-
-                            bufferedChar?.let(::append)
-
-                            previousUpperCharsCount++
-                            bufferedChar = c.lowercaseChar()
-                        } else {
-                            if (bufferedChar != null) {
-                                if (previousUpperCharsCount > 1 && c.isLetter()) {
-                                    append('_')
-                                }
-                                append(bufferedChar)
-                                previousUpperCharsCount = 0
-                                bufferedChar = null
-                            }
-                            append(c)
-                        }
-                    }
-
-                    if(bufferedChar != null) {
-                        append(bufferedChar)
-                    }
-                }
+            override fun serialNameForJson(
+                descriptor: SerialDescriptor,
+                elementIndex: Int,
+                serialName: String
+            ): String = convertCamelCase(serialName, '_')
 
             override fun toString(): String = "kotlinx.serialization.json.JsonNamingStrategy.SnakeCase"
         }
+
+        /**
+         * A strategy that transforms serial names from camel case to kebab case — lowercase characters with words separated by dashes.
+         * The descriptor parameter is not used.
+         *
+         * **Transformation rules**
+         *
+         * Words' bounds are defined by uppercase characters. If there is a single uppercase char, it is transformed into lowercase one with a dash in front:
+         * `twoWords` -> `two-words`. No dash is added if it was a beginning of the name: `MyProperty` -> `my-property`. Also, no dash is added if it was already there:
+         * `camel-Case-WithDashes` -> `camel-case-with-dashes`.
+         *
+         * **Acronyms**
+         *
+         * Since acronym rules are quite complex, it is recommended to lowercase all acronyms in source code.
+         * If there is an uppercase acronym — a sequence of uppercase chars — they are considered as a whole word from the start to second-to-last character of the sequence:
+         * `URLMapping` -> `url-mapping`, `myHTTPAuth` -> `my-http-auth`. Non-letter characters allow the word to continue:
+         * `myHTTP2APIKey` -> `my-http2-api-key`,  `myHTTP2fastApiKey` -> `my-http2fast-api-key`.
+         *
+         * **Note on cases**
+         *
+         * Whether a character is in upper case is determined by the result of [Char.isUpperCase] function.
+         * Lowercase transformation is performed by [Char.lowercaseChar], not by [Char.lowercase],
+         * and therefore does not support one-to-many and many-to-one character mappings.
+         * See the documentation of these functions for details.
+         */
+        @ExperimentalSerializationApi
+        public val KebabCase: JsonNamingStrategy = object : JsonNamingStrategy {
+            override fun serialNameForJson(
+                descriptor: SerialDescriptor,
+                elementIndex: Int,
+                serialName: String
+            ): String = convertCamelCase(serialName, '-')
+
+            override fun toString(): String = "kotlinx.serialization.json.JsonNamingStrategy.KebabCase"
+        }
+
+        private fun convertCamelCase(
+            serialName: String,
+            delimiter: Char
+        ) = buildString(serialName.length * 2) {
+                var bufferedChar: Char? = null
+                var previousUpperCharsCount = 0
+
+                serialName.forEach { c ->
+                    if (c.isUpperCase()) {
+                        if (previousUpperCharsCount == 0 && isNotEmpty() && last() != delimiter)
+                            append(delimiter)
+
+                        bufferedChar?.let(::append)
+
+                        previousUpperCharsCount++
+                        bufferedChar = c.lowercaseChar()
+                    } else {
+                        if (bufferedChar != null) {
+                            if (previousUpperCharsCount > 1 && c.isLetter()) {
+                                append(delimiter)
+                            }
+                            append(bufferedChar)
+                            previousUpperCharsCount = 0
+                            bufferedChar = null
+                        }
+                        append(c)
+                    }
+                }
+
+                if (bufferedChar != null) {
+                    append(bufferedChar)
+                }
+            }
+
     }
 }
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
index c1ed8cc..abdd1c4 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
@@ -27,6 +27,10 @@
         writingFirst = false
     }
 
+    open fun nextItemIfNotFirst() {
+        writingFirst = false
+    }
+
     open fun space() = Unit
 
     fun print(v: Char) = writer.writeChar(v)
@@ -88,6 +92,11 @@
         repeat(level) { print(json.configuration.prettyPrintIndent) }
     }
 
+    override fun nextItemIfNotFirst() {
+        if (writingFirst) writingFirst = false
+        else nextItem()
+    }
+
     override fun space() {
         print(' ')
     }
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonConfiguration.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonConfiguration.kt
deleted file mode 100644
index e69de29..0000000
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonConfiguration.kt
+++ /dev/null
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
index 8acd8fc..9128f3a 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
@@ -110,11 +110,14 @@
 
 @OptIn(ExperimentalSerializationApi::class)
 internal inline fun Json.tryCoerceValue(
-    elementDescriptor: SerialDescriptor,
+    descriptor: SerialDescriptor,
+    index: Int,
     peekNull: (consume: Boolean) -> Boolean,
     peekString: () -> String?,
     onEnumCoercing: () -> Unit = {}
 ): Boolean {
+    if (!descriptor.isElementOptional(index)) return false
+    val elementDescriptor = descriptor.getElementDescriptor(index)
     if (!elementDescriptor.isNullable && peekNull(true)) return true
     if (elementDescriptor.kind == SerialKind.ENUM) {
         if (elementDescriptor.isNullable && peekNull(false)) {
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
index bd658fc..636f340 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt
@@ -9,6 +9,7 @@
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.internal.*
 import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
 import kotlin.jvm.*
 
 @Suppress("UNCHECKED_CAST")
@@ -17,22 +18,37 @@
     value: T,
     ifPolymorphic: (String) -> Unit
 ) {
-    if (serializer !is AbstractPolymorphicSerializer<*> || json.configuration.useArrayPolymorphism) {
+    if (json.configuration.useArrayPolymorphism) {
         serializer.serialize(this, value)
         return
     }
-    val casted = serializer as AbstractPolymorphicSerializer<Any>
-    val baseClassDiscriminator = serializer.descriptor.classDiscriminator(json)
-    val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
-    validateIfSealed(casted, actualSerializer, baseClassDiscriminator)
-    checkKind(actualSerializer.descriptor.kind)
-    ifPolymorphic(baseClassDiscriminator)
+    val isPolymorphicSerializer = serializer is AbstractPolymorphicSerializer<*>
+    val needDiscriminator =
+        if (isPolymorphicSerializer) {
+            json.configuration.classDiscriminatorMode != ClassDiscriminatorMode.NONE
+        } else {
+            when (json.configuration.classDiscriminatorMode) {
+                ClassDiscriminatorMode.NONE, ClassDiscriminatorMode.POLYMORPHIC /* already handled in isPolymorphicSerializer */ -> false
+                ClassDiscriminatorMode.ALL_JSON_OBJECTS -> serializer.descriptor.kind.let { it == StructureKind.CLASS || it == StructureKind.OBJECT }
+            }
+        }
+    val baseClassDiscriminator = if (needDiscriminator) serializer.descriptor.classDiscriminator(json) else null
+    val actualSerializer: SerializationStrategy<T> = if (isPolymorphicSerializer) {
+        val casted = serializer as AbstractPolymorphicSerializer<Any>
+        requireNotNull(value) { "Value for serializer ${serializer.descriptor} should always be non-null. Please report issue to the kotlinx.serialization tracker." }
+        val actual = casted.findPolymorphicSerializer(this, value)
+        if (baseClassDiscriminator != null) validateIfSealed(serializer, actual, baseClassDiscriminator)
+        checkKind(actual.descriptor.kind)
+        actual as SerializationStrategy<T>
+    } else serializer
+
+    if (baseClassDiscriminator != null) ifPolymorphic(baseClassDiscriminator)
     actualSerializer.serialize(this, value)
 }
 
 private fun validateIfSealed(
     serializer: SerializationStrategy<*>,
-    actualSerializer: SerializationStrategy<Any>,
+    actualSerializer: SerializationStrategy<*>,
     classDiscriminator: String
 ) {
     if (serializer !is SealedClassSerializer<*>) return
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
index 0018fce..caa1f4a 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
@@ -213,7 +213,7 @@
      * Checks whether JSON has `null` value for non-null property or unknown enum value for enum property
      */
     private fun coerceInputValue(descriptor: SerialDescriptor, index: Int): Boolean = json.tryCoerceValue(
-        descriptor.getElementDescriptor(index),
+        descriptor, index,
         { lexer.tryConsumeNull(it) },
         { lexer.peekString(configuration.isLenient) },
         { lexer.consumeString() /* skip unknown enum string*/ }
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
index 4f7b1ec..cf562de 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
@@ -96,7 +96,7 @@
     override fun endStructure(descriptor: SerialDescriptor) {
         if (mode.end != INVALID) {
             composer.unIndent()
-            composer.nextItem()
+            composer.nextItemIfNotFirst()
             composer.print(mode.end)
         }
     }
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
index aedfb95..690b35e 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
@@ -190,7 +190,7 @@
      */
     private fun coerceInputValue(descriptor: SerialDescriptor, index: Int, tag: String): Boolean =
         json.tryCoerceValue(
-            descriptor.getElementDescriptor(index),
+            descriptor, index,
             { currentElement(tag) is JsonNull },
             { (currentElement(tag) as? JsonPrimitive)?.contentOrNull }
         )
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt
index c83bdef..f90ee1a 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt
@@ -11,7 +11,7 @@
 import kotlin.math.*
 
 internal const val lenientHint = "Use 'isLenient = true' in 'Json {}' builder to accept non-compliant JSON."
-internal const val coerceInputValuesHint = "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls to default values."
+internal const val coerceInputValuesHint = "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value."
 internal const val specialFlowingValuesHint =
     "It is possible to deserialize them using 'JsonBuilder.allowSpecialFloatingPointValues = true'"
 internal const val ignoreUnknownKeysHint = "Use 'ignoreUnknownKeys = true' in 'Json {}' builder to ignore unknown keys."
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicDecoders.kt b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicDecoders.kt
index 86c7a85..1ff1e40 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicDecoders.kt
+++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicDecoders.kt
@@ -73,7 +73,7 @@
 
     private fun coerceInputValue(descriptor: SerialDescriptor, index: Int, tag: String): Boolean =
         json.tryCoerceValue(
-            descriptor.getElementDescriptor(index),
+            descriptor, index,
             { getByTag(tag) == null },
             { getByTag(tag) as? String }
         )
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt
index b22df62..4f4ca9c 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt
@@ -216,29 +216,34 @@
         val messageDescriptor = messageType.descriptor
 
         val fieldDescriptor = messageDescriptor.getElementDescriptor(index)
+        var unwrappedFieldDescriptor = fieldDescriptor
+        while (unwrappedFieldDescriptor.isInline) {
+            unwrappedFieldDescriptor = unwrappedFieldDescriptor.getElementDescriptor(0)
+        }
+
         val nestedTypes: List<TypeDefinition>
         val typeName: String = when {
             messageDescriptor.isSealedPolymorphic && index == 1 -> {
                 appendLine("  // decoded as message with one of these types:")
-                nestedTypes = fieldDescriptor.elementDescriptors.map { TypeDefinition(it) }.toList()
+                nestedTypes = unwrappedFieldDescriptor.elementDescriptors.map { TypeDefinition(it) }.toList()
                 nestedTypes.forEachIndexed { _, childType ->
                     append("  //   message ").append(childType.descriptor.messageOrEnumName).append(", serial name '")
                         .append(removeLineBreaks(childType.descriptor.serialName)).appendLine('\'')
                 }
-                fieldDescriptor.scalarTypeName()
+                unwrappedFieldDescriptor.scalarTypeName()
             }
-            fieldDescriptor.isProtobufScalar -> {
+            unwrappedFieldDescriptor.isProtobufScalar -> {
                 nestedTypes = emptyList()
-                fieldDescriptor.scalarTypeName(messageDescriptor.getElementAnnotations(index))
+                unwrappedFieldDescriptor.scalarTypeName(messageDescriptor.getElementAnnotations(index))
             }
-            fieldDescriptor.isOpenPolymorphic -> {
+            unwrappedFieldDescriptor.isOpenPolymorphic -> {
                 nestedTypes = listOf(SyntheticPolymorphicType)
                 SyntheticPolymorphicType.descriptor.serialName
             }
             else -> {
                 // enum or regular message
-                nestedTypes = listOf(TypeDefinition(fieldDescriptor))
-                fieldDescriptor.messageOrEnumName
+                nestedTypes = listOf(TypeDefinition(unwrappedFieldDescriptor))
+                unwrappedFieldDescriptor.messageOrEnumName
             }
         }
 
diff --git a/formats/protobuf/jvmTest/resources/OptionalClass.proto b/formats/protobuf/jvmTest/resources/OptionalClass.proto
index 41fdba7..68fda00 100644
--- a/formats/protobuf/jvmTest/resources/OptionalClass.proto
+++ b/formats/protobuf/jvmTest/resources/OptionalClass.proto
@@ -5,9 +5,21 @@
 // serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionalClass'
 message OptionalClass {
   required int32 requiredInt = 1;
+  required int32 requiredUInt = 2;
+  required int32 requiredWrappedUInt = 3;
   // WARNING: a default value decoded when value is missing
-  optional int32 optionalInt = 2;
-  optional int32 nullableInt = 3;
+  optional int32 optionalInt = 4;
   // WARNING: a default value decoded when value is missing
-  optional int32 nullableOptionalInt = 4;
+  optional int32 optionalUInt = 5;
+  // WARNING: a default value decoded when value is missing
+  optional int32 optionalWrappedUInt = 6;
+  optional int32 nullableInt = 7;
+  optional int32 nullableUInt = 8;
+  optional int32 nullableWrappedUInt = 9;
+  // WARNING: a default value decoded when value is missing
+  optional int32 nullableOptionalInt = 10;
+  // WARNING: a default value decoded when value is missing
+  optional int32 nullableOptionalUInt = 11;
+  // WARNING: a default value decoded when value is missing
+  optional int32 nullableOptionalWrappedUInt = 12;
 }
diff --git a/formats/protobuf/jvmTest/resources/common/schema.proto b/formats/protobuf/jvmTest/resources/common/schema.proto
index 79e2d79..44b5a18 100644
--- a/formats/protobuf/jvmTest/resources/common/schema.proto
+++ b/formats/protobuf/jvmTest/resources/common/schema.proto
@@ -63,11 +63,23 @@
 // serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.OptionalClass'
 message OptionalClass {
   required int32 requiredInt = 1;
+  required int32 requiredUInt = 2;
+  required int32 requiredWrappedUInt = 3;
   // WARNING: a default value decoded when value is missing
-  optional int32 optionalInt = 2;
-  optional int32 nullableInt = 3;
+  optional int32 optionalInt = 4;
   // WARNING: a default value decoded when value is missing
-  optional int32 nullableOptionalInt = 4;
+  optional int32 optionalUInt = 5;
+  // WARNING: a default value decoded when value is missing
+  optional int32 optionalWrappedUInt = 6;
+  optional int32 nullableInt = 7;
+  optional int32 nullableUInt = 8;
+  optional int32 nullableWrappedUInt = 9;
+  // WARNING: a default value decoded when value is missing
+  optional int32 nullableOptionalInt = 10;
+  // WARNING: a default value decoded when value is missing
+  optional int32 nullableOptionalUInt = 11;
+  // WARNING: a default value decoded when value is missing
+  optional int32 nullableOptionalWrappedUInt = 12;
 }
 
 // serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.ContextualHolder'
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt
index 61a2dce..f2a4423 100644
--- a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt
@@ -61,7 +61,7 @@
         @ProtoNumber(5)
         val b: Int,
         @ProtoNumber(3)
-        val c: Int
+        val c: UInt,
     )
 
     @Serializable
@@ -84,6 +84,10 @@
     @Serializable
     data class OptionsClass(val i: Int)
 
+    @JvmInline
+    @Serializable
+    value class WrappedUInt(val i : UInt)
+
     @Serializable
     class ListClass(
         val intList: List<Int>,
@@ -113,9 +117,17 @@
     @Serializable
     data class OptionalClass(
         val requiredInt: Int,
+        val requiredUInt: UInt,
+        val requiredWrappedUInt: WrappedUInt,
         val optionalInt: Int = 5,
+        val optionalUInt: UInt = 5U,
+        val optionalWrappedUInt: WrappedUInt = WrappedUInt(5U),
         val nullableInt: Int?,
-        val nullableOptionalInt: Int? = 10
+        val nullableUInt: UInt?,
+        val nullableWrappedUInt: WrappedUInt?,
+        val nullableOptionalInt: Int? = 10,
+        val nullableOptionalUInt: UInt? = 10U,
+        val nullableOptionalWrappedUInt: WrappedUInt? = WrappedUInt(10U),
     )
 
     @Serializable
diff --git a/gradle/configure-source-sets.gradle b/gradle/configure-source-sets.gradle
index 740a475..f744b17 100644
--- a/gradle/configure-source-sets.gradle
+++ b/gradle/configure-source-sets.gradle
@@ -42,7 +42,6 @@
             kotlinOptions {
                 sourceMap = true
                 moduleKind = "umd"
-                metaInfo = true
             }
         }
     }
diff --git a/gradle/native-targets.gradle b/gradle/native-targets.gradle
index 373aeba..8ef7f48 100644
--- a/gradle/native-targets.gradle
+++ b/gradle/native-targets.gradle
@@ -11,13 +11,14 @@
 
         // According to https://kotlinlang.org/docs/native-target-support.html
         // Tier 1
-        linuxX64()
         macosX64()
         macosArm64()
         iosSimulatorArm64()
         iosX64()
 
         // Tier 2
+        linuxX64()
+        linuxArm64()
         watchosSimulatorArm64()
         watchosX64()
         watchosArm32()
@@ -26,7 +27,6 @@
         tvosX64()
         tvosArm64()
         iosArm64()
-        linuxArm64()
 
         // Tier 3
         mingwX64()
diff --git a/guide/example/example-json-12.kt b/guide/example/example-json-12.kt
index 1a37516..99a872b 100644
--- a/guide/example/example-json-12.kt
+++ b/guide/example/example-json-12.kt
@@ -4,13 +4,17 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-val format = Json { decodeEnumsCaseInsensitive = true }
-
-enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B }
+val format = Json { classDiscriminatorMode = ClassDiscriminatorMode.NONE }
 
 @Serializable
-data class CasesList(val cases: List<Cases>)
+sealed class Project {
+    abstract val name: String
+}
+
+@Serializable
+class OwnedProject(override val name: String, val owner: String) : Project()
 
 fun main() {
-  println(format.decodeFromString<CasesList>("""{"cases":["value_A", "alternative"]}""")) 
+    val data: Project = OwnedProject("kotlinx.coroutines", "kotlin")
+    println(format.encodeToString(data))
 }
diff --git a/guide/example/example-json-13.kt b/guide/example/example-json-13.kt
index cd7cf7f..e20afe2 100644
--- a/guide/example/example-json-13.kt
+++ b/guide/example/example-json-13.kt
@@ -4,12 +4,13 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-@Serializable
-data class Project(val projectName: String, val projectOwner: String)
+val format = Json { decodeEnumsCaseInsensitive = true }
 
-val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
+enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B }
+
+@Serializable
+data class CasesList(val cases: List<Cases>)
 
 fun main() {
-    val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
-    println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
+  println(format.decodeFromString<CasesList>("""{"cases":["value_A", "alternative"]}""")) 
 }
diff --git a/guide/example/example-json-14.kt b/guide/example/example-json-14.kt
index 98464dc..50de55f 100644
--- a/guide/example/example-json-14.kt
+++ b/guide/example/example-json-14.kt
@@ -4,9 +4,12 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
+@Serializable
+data class Project(val projectName: String, val projectOwner: String)
+
+val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
+
 fun main() {
-    val element = Json.parseToJsonElement("""
-        {"name":"kotlinx.serialization","language":"Kotlin"}
-    """)
-    println(element)
+    val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
+    println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
 }
diff --git a/guide/example/example-json-15.kt b/guide/example/example-json-15.kt
index 72fd23e..384ae41 100644
--- a/guide/example/example-json-15.kt
+++ b/guide/example/example-json-15.kt
@@ -6,13 +6,7 @@
 
 fun main() {
     val element = Json.parseToJsonElement("""
-        {
-            "name": "kotlinx.serialization",
-            "forks": [{"votes": 42}, {"votes": 9000}, {}]
-        }
+        {"name":"kotlinx.serialization","language":"Kotlin"}
     """)
-    val sum = element
-        .jsonObject["forks"]!!
-        .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 }
-    println(sum)
+    println(element)
 }
diff --git a/guide/example/example-json-16.kt b/guide/example/example-json-16.kt
index cff8ec7..fff287a 100644
--- a/guide/example/example-json-16.kt
+++ b/guide/example/example-json-16.kt
@@ -5,19 +5,14 @@
 import kotlinx.serialization.json.*
 
 fun main() {
-    val element = buildJsonObject {
-        put("name", "kotlinx.serialization")
-        putJsonObject("owner") {
-            put("name", "kotlin")
+    val element = Json.parseToJsonElement("""
+        {
+            "name": "kotlinx.serialization",
+            "forks": [{"votes": 42}, {"votes": 9000}, {}]
         }
-        putJsonArray("forks") {
-            addJsonObject {
-                put("votes", 42)
-            }
-            addJsonObject {
-                put("votes", 9000)
-            }
-        }
-    }
-    println(element)
+    """)
+    val sum = element
+        .jsonObject["forks"]!!
+        .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 }
+    println(sum)
 }
diff --git a/guide/example/example-json-17.kt b/guide/example/example-json-17.kt
index 25be758..72a696a 100644
--- a/guide/example/example-json-17.kt
+++ b/guide/example/example-json-17.kt
@@ -4,14 +4,20 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-@Serializable
-data class Project(val name: String, val language: String)
-
 fun main() {
     val element = buildJsonObject {
         put("name", "kotlinx.serialization")
-        put("language", "Kotlin")
+        putJsonObject("owner") {
+            put("name", "kotlin")
+        }
+        putJsonArray("forks") {
+            addJsonObject {
+                put("votes", 42)
+            }
+            addJsonObject {
+                put("votes", 9000)
+            }
+        }
     }
-    val data = Json.decodeFromJsonElement<Project>(element)
-    println(data)
+    println(element)
 }
diff --git a/guide/example/example-json-18.kt b/guide/example/example-json-18.kt
index 2a1add4..1b655bf 100644
--- a/guide/example/example-json-18.kt
+++ b/guide/example/example-json-18.kt
@@ -4,20 +4,14 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-import java.math.BigDecimal
-
-val format = Json { prettyPrint = true }
+@Serializable
+data class Project(val name: String, val language: String)
 
 fun main() {
-    val pi = BigDecimal("3.141592653589793238462643383279")
-    
-    val piJsonDouble = JsonPrimitive(pi.toDouble())
-    val piJsonString = JsonPrimitive(pi.toString())
-  
-    val piObject = buildJsonObject {
-        put("pi_double", piJsonDouble)
-        put("pi_string", piJsonString)
+    val element = buildJsonObject {
+        put("name", "kotlinx.serialization")
+        put("language", "Kotlin")
     }
-
-    println(format.encodeToString(piObject))
+    val data = Json.decodeFromJsonElement<Project>(element)
+    println(data)
 }
diff --git a/guide/example/example-json-19.kt b/guide/example/example-json-19.kt
index d59bf26..b001c55 100644
--- a/guide/example/example-json-19.kt
+++ b/guide/example/example-json-19.kt
@@ -10,15 +10,11 @@
 
 fun main() {
     val pi = BigDecimal("3.141592653589793238462643383279")
-
-    // use JsonUnquotedLiteral to encode raw JSON content
-    val piJsonLiteral = JsonUnquotedLiteral(pi.toString())
-
+    
     val piJsonDouble = JsonPrimitive(pi.toDouble())
     val piJsonString = JsonPrimitive(pi.toString())
   
     val piObject = buildJsonObject {
-        put("pi_literal", piJsonLiteral)
         put("pi_double", piJsonDouble)
         put("pi_string", piJsonString)
     }
diff --git a/guide/example/example-json-20.kt b/guide/example/example-json-20.kt
index 2f481da..f522b3f 100644
--- a/guide/example/example-json-20.kt
+++ b/guide/example/example-json-20.kt
@@ -6,18 +6,22 @@
 
 import java.math.BigDecimal
 
+val format = Json { prettyPrint = true }
+
 fun main() {
-    val piObjectJson = """
-          {
-              "pi_literal": 3.141592653589793238462643383279
-          }
-      """.trimIndent()
-    
-    val piObject: JsonObject = Json.decodeFromString(piObjectJson)
-    
-    val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content
-    
-    val pi = BigDecimal(piJsonLiteral)
-    
-    println(pi)
+    val pi = BigDecimal("3.141592653589793238462643383279")
+
+    // use JsonUnquotedLiteral to encode raw JSON content
+    val piJsonLiteral = JsonUnquotedLiteral(pi.toString())
+
+    val piJsonDouble = JsonPrimitive(pi.toDouble())
+    val piJsonString = JsonPrimitive(pi.toString())
+  
+    val piObject = buildJsonObject {
+        put("pi_literal", piJsonLiteral)
+        put("pi_double", piJsonDouble)
+        put("pi_string", piJsonString)
+    }
+
+    println(format.encodeToString(piObject))
 }
diff --git a/guide/example/example-json-21.kt b/guide/example/example-json-21.kt
index 86a4b73..efd6071 100644
--- a/guide/example/example-json-21.kt
+++ b/guide/example/example-json-21.kt
@@ -4,7 +4,20 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
+import java.math.BigDecimal
+
 fun main() {
-    // caution: creating null with JsonUnquotedLiteral will cause an exception! 
-    JsonUnquotedLiteral("null")
+    val piObjectJson = """
+          {
+              "pi_literal": 3.141592653589793238462643383279
+          }
+      """.trimIndent()
+    
+    val piObject: JsonObject = Json.decodeFromString(piObjectJson)
+    
+    val piJsonLiteral = piObject["pi_literal"]!!.jsonPrimitive.content
+    
+    val pi = BigDecimal(piJsonLiteral)
+    
+    println(pi)
 }
diff --git a/guide/example/example-json-22.kt b/guide/example/example-json-22.kt
index 84bd0d8..e64ab06 100644
--- a/guide/example/example-json-22.kt
+++ b/guide/example/example-json-22.kt
@@ -4,29 +4,7 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-import kotlinx.serialization.builtins.*
-
-@Serializable
-data class Project(
-    val name: String,
-    @Serializable(with = UserListSerializer::class)
-    val users: List<User>
-)
-
-@Serializable
-data class User(val name: String)
-
-object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {
-    // If response is not an array, then it is a single object that should be wrapped into the array
-    override fun transformDeserialize(element: JsonElement): JsonElement =
-        if (element !is JsonArray) JsonArray(listOf(element)) else element
-}
-
 fun main() {
-    println(Json.decodeFromString<Project>("""
-        {"name":"kotlinx.serialization","users":{"name":"kotlin"}}
-    """))
-    println(Json.decodeFromString<Project>("""
-        {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]}
-    """))
+    // caution: creating null with JsonUnquotedLiteral will cause an exception! 
+    JsonUnquotedLiteral("null")
 }
diff --git a/guide/example/example-json-23.kt b/guide/example/example-json-23.kt
index bb23f52..ffa9f7d 100644
--- a/guide/example/example-json-23.kt
+++ b/guide/example/example-json-23.kt
@@ -17,14 +17,16 @@
 data class User(val name: String)
 
 object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {
-
-    override fun transformSerialize(element: JsonElement): JsonElement {
-        require(element is JsonArray) // this serializer is used only with lists
-        return element.singleOrNull() ?: element
-    }
+    // If response is not an array, then it is a single object that should be wrapped into the array
+    override fun transformDeserialize(element: JsonElement): JsonElement =
+        if (element !is JsonArray) JsonArray(listOf(element)) else element
 }
 
 fun main() {
-    val data = Project("kotlinx.serialization", listOf(User("kotlin")))
-    println(Json.encodeToString(data))
+    println(Json.decodeFromString<Project>("""
+        {"name":"kotlinx.serialization","users":{"name":"kotlin"}}
+    """))
+    println(Json.decodeFromString<Project>("""
+        {"name":"kotlinx.serialization","users":[{"name":"kotlin"},{"name":"jetbrains"}]}
+    """))
 }
diff --git a/guide/example/example-json-24.kt b/guide/example/example-json-24.kt
index def90f2..010bd27 100644
--- a/guide/example/example-json-24.kt
+++ b/guide/example/example-json-24.kt
@@ -4,19 +4,27 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-@Serializable
-class Project(val name: String, val language: String)
+import kotlinx.serialization.builtins.*
 
-object ProjectSerializer : JsonTransformingSerializer<Project>(Project.serializer()) {
-    override fun transformSerialize(element: JsonElement): JsonElement =
-        // Filter out top-level key value pair with the key "language" and the value "Kotlin"
-        JsonObject(element.jsonObject.filterNot {
-            (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin"
-        })
+@Serializable
+data class Project(
+    val name: String,
+    @Serializable(with = UserListSerializer::class)
+    val users: List<User>
+)
+
+@Serializable
+data class User(val name: String)
+
+object UserListSerializer : JsonTransformingSerializer<List<User>>(ListSerializer(User.serializer())) {
+
+    override fun transformSerialize(element: JsonElement): JsonElement {
+        require(element is JsonArray) // this serializer is used only with lists
+        return element.singleOrNull() ?: element
+    }
 }
 
 fun main() {
-    val data = Project("kotlinx.serialization", "Kotlin")
-    println(Json.encodeToString(data)) // using plugin-generated serializer
-    println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer
+    val data = Project("kotlinx.serialization", listOf(User("kotlin")))
+    println(Json.encodeToString(data))
 }
diff --git a/guide/example/example-json-25.kt b/guide/example/example-json-25.kt
index 6f6d67a..a7d19a7 100644
--- a/guide/example/example-json-25.kt
+++ b/guide/example/example-json-25.kt
@@ -4,33 +4,19 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-import kotlinx.serialization.builtins.*
-
 @Serializable
-abstract class Project {
-    abstract val name: String
-}
+class Project(val name: String, val language: String)
 
-@Serializable
-data class BasicProject(override val name: String): Project()
-
-
-@Serializable
-data class OwnedProject(override val name: String, val owner: String) : Project()
-
-object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
-    override fun selectDeserializer(element: JsonElement) = when {
-        "owner" in element.jsonObject -> OwnedProject.serializer()
-        else -> BasicProject.serializer()
-    }
+object ProjectSerializer : JsonTransformingSerializer<Project>(Project.serializer()) {
+    override fun transformSerialize(element: JsonElement): JsonElement =
+        // Filter out top-level key value pair with the key "language" and the value "Kotlin"
+        JsonObject(element.jsonObject.filterNot {
+            (k, v) -> k == "language" && v.jsonPrimitive.content == "Kotlin"
+        })
 }
 
 fun main() {
-    val data = listOf(
-        OwnedProject("kotlinx.serialization", "kotlin"),
-        BasicProject("example")
-    )
-    val string = Json.encodeToString(ListSerializer(ProjectSerializer), data)
-    println(string)
-    println(Json.decodeFromString(ListSerializer(ProjectSerializer), string))
+    val data = Project("kotlinx.serialization", "Kotlin")
+    println(Json.encodeToString(data)) // using plugin-generated serializer
+    println(Json.encodeToString(ProjectSerializer, data)) // using custom serializer
 }
diff --git a/guide/example/example-json-26.kt b/guide/example/example-json-26.kt
index c308b63..b1b9299 100644
--- a/guide/example/example-json-26.kt
+++ b/guide/example/example-json-26.kt
@@ -4,56 +4,33 @@
 import kotlinx.serialization.*
 import kotlinx.serialization.json.*
 
-import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.encoding.*
+import kotlinx.serialization.builtins.*
 
-@Serializable(with = ResponseSerializer::class)
-sealed class Response<out T> {
-    data class Ok<out T>(val data: T) : Response<T>()
-    data class Error(val message: String) : Response<Nothing>()
-}
-
-class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Response<T>> {
-    override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) {
-        element("Ok", dataSerializer.descriptor)
-        element("Error", buildClassSerialDescriptor("Error") {
-          element<String>("message")
-        })
-    }
-
-    override fun deserialize(decoder: Decoder): Response<T> {
-        // Decoder -> JsonDecoder
-        require(decoder is JsonDecoder) // this class can be decoded only by Json
-        // JsonDecoder -> JsonElement
-        val element = decoder.decodeJsonElement()
-        // JsonElement -> value
-        if (element is JsonObject && "error" in element)
-            return Response.Error(element["error"]!!.jsonPrimitive.content)
-        return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element))
-    }
-
-    override fun serialize(encoder: Encoder, value: Response<T>) {
-        // Encoder -> JsonEncoder
-        require(encoder is JsonEncoder) // This class can be encoded only by Json
-        // value -> JsonElement
-        val element = when (value) {
-            is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data)
-            is Response.Error -> buildJsonObject { put("error", value.message) }
-        }
-        // JsonElement -> JsonEncoder
-        encoder.encodeJsonElement(element)
-    }
+@Serializable
+abstract class Project {
+    abstract val name: String
 }
 
 @Serializable
-data class Project(val name: String)
+data class BasicProject(override val name: String): Project()
+
+
+@Serializable
+data class OwnedProject(override val name: String, val owner: String) : Project()
+
+object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
+    override fun selectDeserializer(element: JsonElement) = when {
+        "owner" in element.jsonObject -> OwnedProject.serializer()
+        else -> BasicProject.serializer()
+    }
+}
 
 fun main() {
-    val responses = listOf(
-        Response.Ok(Project("kotlinx.serialization")),
-        Response.Error("Not found")
+    val data = listOf(
+        OwnedProject("kotlinx.serialization", "kotlin"),
+        BasicProject("example")
     )
-    val string = Json.encodeToString(responses)
+    val string = Json.encodeToString(ListSerializer(ProjectSerializer), data)
     println(string)
-    println(Json.decodeFromString<List<Response<Project>>>(string))
+    println(Json.decodeFromString(ListSerializer(ProjectSerializer), string))
 }
diff --git a/guide/example/example-json-27.kt b/guide/example/example-json-27.kt
index 219de6e..5905733 100644
--- a/guide/example/example-json-27.kt
+++ b/guide/example/example-json-27.kt
@@ -7,31 +7,53 @@
 import kotlinx.serialization.descriptors.*
 import kotlinx.serialization.encoding.*
 
-data class UnknownProject(val name: String, val details: JsonObject)
+@Serializable(with = ResponseSerializer::class)
+sealed class Response<out T> {
+    data class Ok<out T>(val data: T) : Response<T>()
+    data class Error(val message: String) : Response<Nothing>()
+}
 
-object UnknownProjectSerializer : KSerializer<UnknownProject> {
-    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") {
-        element<String>("name")
-        element<JsonElement>("details")
+class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Response<T>> {
+    override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) {
+        element("Ok", dataSerializer.descriptor)
+        element("Error", buildClassSerialDescriptor("Error") {
+          element<String>("message")
+        })
     }
 
-    override fun deserialize(decoder: Decoder): UnknownProject {
-        // Cast to JSON-specific interface
-        val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON")
-        // Read the whole content as JSON
-        val json = jsonInput.decodeJsonElement().jsonObject
-        // Extract and remove name property
-        val name = json.getValue("name").jsonPrimitive.content
-        val details = json.toMutableMap()
-        details.remove("name")
-        return UnknownProject(name, JsonObject(details))
+    override fun deserialize(decoder: Decoder): Response<T> {
+        // Decoder -> JsonDecoder
+        require(decoder is JsonDecoder) // this class can be decoded only by Json
+        // JsonDecoder -> JsonElement
+        val element = decoder.decodeJsonElement()
+        // JsonElement -> value
+        if (element is JsonObject && "error" in element)
+            return Response.Error(element["error"]!!.jsonPrimitive.content)
+        return Response.Ok(decoder.json.decodeFromJsonElement(dataSerializer, element))
     }
 
-    override fun serialize(encoder: Encoder, value: UnknownProject) {
-        error("Serialization is not supported")
+    override fun serialize(encoder: Encoder, value: Response<T>) {
+        // Encoder -> JsonEncoder
+        require(encoder is JsonEncoder) // This class can be encoded only by Json
+        // value -> JsonElement
+        val element = when (value) {
+            is Response.Ok -> encoder.json.encodeToJsonElement(dataSerializer, value.data)
+            is Response.Error -> buildJsonObject { put("error", value.message) }
+        }
+        // JsonElement -> JsonEncoder
+        encoder.encodeJsonElement(element)
     }
 }
 
+@Serializable
+data class Project(val name: String)
+
 fun main() {
-    println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}"""))
+    val responses = listOf(
+        Response.Ok(Project("kotlinx.serialization")),
+        Response.Error("Not found")
+    )
+    val string = Json.encodeToString(responses)
+    println(string)
+    println(Json.decodeFromString<List<Response<Project>>>(string))
 }
diff --git a/guide/example/example-json-28.kt b/guide/example/example-json-28.kt
new file mode 100644
index 0000000..a3fab61
--- /dev/null
+++ b/guide/example/example-json-28.kt
@@ -0,0 +1,37 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson28
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+
+data class UnknownProject(val name: String, val details: JsonObject)
+
+object UnknownProjectSerializer : KSerializer<UnknownProject> {
+    override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UnknownProject") {
+        element<String>("name")
+        element<JsonElement>("details")
+    }
+
+    override fun deserialize(decoder: Decoder): UnknownProject {
+        // Cast to JSON-specific interface
+        val jsonInput = decoder as? JsonDecoder ?: error("Can be deserialized only by JSON")
+        // Read the whole content as JSON
+        val json = jsonInput.decodeJsonElement().jsonObject
+        // Extract and remove name property
+        val name = json.getValue("name").jsonPrimitive.content
+        val details = json.toMutableMap()
+        details.remove("name")
+        return UnknownProject(name, JsonObject(details))
+    }
+
+    override fun serialize(encoder: Encoder, value: UnknownProject) {
+        error("Serialization is not supported")
+    }
+}
+
+fun main() {
+    println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}"""))
+}
diff --git a/guide/test/BasicSerializationTest.kt b/guide/test/BasicSerializationTest.kt
index dc89feb..11f9e9f 100644
--- a/guide/test/BasicSerializationTest.kt
+++ b/guide/test/BasicSerializationTest.kt
@@ -110,7 +110,7 @@
     fun testExampleClasses12() {
         captureOutput("ExampleClasses12") { example.exampleClasses12.main() }.verifyOutputLinesStart(
             "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 52: Expected string literal but 'null' literal was found at path: $.language",
-            "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls to default values."
+            "Use 'coerceInputValues = true' in 'Json {}' builder to coerce nulls if property has a default value."
         )
     }
 
diff --git a/guide/test/JsonTest.kt b/guide/test/JsonTest.kt
index 115ef77..0c5ed85 100644
--- a/guide/test/JsonTest.kt
+++ b/guide/test/JsonTest.kt
@@ -90,52 +90,49 @@
     @Test
     fun testExampleJson12() {
         captureOutput("ExampleJson12") { example.exampleJson12.main() }.verifyOutputLines(
-            "CasesList(cases=[VALUE_A, VALUE_B])"
+            "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}"
         )
     }
 
     @Test
     fun testExampleJson13() {
         captureOutput("ExampleJson13") { example.exampleJson13.main() }.verifyOutputLines(
-            "{\"project_name\":\"kotlinx.serialization\",\"project_owner\":\"Kotlin\"}"
+            "CasesList(cases=[VALUE_A, VALUE_B])"
         )
     }
 
     @Test
     fun testExampleJson14() {
         captureOutput("ExampleJson14") { example.exampleJson14.main() }.verifyOutputLines(
-            "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
+            "{\"project_name\":\"kotlinx.serialization\",\"project_owner\":\"Kotlin\"}"
         )
     }
 
     @Test
     fun testExampleJson15() {
         captureOutput("ExampleJson15") { example.exampleJson15.main() }.verifyOutputLines(
-            "9042"
+            "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
         )
     }
 
     @Test
     fun testExampleJson16() {
         captureOutput("ExampleJson16") { example.exampleJson16.main() }.verifyOutputLines(
-            "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}"
+            "9042"
         )
     }
 
     @Test
     fun testExampleJson17() {
         captureOutput("ExampleJson17") { example.exampleJson17.main() }.verifyOutputLines(
-            "Project(name=kotlinx.serialization, language=Kotlin)"
+            "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}"
         )
     }
 
     @Test
     fun testExampleJson18() {
         captureOutput("ExampleJson18") { example.exampleJson18.main() }.verifyOutputLines(
-            "{",
-            "    \"pi_double\": 3.141592653589793,",
-            "    \"pi_string\": \"3.141592653589793238462643383279\"",
-            "}"
+            "Project(name=kotlinx.serialization, language=Kotlin)"
         )
     }
 
@@ -143,7 +140,6 @@
     fun testExampleJson19() {
         captureOutput("ExampleJson19") { example.exampleJson19.main() }.verifyOutputLines(
             "{",
-            "    \"pi_literal\": 3.141592653589793238462643383279,",
             "    \"pi_double\": 3.141592653589793,",
             "    \"pi_string\": \"3.141592653589793238462643383279\"",
             "}"
@@ -153,59 +149,70 @@
     @Test
     fun testExampleJson20() {
         captureOutput("ExampleJson20") { example.exampleJson20.main() }.verifyOutputLines(
-            "3.141592653589793238462643383279"
+            "{",
+            "    \"pi_literal\": 3.141592653589793238462643383279,",
+            "    \"pi_double\": 3.141592653589793,",
+            "    \"pi_string\": \"3.141592653589793238462643383279\"",
+            "}"
         )
     }
 
     @Test
     fun testExampleJson21() {
-        captureOutput("ExampleJson21") { example.exampleJson21.main() }.verifyOutputLinesStart(
-            "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive"
+        captureOutput("ExampleJson21") { example.exampleJson21.main() }.verifyOutputLines(
+            "3.141592653589793238462643383279"
         )
     }
 
     @Test
     fun testExampleJson22() {
-        captureOutput("ExampleJson22") { example.exampleJson22.main() }.verifyOutputLines(
-            "Project(name=kotlinx.serialization, users=[User(name=kotlin)])",
-            "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])"
+        captureOutput("ExampleJson22") { example.exampleJson22.main() }.verifyOutputLinesStart(
+            "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonEncodingException: Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive"
         )
     }
 
     @Test
     fun testExampleJson23() {
         captureOutput("ExampleJson23") { example.exampleJson23.main() }.verifyOutputLines(
-            "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}"
+            "Project(name=kotlinx.serialization, users=[User(name=kotlin)])",
+            "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])"
         )
     }
 
     @Test
     fun testExampleJson24() {
         captureOutput("ExampleJson24") { example.exampleJson24.main() }.verifyOutputLines(
-            "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}",
-            "{\"name\":\"kotlinx.serialization\"}"
+            "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}"
         )
     }
 
     @Test
     fun testExampleJson25() {
         captureOutput("ExampleJson25") { example.exampleJson25.main() }.verifyOutputLines(
-            "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]",
-            "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]"
+            "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}",
+            "{\"name\":\"kotlinx.serialization\"}"
         )
     }
 
     @Test
     fun testExampleJson26() {
         captureOutput("ExampleJson26") { example.exampleJson26.main() }.verifyOutputLines(
-            "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]",
-            "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]"
+            "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]",
+            "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]"
         )
     }
 
     @Test
     fun testExampleJson27() {
         captureOutput("ExampleJson27") { example.exampleJson27.main() }.verifyOutputLines(
+            "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]",
+            "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]"
+        )
+    }
+
+    @Test
+    fun testExampleJson28() {
+        captureOutput("ExampleJson28") { example.exampleJson28.main() }.verifyOutputLines(
             "UnknownProject(name=example, details={\"type\":\"unknown\",\"maintainer\":\"Unknown\",\"license\":\"Apache 2.0\"})"
         )
     }
diff --git a/integration-test/build.gradle b/integration-test/build.gradle
index 00237a7..6c4e700 100644
--- a/integration-test/build.gradle
+++ b/integration-test/build.gradle
@@ -45,7 +45,6 @@
             kotlinOptions {
                 sourceMap = true
                 moduleKind = "umd"
-                metaInfo = true
             }
         }
     }