1 /*
2 * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 package kotlinx.serialization.json
6
7 import kotlinx.serialization.*
8 import kotlinx.serialization.json.internal.*
9 import kotlinx.serialization.modules.*
10 import kotlinx.serialization.descriptors.*
11 import kotlin.native.concurrent.*
12
13 /**
14 * The main entry point to work with JSON serialization.
15 * It is typically used by constructing an application-specific instance, with configured JSON-specific behaviour
16 * and, if necessary, registered in [SerializersModule] custom serializers.
17 * `Json` instance can be configured in its `Json {}` factory function using [JsonBuilder].
18 * For demonstration purposes or trivial usages, Json [companion][Json.Default] can be used instead.
19 *
20 * Then constructed instance can be used either as regular [SerialFormat] or [StringFormat]
21 * or for converting objects to [JsonElement] back and forth.
22 *
23 * This is the only serial format which has the first-class [JsonElement] support.
24 * Any serializable class can be serialized to or from [JsonElement] with [Json.decodeFromJsonElement] and [Json.encodeToJsonElement] respectively or
25 * serialize properties of [JsonElement] type.
26 *
27 * Example of usage:
28 * ```
29 * @Serializable
30 * class DataHolder(val id: Int, val data: String, val extensions: JsonElement)
31 *
32 * val json = Json
33 * val instance = DataHolder(42, "some data", buildJsonObject { put("additional key", "value") }
34 *
35 * // Plain StringFormat usage
36 * val stringOutput: String = json.encodeToString(instance)
37 *
38 * // JsonElement serialization specific for JSON only
39 * val jsonTree: JsonElement = json.encodeToJsonElement(instance)
40 *
41 * // Deserialize from string
42 * val deserialized: DataHolder = json.decodeFromString<DataHolder>(stringOutput)
43 *
44 * // Deserialize from json tree, JSON-specific
45 * val deserializedFromTree: DataHolder = json.decodeFromJsonElement<DataHolder>(jsonTree)
46 *
47 * // Deserialize from string to JSON tree, JSON-specific
48 * val deserializedToTree: JsonElement = json.parseToJsonElement(stringOutput)
49 * ```
50 *
51 * Json instance also exposes its [configuration] that can be used in custom serializers
52 * that rely on [JsonDecoder] and [JsonEncoder] for customizable behaviour.
53 */
54 public sealed class Json(
55 public val configuration: JsonConfiguration,
56 override val serializersModule: SerializersModule
57 ) : StringFormat {
58
59 @Deprecated(
60 "Should not be accessed directly, use Json.schemaCache accessor instead",
61 ReplaceWith("schemaCache"),
62 DeprecationLevel.ERROR
63 )
64 internal val _schemaCache: DescriptorSchemaCache = DescriptorSchemaCache()
65
66 /**
67 * The default instance of [Json] with default configuration.
68 */
69 @ThreadLocal // to support caching
70 @OptIn(ExperimentalSerializationApi::class)
71 public companion object Default : Json(JsonConfiguration(), EmptySerializersModule())
72
73 /**
74 * Serializes the [value] into an equivalent JSON using the given [serializer].
75 *
76 * @throws [SerializationException] if the given value cannot be serialized to JSON.
77 */
encodeToStringnull78 public final override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
79 val result = JsonToStringWriter()
80 try {
81 encodeByWriter(this@Json, result, serializer, value)
82 return result.toString()
83 } finally {
84 result.release()
85 }
86 }
87
88 /**
89 * Decodes and deserializes the given JSON [string] to the value of type [T] using deserializer
90 * retrieved from the reified type parameter.
91 *
92 * @throws SerializationException in case of any decoding-specific error
93 * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
94 */
decodeFromStringnull95 public inline fun <reified T> decodeFromString(@FormatLanguage("json", "", "") string: String): T =
96 decodeFromString(serializersModule.serializer(), string)
97
98 /**
99 * Deserializes the given JSON [string] into a value of type [T] using the given [deserializer].
100 *
101 * @throws [SerializationException] if the given JSON string is not a valid JSON input for the type [T]
102 * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
103 */
104 public final override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, @FormatLanguage("json", "", "") string: String): T {
105 val lexer = StringJsonLexer(string)
106 val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor, null)
107 val result = input.decodeSerializableValue(deserializer)
108 lexer.expectEof()
109 return result
110 }
111 /**
112 * Serializes the given [value] into an equivalent [JsonElement] using the given [serializer]
113 *
114 * @throws [SerializationException] if the given value cannot be serialized to JSON
115 */
encodeToJsonElementnull116 public fun <T> encodeToJsonElement(serializer: SerializationStrategy<T>, value: T): JsonElement {
117 return writeJson(this@Json, value, serializer)
118 }
119
120 /**
121 * Deserializes the given [element] into a value of type [T] using the given [deserializer].
122 *
123 * @throws [SerializationException] if the given JSON element is not a valid JSON input for the type [T]
124 * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
125 */
decodeFromJsonElementnull126 public fun <T> decodeFromJsonElement(deserializer: DeserializationStrategy<T>, element: JsonElement): T {
127 return readJson(this@Json, element, deserializer)
128 }
129
130 /**
131 * Deserializes the given JSON [string] into a corresponding [JsonElement] representation.
132 *
133 * @throws [SerializationException] if the given string is not a valid JSON
134 */
parseToJsonElementnull135 public fun parseToJsonElement(@FormatLanguage("json", "", "") string: String): JsonElement {
136 return decodeFromString(JsonElementSerializer, string)
137 }
138 }
139
140 /**
141 * Description of JSON input shape used for decoding to sequence.
142 *
143 * The sequence represents a stream of objects parsed one by one;
144 * [DecodeSequenceMode] defines a separator between these objects.
145 * Typically, these objects are not separated by meaningful characters ([WHITESPACE_SEPARATED]),
146 * or the whole stream is a large array of objects separated with commas ([ARRAY_WRAPPED]).
147 */
148 @ExperimentalSerializationApi
149 public enum class DecodeSequenceMode {
150 /**
151 * Declares that objects in the input stream are separated by whitespace characters.
152 *
153 * The stream is read as multiple JSON objects separated by any number of whitespace characters between objects. Starting and trailing whitespace characters are also permitted.
154 * Each individual object is parsed lazily, when it is requested from the resulting sequence.
155 *
156 * Whitespace character is either ' ', '\n', '\r' or '\t'.
157 *
158 * Example of `WHITESPACE_SEPARATED` stream content:
159 * ```
160 * """{"key": "value"}{"key": "value2"} {"key2": "value2"}"""
161 * ```
162 */
163 WHITESPACE_SEPARATED,
164
165 /**
166 * Declares that objects in the input stream are wrapped in the JSON array.
167 * Each individual object in the array is parsed lazily when it is requested from the resulting sequence.
168 *
169 * The stream is read as multiple JSON objects wrapped into a JSON array.
170 * The stream must start with an array start character `[` and end with an array end character `]`,
171 * otherwise, [JsonDecodingException] is thrown.
172 *
173 * Example of `ARRAY_WRAPPED` stream content:
174 * ```
175 * """[{"key": "value"}, {"key": "value2"},{"key2": "value2"}]"""
176 * ```
177 */
178 ARRAY_WRAPPED,
179
180 /**
181 * Declares that parser itself should select between [WHITESPACE_SEPARATED] and [ARRAY_WRAPPED] modes.
182 * The selection is performed by looking at the first meaningful character of the stream.
183 *
184 * In most cases, auto-detection is sufficient to correctly parse an input.
185 * If the input is _whitespace-separated stream of the arrays_, parser could select an incorrect mode,
186 * for that [DecodeSequenceMode] must be specified explicitly.
187 *
188 * Example of an exceptional case:
189 * `[1, 2, 3] [4, 5, 6]\n[7, 8, 9]`
190 */
191 AUTO_DETECT;
192 }
193
194 /**
195 * Creates an instance of [Json] configured from the optionally given [Json instance][from] and adjusted with [builderAction].
196 */
Jsonnull197 public fun Json(from: Json = Json.Default, builderAction: JsonBuilder.() -> Unit): Json {
198 val builder = JsonBuilder(from)
199 builder.builderAction()
200 val conf = builder.build()
201 return JsonImpl(conf, builder.serializersModule)
202 }
203
204 /**
205 * Serializes the given [value] into an equivalent [JsonElement] using a serializer retrieved
206 * from reified type parameter.
207 *
208 * @throws [SerializationException] if the given value cannot be serialized to JSON.
209 */
encodeToJsonElementnull210 public inline fun <reified T> Json.encodeToJsonElement(value: T): JsonElement {
211 return encodeToJsonElement(serializersModule.serializer(), value)
212 }
213
214 /**
215 * Deserializes the given [json] element into a value of type [T] using a deserializer retrieved
216 * from reified type parameter.
217 *
218 * @throws [SerializationException] if the given JSON element is not a valid JSON input for the type [T]
219 * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
220 */
decodeFromJsonElementnull221 public inline fun <reified T> Json.decodeFromJsonElement(json: JsonElement): T =
222 decodeFromJsonElement(serializersModule.serializer(), json)
223
224 /**
225 * Builder of the [Json] instance provided by `Json { ... }` factory function.
226 */
227 @Suppress("unused", "DeprecatedCallableAddReplaceWith")
228 @OptIn(ExperimentalSerializationApi::class)
229 public class JsonBuilder internal constructor(json: Json) {
230 /**
231 * Specifies whether default values of Kotlin properties should be encoded.
232 * `false` by default.
233 */
234 public var encodeDefaults: Boolean = json.configuration.encodeDefaults
235
236 /**
237 * Specifies whether `null` values should be encoded for nullable properties and must be present in JSON object
238 * during decoding.
239 *
240 * When this flag is disabled properties with `null` values without default are not encoded;
241 * during decoding, the absence of a field value is treated as `null` for nullable properties without a default value.
242 *
243 * `true` by default.
244 */
245 @ExperimentalSerializationApi
246 public var explicitNulls: Boolean = json.configuration.explicitNulls
247
248 /**
249 * Specifies whether encounters of unknown properties in the input JSON
250 * should be ignored instead of throwing [SerializationException].
251 * `false` by default.
252 */
253 public var ignoreUnknownKeys: Boolean = json.configuration.ignoreUnknownKeys
254
255 /**
256 * Removes JSON specification restriction (RFC-4627) and makes parser
257 * more liberal to the malformed input. In lenient mode, unquoted JSON keys and string values are allowed.
258 *
259 * Its relaxations can be expanded in the future, so that lenient parser becomes even more
260 * permissive to invalid values in the input.
261 *
262 * `false` by default.
263 */
264 public var isLenient: Boolean = json.configuration.isLenient
265
266 /**
267 * Enables structured objects to be serialized as map keys by
268 * changing serialized form of the map from JSON object (key-value pairs) to flat array like `[k1, v1, k2, v2]`.
269 * `false` by default.
270 */
271 public var allowStructuredMapKeys: Boolean = json.configuration.allowStructuredMapKeys
272
273 /**
274 * Specifies whether resulting JSON should be pretty-printed.
275 * `false` by default.
276 */
277 public var prettyPrint: Boolean = json.configuration.prettyPrint
278
279 /**
280 * Specifies indent string to use with [prettyPrint] mode
281 * 4 spaces by default.
282 * Experimentality note: this API is experimental because
283 * it is not clear whether this option has compelling use-cases.
284 */
285 @ExperimentalSerializationApi
286 public var prettyPrintIndent: String = json.configuration.prettyPrintIndent
287
288 /**
289 * Enables coercing incorrect JSON values to the default property value (if exists) in the following cases:
290 * 1. JSON value is `null` but the property type is non-nullable.
291 * 2. Property type is an enum type, but JSON value contains unknown enum member.
292 *
293 * `false` by default.
294 */
295 public var coerceInputValues: Boolean = json.configuration.coerceInputValues
296
297 /**
298 * Switches polymorphic serialization to the default array format.
299 * This is an option for legacy JSON format and should not be generally used.
300 * `false` by default.
301 *
302 * This option can only be used if [classDiscriminatorMode] in a default [ClassDiscriminatorMode.POLYMORPHIC] state.
303 */
304 public var useArrayPolymorphism: Boolean = json.configuration.useArrayPolymorphism
305
306 /**
307 * Name of the class descriptor property for polymorphic serialization.
308 * "type" by default.
309 */
310 public var classDiscriminator: String = json.configuration.classDiscriminator
311
312
313 /**
314 * Defines which classes and objects should have class discriminator added to the output.
315 * [ClassDiscriminatorMode.POLYMORPHIC] by default.
316 *
317 * Other modes are generally intended to produce JSON for consumption by third-party libraries,
318 * therefore, this setting does not affect the deserialization process.
319 */
320 public var classDiscriminatorMode: ClassDiscriminatorMode = json.configuration.classDiscriminatorMode
321
322 /**
323 * Removes JSON specification restriction on
324 * special floating-point values such as `NaN` and `Infinity` and enables their serialization and deserialization.
325 * When enabling it, please ensure that the receiving party will be able to encode and decode these special values.
326 * `false` by default.
327 */
328 public var allowSpecialFloatingPointValues: Boolean = json.configuration.allowSpecialFloatingPointValues
329
330 /**
331 * Specifies whether Json instance makes use of [JsonNames] annotation.
332 *
333 * Disabling this flag when one does not use [JsonNames] at all may sometimes result in better performance,
334 * particularly when a large count of fields is skipped with [ignoreUnknownKeys].
335 * `true` by default.
336 */
337 public var useAlternativeNames: Boolean = json.configuration.useAlternativeNames
338
339 /**
340 * Specifies [JsonNamingStrategy] that should be used for all properties in classes for serialization and deserialization.
341 *
342 * `null` by default.
343 *
344 * This strategy is applied for all entities that have [StructureKind.CLASS].
345 */
346 @ExperimentalSerializationApi
347 public var namingStrategy: JsonNamingStrategy? = json.configuration.namingStrategy
348
349 /**
350 * Enables decoding enum values in a case-insensitive manner.
351 * Encoding is not affected.
352 *
353 * This affects both enum serial names and alternative names (specified with the [JsonNames] annotation).
354 * In the following example, string `[VALUE_A, VALUE_B]` will be printed:
355 * ```
356 * enum class E { VALUE_A, @JsonNames("ALTERNATIVE") VALUE_B }
357 *
358 * @Serializable
359 * data class Outer(val enums: List<E>)
360 *
361 * val j = Json { decodeEnumsCaseInsensitive = true }
362 * println(j.decodeFromString<Outer>("""{"enums":["value_A", "alternative"]}""").enums)
363 * ```
364 *
365 * If this feature is enabled,
366 * it is no longer possible to decode enum values that have the same name in a lowercase form.
367 * The following code will throw a serialization exception:
368 *
369 * ```
370 * enum class BadEnum { Bad, BAD }
371 * val j = Json { decodeEnumsCaseInsensitive = true }
372 * j.decodeFromString<Box<BadEnum>>("""{"boxed":"bad"}""")
373 * ```
374 */
375 @ExperimentalSerializationApi
376 public var decodeEnumsCaseInsensitive: Boolean = json.configuration.decodeEnumsCaseInsensitive
377
378 /**
379 * Allows parser to accept trailing (ending) commas in JSON objects and arrays,
380 * making inputs like `[1, 2, 3,]` valid.
381 *
382 * Does not affect encoding.
383 * `false` by default.
384 */
385 @ExperimentalSerializationApi
386 public var allowTrailingComma: Boolean = json.configuration.allowTrailingComma
387
388 /**
389 * Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
390 *
391 * @see SerializersModule
392 * @see Contextual
393 * @see Polymorphic
394 */
395 public var serializersModule: SerializersModule = json.serializersModule
396
397 @OptIn(ExperimentalSerializationApi::class)
398 internal fun build(): JsonConfiguration {
399 if (useArrayPolymorphism) {
400 require(classDiscriminator == defaultDiscriminator) {
401 "Class discriminator should not be specified when array polymorphism is specified"
402 }
403 require(classDiscriminatorMode == ClassDiscriminatorMode.POLYMORPHIC) {
404 "useArrayPolymorphism option can only be used if classDiscriminatorMode in a default POLYMORPHIC state."
405 }
406 }
407
408 if (!prettyPrint) {
409 require(prettyPrintIndent == defaultIndent) {
410 "Indent should not be specified when default printing mode is used"
411 }
412 } else if (prettyPrintIndent != defaultIndent) {
413 // Values allowed by JSON specification as whitespaces
414 val allWhitespaces = prettyPrintIndent.all { it == ' ' || it == '\t' || it == '\r' || it == '\n' }
415 require(allWhitespaces) {
416 "Only whitespace, tab, newline and carriage return are allowed as pretty print symbols. Had $prettyPrintIndent"
417 }
418 }
419
420 return JsonConfiguration(
421 encodeDefaults, ignoreUnknownKeys, isLenient,
422 allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
423 coerceInputValues, useArrayPolymorphism,
424 classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
425 namingStrategy, decodeEnumsCaseInsensitive, allowTrailingComma, classDiscriminatorMode
426 )
427 }
428 }
429
430 @OptIn(ExperimentalSerializationApi::class)
431 private class JsonImpl(configuration: JsonConfiguration, module: SerializersModule) : Json(configuration, module) {
432
433 init {
434 validateConfiguration()
435 }
436
validateConfigurationnull437 private fun validateConfiguration() {
438 if (serializersModule == EmptySerializersModule()) return // Fast-path for in-place JSON allocations
439 val collector = PolymorphismValidator(configuration.useArrayPolymorphism, configuration.classDiscriminator)
440 serializersModule.dumpTo(collector)
441 }
442 }
443
444 /**
445 * This accessor should be used to workaround for freezing problems in Native, see Native source set
446 */
447 internal expect val Json.schemaCache: DescriptorSchemaCache
448
449 private const val defaultIndent = " "
450 private const val defaultDiscriminator = "type"
451