blob: 99102b070ec3ee45e1856e9f9ec774882c5ed950 [file] [log] [blame]
/*
* Copyright 2017-2020 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 kotlinx.serialization.json.internal.*
import kotlinx.serialization.modules.*
import kotlin.js.*
import kotlin.jvm.*
/**
* The main entry point to work with JSON serialization.
* It is typically used by constructing an application-specific instance, with configured JSON-specific behaviour
* and, if necessary, registered in [SerializersModule] custom serializers.
* `Json` instance can be configured in its `Json {}` factory function using [JsonBuilder].
* For demonstration purposes or trivial usages, Json [companion][Json.Default] can be used instead.
*
* Then constructed instance can be used either as regular [SerialFormat] or [StringFormat]
* or for converting objects to [JsonElement] back and forth.
*
* This is the only serial format which has the first-class [JsonElement] support.
* Any serializable class can be serialized to or from [JsonElement] with [Json.decodeFromJsonElement] and [Json.encodeToJsonElement] respectively or
* serialize properties of [JsonElement] type.
*
* Example of usage:
* ```
* @Serializable
* class DataHolder(val id: Int, val data: String, val extensions: JsonElement)
*
* val json = Json
* val instance = DataHolder(42, "some data", buildJsonObject { put("additional key", "value") }
*
* // Plain StringFormat usage
* val stringOutput: String = json.encodeToString(instance)
*
* // JsonElement serialization specific for JSON only
* val jsonTree: JsonElement = json.encodeToJsonElement(instance)
*
* // Deserialize from string
* val deserialized: DataHolder = json.decodeFromString<DataHolder>(stringOutput)
*
* // Deserialize from json tree, JSON-specific
* val deserializedFromTree: DataHolder = json.decodeFromJsonElement<DataHolder>(jsonTree)
*
* // Deserialize from string to JSON tree, JSON-specific
* val deserializedToTree: JsonElement = json.parseJsonElement(stringOutput)
* ```
*/
public sealed class Json(internal val configuration: JsonConf) : StringFormat {
override val serializersModule: SerializersModule
get() = configuration.serializersModule
/**
* The default instance of [Json] with default configuration.
*/
public companion object Default : Json(JsonConf())
/**
* Serializes the [value] into an equivalent JSON using the given [serializer].
*
* @throws [SerializationException] if the given value cannot be serialized to JSON.
*/
public final override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
val result = StringBuilder()
val encoder = StreamingJsonEncoder(
result, this,
WriteMode.OBJ,
arrayOfNulls(WriteMode.values().size)
)
encoder.encodeSerializableValue(serializer, value)
return result.toString()
}
/**
* Deserializes the given JSON [string] into a value of type [T] using the given [deserializer].
*
* @throws [SerializationException] if the given JSON string cannot be deserialized to the value of type [T].
*/
public final override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
val reader = JsonReader(string)
val input = StreamingJsonDecoder(this, WriteMode.OBJ, reader)
val result = input.decodeSerializableValue(deserializer)
if (!reader.isDone) { error("Reader has not consumed the whole input: $reader") }
return result
}
/**
* Serializes the given [value] into an equivalent [JsonElement] using the given [serializer]
*
* @throws [SerializationException] if the given value cannot be serialized.
*/
public fun <T> encodeToJsonElement(serializer: SerializationStrategy<T>, value: T): JsonElement {
return writeJson(value, serializer)
}
/**
* Deserializes the given [element] into a value of type [T] using the given [deserializer].
*
* @throws [SerializationException] if the given JSON string cannot be deserialized to the value of type [T].
*/
public fun <T> decodeFromJsonElement(deserializer: DeserializationStrategy<T>, element: JsonElement): T {
return readJson(element, deserializer)
}
/**
* Deserializes the given JSON [string] into a corresponding [JsonElement] representation.
*
* @throws [SerializationException] if the given JSON string is malformed and cannot be deserialized
*/
public fun parseToJsonElement(string: String): JsonElement {
return decodeFromString(JsonElementSerializer, 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)
builder.builderAction()
val conf = builder.build()
return JsonImpl(conf)
}
/**
* Serializes the given [value] into an equivalent [JsonElement] using a serializer retrieved
* from reified type parameter.
*
* @throws [SerializationException] if the given value cannot be serialized to JSON.
*/
public inline fun <reified T : Any> Json.encodeToJsonElement(value: T): JsonElement {
return encodeToJsonElement(serializersModule.getContextualOrDefault(), value)
}
/**
* Deserializes the given [json] element into a value of type [T] using a deserialize retrieved
* from reified type parameter.
*
* @throws [SerializationException] if the given JSON string is malformed or cannot be deserialized to the value of type [T].
*/
public inline fun <reified T : Any> Json.decodeFromJsonElement(tree: JsonElement): T =
decodeFromJsonElement(serializersModule.getContextualOrDefault(), tree)
/**
* Builder of the [Json] instance provided by `Json` factory function.
*/
@Suppress("unused", "DeprecatedCallableAddReplaceWith")
public class JsonBuilder internal constructor(conf: JsonConf) {
/**
* Specifies whether default values of Kotlin properties should be encoded.
* `true` by default.
*/
public var encodeDefaults: Boolean = conf.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 = conf.ignoreUnknownKeys
/**
* 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.
* `false` by default.
*/
public var isLenient: Boolean = conf.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 = conf.allowStructuredMapKeys
/**
* Specifies whether resulting JSON should be pretty-printed.
* `false` by default.
*/
public var prettyPrint: Boolean = conf.prettyPrint
/**
* Specifies indent string to use with [prettyPrint] mode.
* TODO specify whitespaces
*/
public var prettyPrintIndent: String = conf.prettyPrintIndent
/**
* Enables coercing incorrect JSON values to the default property value in the following cases:
* 1. JSON value is `null` but property type is non-nullable.
* 2. Property type is an enum type, but JSON value contains unknown enum member.
*
* `false` by default.
*/
public var coerceInputValues: Boolean = conf.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 = conf.useArrayPolymorphism
/**
* Name of the class descriptor property for polymorphic serialization.
* "type" by default.
*/
public var classDiscriminator: String = conf.classDiscriminator
/**
* Removes JSON specification restriction on
* special floating-point values such as `NaN` and `Infinity` and enables their serialization.
* 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 = conf.allowSpecialFloatingPointValues
/**
* Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
*/
public var serializersModule: SerializersModule = conf.serializersModule
internal fun build(): JsonConf {
if (useArrayPolymorphism) require(classDiscriminator == defaultDiscriminator) {
"Class discriminator should not be specified when array polymorphism is specified"
}
if (!prettyPrint) require(prettyPrintIndent == defaultIndent) {
"Indent should not be specified when default printing mode is used"
}
return JsonConf(
encodeDefaults, ignoreUnknownKeys, isLenient,
allowStructuredMapKeys, prettyPrint, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
classDiscriminator, allowSpecialFloatingPointValues, serializersModule
)
}
private companion object {
@JvmStatic
private val defaultIndent = " "
@JvmStatic
private val defaultDiscriminator = "type"
}
// Deprecated members below
@Deprecated(
level = DeprecationLevel.ERROR,
message = "This flag was renamed to 'allowSpecialFloatingPointValues' during serialization 1.0 API stabilization",
replaceWith = ReplaceWith("allowSpecialFloatingPointValues")
)
public var serializeSpecialFloatingPointValues: Boolean = false
@Deprecated(
level = DeprecationLevel.ERROR,
message = "This flag was renamed to 'prettyPrintIndent' during serialization 1.0 API stabilization",
replaceWith = ReplaceWith("prettyPrintIndent")
)
public var indent: String = " "
@Deprecated(
level = DeprecationLevel.ERROR,
message = "'strictMode = true' is replaced with 3 new configuration parameters: " +
"'ignoreUnknownKeys = false' to fail if an unknown key is encountered, " +
"'serializeSpecialFloatingPointValues = false' to fail on 'NaN' and 'Infinity' values, " +
"'isLenient = false' to prohibit parsing of any non-compliant or malformed JSON"
)
public var strictMode: Boolean = true
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Unquoted mode was deprecated without replacement",
)
public var unquoted: Boolean = false
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Unquoted mode was deprecated without replacement"
)
public var unquotedPrint: Boolean = false
@Deprecated(
level = DeprecationLevel.ERROR,
message = "'serialModule' was renamed to 'serializersModule' during serialization 1.0 API stabilization",
replaceWith = ReplaceWith("serializersModule")
)
public var serialModule: SerializersModule = EmptySerializersModule
@Deprecated(
level = DeprecationLevel.ERROR,
message = "This method was deprecated for removal during serialization 1.0 API stabilization",
)
@Suppress("DEPRECATION_ERROR")
public fun buildConfiguration(): JsonConfiguration = error("Deprecated and should not be called")
@Deprecated(
level = DeprecationLevel.ERROR,
message = "This method was deprecated for removal during serialization 1.0 API stabilization",
)
public fun buildModule(): SerializersModule = error("Deprecated and should not be called")
}
private class JsonImpl(configuration: JsonConf) : Json(configuration) {
init {
validateConfiguration()
}
private fun validateConfiguration() {
if (configuration.useArrayPolymorphism) return
val collector = ContextValidator(configuration.classDiscriminator)
serializersModule.dumpTo(collector)
}
}
// Deprecated helpers
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of Json {} builder function during serialization 1.0 API stabilization.\n" +
"In order to migrate, please replace it with Json { } builder function. JsonBuilder receiver has the same " +
"properties as 'JsonConfiguration' and 'context' and should be configured there.\n" +
"Json(JsonConfiguration.Default) can be replaced with Json companion instead."
)
@Suppress("DEPRECATION_ERROR")
public fun Json(
configuration: JsonConfiguration, context: SerializersModule = EmptySerializersModule
): Json = error("Deprecated and should not be called")
/*
* These two declarations are trick for better migration.
* Most of the usages (according to GitHub search) were the following:
* ```
* Json(JsonConfiguration.Default)
* Json(JsonConfiguration.Stable)
* Json(JsonConfiguration.X, module)
* ```
*
* To migrate them gracefully, these properties subtype was narrowed down to `SubtypeToDetectDefaults`
*/
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of Json.Default companion during serialization 1.0 API stabilization.\n",
replaceWith = ReplaceWith("Json")
)
public fun Json(
configuration: SubtypeToDetectDefault
): Json = error("Deprecated and should not be called")
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of Json {} builder function during serialization 1.0 API stabilization.\n",
replaceWith = ReplaceWith("Json { allowStructuredMapKeys = true }") // stable configuration
)
public fun Json(
configuration: SubtypeToDetectStable
): Json = error("Deprecated and should not be called")
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of Json {} builder function during serialization 1.0 API stabilization.\n",
replaceWith = ReplaceWith("Json { serializersModule = context }")
)
public fun Json(
configuration: SubtypeToDetectDefault, context: SerializersModule
): Json = error("Deprecated and should not be called")
@Deprecated(
"Deprecated in the favour of Json {} builder function during serialization 1.0 API stabilization",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("Json { serializersModule = context }")
)
public fun Json(context: SerializersModule): Json = error("Deprecated and should not be called")
@JsName("_Json")
@Deprecated(
"Empty constructor was deprecated in the favour of Json.Default instance during serialization 1.0 API stabilization",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("Json")
)
public fun Json(): Json = error("Deprecated and should not be called")