• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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