Skip to content

Commit

Permalink
Make JsonConfiguration public and expose it for custom serializers (#…
Browse files Browse the repository at this point in the history
…1409)

* Make JsonConfiguration public and expose it for custom serializers

Fixes #1361
  • Loading branch information
qwwdfsad committed Apr 15, 2021
1 parent 436cf83 commit 59d3216
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 53 deletions.
19 changes: 18 additions & 1 deletion formats/json/api/kotlinx-serialization-json.api
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
public abstract class kotlinx/serialization/json/Json : kotlinx/serialization/StringFormat {
public static final field Default Lkotlinx/serialization/json/Json$Default;
public synthetic fun <init> (Lkotlinx/serialization/json/internal/JsonConfiguration;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Lkotlinx/serialization/json/JsonConfiguration;Lkotlinx/serialization/modules/SerializersModule;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun decodeFromJsonElement (Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/JsonElement;)Ljava/lang/Object;
public final fun decodeFromString (Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/String;)Ljava/lang/Object;
public final fun encodeToJsonElement (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Lkotlinx/serialization/json/JsonElement;
public final fun encodeToString (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/lang/String;
public final fun getConfiguration ()Lkotlinx/serialization/json/JsonConfiguration;
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public final fun parseToJsonElement (Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement;
}
Expand Down Expand Up @@ -100,6 +101,22 @@ public final class kotlinx/serialization/json/JsonBuilder {
public final fun setUseArrayPolymorphism (Z)V
}

public final class kotlinx/serialization/json/JsonConfiguration {
public fun <init> ()V
public final fun getAllowSpecialFloatingPointValues ()Z
public final fun getAllowStructuredMapKeys ()Z
public final fun getClassDiscriminator ()Ljava/lang/String;
public final fun getCoerceInputValues ()Z
public final fun getEncodeDefaults ()Z
public final fun getIgnoreUnknownKeys ()Z
public final fun getPrettyPrint ()Z
public final fun getPrettyPrintIndent ()Ljava/lang/String;
public final fun getUseAlternativeNames ()Z
public final fun getUseArrayPolymorphism ()Z
public final fun isLenient ()Z
public fun toString ()Ljava/lang/String;
}

public abstract class kotlinx/serialization/json/JsonContentPolymorphicSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlin/reflect/KClass;)V
public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
Expand Down
47 changes: 25 additions & 22 deletions formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,23 @@ import kotlin.native.concurrent.*
* // Deserialize from string to JSON tree, JSON-specific
* val deserializedToTree: JsonElement = json.parseJsonElement(stringOutput)
* ```
*
* Json instance also exposes its [configuration] that can be used in custom serializers
* that rely on [JsonDecoder] and [JsonEncoder] for customizable behaviour.
*/
@OptIn(ExperimentalSerializationApi::class)
public sealed class Json(internal val configuration: JsonConfiguration) : StringFormat {

public sealed class Json(
@ExperimentalSerializationApi public val configuration: JsonConfiguration,
override val serializersModule: SerializersModule
get() = configuration.serializersModule
) : StringFormat {

internal val schemaCache: DescriptorSchemaCache = DescriptorSchemaCache()

/**
* The default instance of [Json] with default configuration.
*/
@ThreadLocal // to support caching
public companion object Default : Json(JsonConfiguration())
public companion object Default : Json(JsonConfiguration(), EmptySerializersModule)

/**
* Serializes the [value] into an equivalent JSON using the given [serializer].
Expand Down Expand Up @@ -125,10 +128,10 @@ public sealed class Json(internal val configuration: JsonConfiguration) : String
* Creates an instance of [Json] configured from the optionally given [Json instance][from] and adjusted with [builderAction].
*/
public fun Json(from: Json = Json.Default, builderAction: JsonBuilder.() -> Unit): Json {
val builder = JsonBuilder(from.configuration)
val builder = JsonBuilder(from)
builder.builderAction()
val conf = builder.build()
return JsonImpl(conf)
return JsonImpl(conf, builder.serializersModule)
}

/**
Expand All @@ -154,19 +157,20 @@ public inline fun <reified T> Json.decodeFromJsonElement(json: JsonElement): T =
* Builder of the [Json] instance provided by `Json { ... }` factory function.
*/
@Suppress("unused", "DeprecatedCallableAddReplaceWith")
public class JsonBuilder internal constructor(configuration: JsonConfiguration) {
@OptIn(ExperimentalSerializationApi::class)
public class JsonBuilder internal constructor(json: Json) {
/**
* Specifies whether default values of Kotlin properties should be encoded.
* `false` by default.
*/
public var encodeDefaults: Boolean = configuration.encodeDefaults
public var encodeDefaults: Boolean = json.configuration.encodeDefaults

/**
* Specifies whether encounters of unknown properties in the input JSON
* should be ignored instead of throwing [SerializationException].
* `false` by default.
*/
public var ignoreUnknownKeys: Boolean = configuration.ignoreUnknownKeys
public var ignoreUnknownKeys: Boolean = json.configuration.ignoreUnknownKeys

/**
* Removes JSON specification restriction (RFC-4627) and makes parser
Expand All @@ -178,20 +182,20 @@ public class JsonBuilder internal constructor(configuration: JsonConfiguration)
*
* `false` by default.
*/
public var isLenient: Boolean = configuration.isLenient
public var isLenient: Boolean = json.configuration.isLenient

/**
* Enables structured objects to be serialized as map keys by
* changing serialized form of the map from JSON object (key-value pairs) to flat array like `[k1, v1, k2, v2]`.
* `false` by default.
*/
public var allowStructuredMapKeys: Boolean = configuration.allowStructuredMapKeys
public var allowStructuredMapKeys: Boolean = json.configuration.allowStructuredMapKeys

/**
* Specifies whether resulting JSON should be pretty-printed.
* `false` by default.
*/
public var prettyPrint: Boolean = configuration.prettyPrint
public var prettyPrint: Boolean = json.configuration.prettyPrint

/**
* Specifies indent string to use with [prettyPrint] mode
Expand All @@ -200,7 +204,7 @@ public class JsonBuilder internal constructor(configuration: JsonConfiguration)
* it is not clear whether this option has compelling use-cases.
*/
@ExperimentalSerializationApi
public var prettyPrintIndent: String = configuration.prettyPrintIndent
public var prettyPrintIndent: String = json.configuration.prettyPrintIndent

/**
* Enables coercing incorrect JSON values to the default property value in the following cases:
Expand All @@ -209,28 +213,28 @@ public class JsonBuilder internal constructor(configuration: JsonConfiguration)
*
* `false` by default.
*/
public var coerceInputValues: Boolean = configuration.coerceInputValues
public var coerceInputValues: Boolean = json.configuration.coerceInputValues

/**
* 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.
*/
public var useArrayPolymorphism: Boolean = configuration.useArrayPolymorphism
public var useArrayPolymorphism: Boolean = json.configuration.useArrayPolymorphism

/**
* Name of the class descriptor property for polymorphic serialization.
* "type" by default.
*/
public var classDiscriminator: String = configuration.classDiscriminator
public var classDiscriminator: String = json.configuration.classDiscriminator

/**
* Removes JSON specification restriction on
* special floating-point values such as `NaN` and `Infinity` and enables their serialization and deserialization.
* When enabling it, please ensure that the receiving party will be able to encode and decode these special values.
* `false` by default.
*/
public var allowSpecialFloatingPointValues: Boolean = configuration.allowSpecialFloatingPointValues
public var allowSpecialFloatingPointValues: Boolean = json.configuration.allowSpecialFloatingPointValues

/**
* Specifies whether Json instance makes use of [JsonNames] annotation.
Expand All @@ -239,12 +243,12 @@ public class JsonBuilder internal constructor(configuration: JsonConfiguration)
* particularly when a large count of fields is skipped with [ignoreUnknownKeys].
* `true` by default.
*/
public var useAlternativeNames: Boolean = configuration.useAlternativeNames
public var useAlternativeNames: Boolean = json.configuration.useAlternativeNames

/**
* Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
*/
public var serializersModule: SerializersModule = configuration.serializersModule
public var serializersModule: SerializersModule = json.serializersModule

@OptIn(ExperimentalSerializationApi::class)
internal fun build(): JsonConfiguration {
Expand All @@ -268,14 +272,13 @@ public class JsonBuilder internal constructor(configuration: JsonConfiguration)
encodeDefaults, ignoreUnknownKeys, isLenient,
allowStructuredMapKeys, prettyPrint, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
serializersModule
classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames
)
}
}

@OptIn(ExperimentalSerializationApi::class)
private class JsonImpl(configuration: JsonConfiguration) : Json(configuration) {
private class JsonImpl(configuration: JsonConfiguration, module: SerializersModule) : Json(configuration, module) {

init {
validateConfiguration()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kotlinx.serialization.json

import kotlinx.serialization.*
import kotlinx.serialization.modules.*

/**
* Configuration of the current [Json] instance available through [Json.configuration]
* and configured with [JsonBuilder] constructor.
*
* Can be used for debug purposes and for custom Json-specific serializers
* via [JsonEncoder] and [JsonDecoder].
*
* Standalone configuration object is meaningless and can nor be used outside of the
* [Json], neither new [Json] instance can be created from it.
*
* Detailed description of each property is available in [JsonBuilder] class.
*/
@ExperimentalSerializationApi
public class JsonConfiguration internal constructor(
public val encodeDefaults: Boolean = false,
public val ignoreUnknownKeys: Boolean = false,
public val isLenient: Boolean = false,
public val allowStructuredMapKeys: Boolean = false,
public val prettyPrint: Boolean = false,
@ExperimentalSerializationApi
public val prettyPrintIndent: String = " ",
public val coerceInputValues: Boolean = false,
public val useArrayPolymorphism: Boolean = false,
public val classDiscriminator: String = "type",
public val allowSpecialFloatingPointValues: Boolean = false,
public val useAlternativeNames: Boolean = true
) {

/** @suppress Dokka **/
override fun toString(): String {
return "JsonConfiguration(encodeDefaults=$encodeDefaults, ignoreUnknownKeys=$ignoreUnknownKeys, isLenient=$isLenient, allowStructuredMapKeys=$allowStructuredMapKeys, prettyPrint=$prettyPrint, prettyPrintIndent='$prettyPrintIndent', coerceInputValues=$coerceInputValues, useArrayPolymorphism=$useArrayPolymorphism, classDiscriminator='$classDiscriminator', allowSpecialFloatingPointValues=$allowSpecialFloatingPointValues)"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ import kotlinx.serialization.descriptors.*
* }
*
* override fun deserialize(decoder: Decoder): Either {
* val input = decoder as? JsonDecoder ?: throw SerializationException("This class can be loaded only by Json")
* val input = decoder as? JsonDecoder ?: throw SerializationException("This class can be decoded only by Json format")
* val tree = input.decodeJsonElement() as? JsonObject ?: throw SerializationException("Expected JsonObject")
* if ("error" in tree) return Either.Left(tree["error"]!!.jsonPrimitive.content)
* return Either.Right(input.json.decodeFromJsonElement(Payload.serializer(), tree))
* }
*
* override fun serialize(encoder: Encoder, value: Either) {
* val output = encoder as? JsonEncoder ?: throw SerializationException("This class can be saved only by Json")
* val output = encoder as? JsonEncoder ?: throw SerializationException("This class can be encoded only by Json format")
* val tree = when (value) {
* is Either.Left -> JsonObject(mapOf("error" to JsonPrimitive(value.errorMsg)))
* is Either.Right -> output.json.encodeToJsonElement(Payload.serializer(), value.data)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ import kotlinx.serialization.encoding.*
* }
*
* override fun deserialize(decoder: Decoder): Either {
* val input = decoder as? JsonDecoder ?: throw SerializationException("This class can be loaded only by Json")
* val input = decoder as? JsonDecoder ?: throw SerializationException("This class can be decoded only by Json format")
* val tree = input.decodeJsonElement() as? JsonObject ?: throw SerializationException("Expected JsonObject")
* if ("error" in tree) return Either.Left(tree["error"]!!.jsonPrimitive.content)
* return Either.Right(input.json.decodeFromJsonElement(Payload.serializer(), tree))
* }
*
* override fun serialize(encoder: Encoder, value: Either) {
* val output = encoder as? JsonEncoder ?: throw SerializationException("This class can be saved only by Json")
* val output = encoder as? JsonEncoder ?: throw SerializationException("This class can be encoded only by Json format")
* val tree = when (value) {
* is Either.Left -> JsonObject(mapOf("error" to JsonPrimitve(value.errorMsg)))
* is Either.Right -> output.json.encodeToJsonElement(Payload.serializer(), value.data)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +0,0 @@
/*
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.serialization.json.internal

import kotlinx.serialization.*
import kotlinx.serialization.modules.*
import kotlin.jvm.*

// Mirror of the deprecated JsonConfiguration. Not for external use.
@OptIn(ExperimentalSerializationApi::class)
internal data class JsonConfiguration(
@JvmField public val encodeDefaults: Boolean = false,
@JvmField public val ignoreUnknownKeys: Boolean = false,
@JvmField public val isLenient: Boolean = false,
@JvmField public val allowStructuredMapKeys: Boolean = false,
@JvmField public val prettyPrint: Boolean = false,
@JvmField public val prettyPrintIndent: String = " ",
@JvmField public val coerceInputValues: Boolean = false,
@JvmField public val useArrayPolymorphism: Boolean = false,
@JvmField public val classDiscriminator: String = "type",
@JvmField public val allowSpecialFloatingPointValues: Boolean = false,
@JvmField public val useAlternativeNames: Boolean = true,
@JvmField public val serializersModule: SerializersModule = EmptySerializersModule
)

0 comments on commit 59d3216

Please sign in to comment.