Upgrade kotlinx.serialization to v1.6.3
This project was upgraded with external_updater.
Usage: tools/external_updater/updater.sh update external/kotlinx.serialization
For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
Test: TreeHugger
Change-Id: I0a33712c9ac0a953307b3e8c3470f29cdd6dcaec
diff --git a/.gitignore b/.gitignore
index c56b7b9..054cf97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@
# IntelliJ files
**/.idea/*
!/.idea/vcs.xml
+!/.idea/codeStyles
+!/.idea/copyright
out/
*.iml
@@ -15,3 +17,6 @@
# Modules for JS projects build
node_modules
+
+# benchmarks.jar
+/benchmarks.jar
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..5662cc4
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,15 @@
+<component name="ProjectCodeStyleConfiguration">
+ <code_scheme name="Project" version="173">
+ <JetCodeStyleSettings>
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="1"/>
+ <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="1"/>
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL"/>
+ </JetCodeStyleSettings>
+ <codeStyleSettings language="kotlin">
+ <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL"/>
+ <indentOptions>
+ <option name="CONTINUATION_INDENT_SIZE" value="4"/>
+ </indentOptions>
+ </codeStyleSettings>
+ </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..127f806
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="USE_PER_PROJECT_SETTINGS" value="true"/>
+ </state>
+</component>
\ No newline at end of file
diff --git a/.idea/copyright/kotlinx_serialization.xml b/.idea/copyright/kotlinx_serialization.xml
new file mode 100644
index 0000000..52e9052
--- /dev/null
+++ b/.idea/copyright/kotlinx_serialization.xml
@@ -0,0 +1,6 @@
+ <component name="CopyrightManager">
+ <copyright>
+ <option name="notice" value="Copyright 2017-&#36;today.year JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license." />
+ <option name="myName" value="kotlinx.serialization" />
+ </copyright>
+</component>
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..caad99f
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+<component name="CopyrightManager">
+ <settings default="kotlinx.serialization" />
+</component>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index cc23b03..9dcab70 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -2,7 +2,6 @@
<project version="4">
<component name="CommitMessageInspectionProfile">
<profile version="1.0">
- <inspection_tool class="CommitMessageSpellChecking" enabled="false" level="TYPO" enabled_by_default="true" />
<inspection_tool class="GraziCommit" enabled="true" level="TYPO" enabled_by_default="true" />
<inspection_tool class="GrazieCommit" enabled="true" level="TYPO" enabled_by_default="true" />
</profile>
@@ -21,10 +20,14 @@
<option name="issueRegexp" value="#(\d+)" />
<option name="linkRegexp" value="https://github.com/Kotlin/kotlinx.serialization/issues/$1" />
</IssueNavigationLink>
+ <IssueNavigationLink>
+ <option name="issueRegexp" value="[A-Z]+\-\d+"/>
+ <option name="linkRegexp" value="http://youtrack.jetbrains.com/issue/$0"/>
+ </IssueNavigationLink>
</list>
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
-</project>
+</project>
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 621bf09..22d392c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,363 @@
+1.6.3 / 2024-02-16
+==================
+
+This release provides a couple of new features and uses Kotlin 1.9.22 as default.
+
+### Class discriminator output mode
+
+Class discriminator provides information for serializing and deserializing [polymorphic class hierarchies](docs/polymorphism.md#sealed-classes).
+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.
+You can learn more about this feature in the documentation and corresponding [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2532).
+
+### Other features
+
+* Add kebab-case naming strategy (#2531) (thanks to [Emil Kantis](https://github.com/Kantis))
+* Add value class support to the ProtoBufSchemaGenerator (#2542) (thanks to [Felipe Rotilho](https://github.com/rotilho))
+
+### Bugfixes and improvements
+
+* Fix: Hocon polymorphic serialization in containers (#2151) (thanks to [LichtHund](https://github.com/LichtHund))
+* Actualize lenient mode documentation (#2568)
+* Slightly improve error messages thrown from serializer<T>() function (#2533)
+* Do not try to coerce input values for properties (#2530)
+* Make empty objects and arrays collapsed in pretty print mode (#2506)
+* Update Gradle dokka configuration to make sure "source" button is visible in all API docs (#2518, #2524)
+
+1.6.2 / 2023-11-30
+==================
+
+This is a patch release accompanying Kotlin 1.9.21. It also provides additional targets that were not available in 1.6.1:
+wasm-wasi and (deprecated) linuxArm32Hfp.
+
+* Add Wasm WASI target (#2510)
+* Bring back linuxArm32Hfp target because it is deprecated, but not removed yet. (#2505)
+
+1.6.1 / 2023-11-15
+==================
+
+This release uses Kotlin 1.9.20 by default, while upcoming 1.9.21 is also supported.
+
+### Trailing commas in Json
+
+Trailing commas are one of the most popular non-spec Json variations.
+A new configuration flag, `allowTrailingComma`, makes Json parser accept them instead of throwing an exception.
+Note that it does not affect encoding, so kotlinx.serialization always produces Json without trailing commas.
+See details in the corresponding [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2480)
+
+### Support of WasmJs target
+
+Kotlin/Wasm has been experimental for some time and gained enough maturity to be added to the kotlinx libraries.
+Starting with 1.6.1, kotlinx.serialization provides a wasm-js flavor, so your projects with Kotlin/Wasm can have even more
+functionality.
+As usual, just add serialization dependencies to your build
+and [declare wasmJs target](https://kotlinlang.org/docs/whatsnew1920.html#new-wasm-wasi-target-and-the-renaming-of-the-wasm-target-to-wasm-js).
+Please remember that Kotlin/Wasm is still experimental, so changes are expected.
+
+### Bugfixes and improvements
+
+* Fix TaggedDecoder nullable decoding (#2456) (thanks to [Phillip Schichtel](https://github.com/pschichtel))
+* Fix IllegalAccessException for some JPMS boundaries (#2469)
+* Cbor: check if inline value classes are marked as @ByteString (#2466) (thanks to [eater](https://github.com/the-eater))
+* Improve polymorphic deserialization optimization (#2481)
+* Update Okio dependency to 3.6.0 (#2473)
+* Add protobuf conformance tests (#2404) (thanks to [Doğaç Eldenk](https://github.com/Dogacel))
+* Support decoding maps with boolean keys (#2440)
+
+1.6.0 / 2023-08-22
+==================
+
+This release contains all features and bugfixes from [1.6.0-RC](https://github.com/Kotlin/kotlinx.serialization/releases/tag/v1.6.0-RC) plus some bugfixes on its own (see below).
+Kotlin 1.9.0 is used as a default, while 1.9.10 is also supported.
+
+### Bugfixes
+
+ * Improve error messages from Json parser (#2406)
+ * Mark @SerialName, @Required and @Transient with @MustBeDocumented (#2407)
+ * Ensure that no additional files except java compiler output get into multi-release jar (#2405)
+ * Fix enums with negative numbers in protobuf not serializing & de-serializing (#2400) (thanks to [Doğaç Eldenk](https://github.com/Dogacel))
+
+1.6.0-RC / 2023-08-03
+==================
+
+This release is based on the Kotlin 1.9.0.
+
+### Removal of Legacy JS target
+
+Some time ago, in Kotlin 1.8, [JS IR compiler was promoted to stable and old JS compiler was deprecated](https://kotlinlang.org/docs/whatsnew18.html#stable-js-ir-compiler-backend).
+Kotlin 1.9 promotes the usage of deprecated JS compiler to an error. As a result, kotlinx.serialization no longer builds with the legacy compiler
+and does not distribute artifacts for it. You can read the migration guide for JS IR compiler [here](https://kotlinlang.org/docs/js-ir-migration.html).
+
+Also pay attention to the fact that Kotlin/Native also has some [deprecated targets](https://kotlinlang.org/docs/native-target-support.html#deprecated-targets)
+that are going to be removed in the Kotlin 1.9.20. Therefore, kotlinx.serialization 1.6.0-RC and 1.6.0 are likely the last releases that support these targets.
+
+### Case insensitivity for enums in Json
+
+This release features a new configuration flag for Json: `decodeEnumsCaseInsensitive`
+that allows you to decode enum values in a case-insensitive manner.
+For example, when decoding `enum class Foo { VALUE_A , VALUE_B}` both inputs `"value_a"` and `"value_A"` will yield `Foo.VALUE_A`.
+You can read more about this feature in the documentation and corresponding [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2345).
+
+### Other bugfixes and enhancements
+
+ * Add support to decode numeric literals containing an exponent (#2227) (thanks to [Roberto Blázquez](https://github.com/xBaank))
+ * Fix NoSuchMethodError related to Java 8 API compatibility (#2328, #2350) (thanks to [Björn Kautler](https://github.com/Vampire))
+ * Changed actual FormatLanguage annotation for JS and Native to avoid problems with duplicating org.intellij.lang.annotations.Language (#2390, #2379)
+ * Fix error triggered by 'consume leading class discriminator' polymorphic parsing optimization (#2362)
+ * Fix runtime error with Serializer for Nothing on the JS target (#2330) (thanks to [Shreck Ye](https://github.com/ShreckYe))
+ * Fix beginStructure in JsonTreeDecoder when inner structure descriptor is same as outer (#2346) (thanks to [Ugljesa Jovanovic](https://github.com/ionspin))
+ * Actualize 'serializer not found' platform-specific message (#2339)
+ * Fixed regression with serialization using a list parametrized with contextual types (#2331)
+
+
+1.5.1 / 2023-05-11
+==================
+This release contains an important Native targets overhaul, as well as numerous enhancements and bugfixes.
+Kotlin 1.8.21 is used by default.
+
+### New set of Native targets
+
+The official [Kotlin target support policy](https://kotlinlang.org/docs/native-target-support.html) has recently been published
+describing new target policy: each target belongs to a certain _tier_, and different tiers have different stability guarantees.
+The official recommendation for library authors is to support targets up to Tier 3,
+and kotlinx.serialization now follows it.
+It means that in this release, there are a lot of new targets added from this tier,
+such as `androidNativeX86` or `watchosDeviceArm64`.
+Note that since they belong to Tier 3, they're not auto-tested on CI.
+
+kotlinx.serialization also ships some deprecated Kotlin/Native targets that do not belong to any tier (e.g. `iosArm32`, `mingwX86`).
+We'll continue to release them, but we do not provide support for them, nor do we plan to add new targets from the deprecated list.
+
+### Improvements in Json elements
+
+There are two new function sets that should make creating raw Json elements easier.
+[First one](https://github.com/Kotlin/kotlinx.serialization/pull/2160) contains overloads for `JsonPrimitive` constructor-like function
+that accept unsigned types: `JsonPrimitive(1u)`.
+[Second one](https://github.com/Kotlin/kotlinx.serialization/pull/2156) adds new `addAll` functions to `JsonArrayBuilder` to be used with collections
+of numbers, booleans or strings: `buildJsonArray { addAll(listOf(1, 2, 3)) }`
+Both were contributed to us by [aSemy](https://github.com/aSemy).
+
+### Other enhancements
+
+ * **Potential source-breaking change**: Rename json-okio `target` variables to `sink` (#2226)
+ * Function to retrieve KSerializer by KClass and type arguments serializers (#2291)
+ * Added FormatLanguage annotation to Json methods (#2234)
+ * Properties Format: Support sealed/polymorphic classes as class properties (#2255)
+
+### Bugfixes
+
+ * KeyValueSerializer: Fix missing call to endStructure() (#2272)
+ * ObjectSerializer: Respect sequential decoding (#2273)
+ * Fix value class encoding in various corner cases (#2242)
+ * Fix incorrect json decoding iterator's .hasNext() behavior on array-wrapped inputs (#2268)
+ * Fix memory leak caused by invalid KTypeWrapper's equals method (#2274)
+ * Fixed NoSuchMethodError when parsing a JSON stream on Java 8 (#2219)
+ * Fix MissingFieldException duplication (#2213)
+
+
+1.5.0 / 2023-02-27
+==================
+
+This release contains all features and bugfixes from 1.5.0-RC plus some experimental features and bugfixes on its own (see below).
+Kotlin 1.8.10 is used as a default.
+
+### HoconEncoder and HoconDecoder interfaces and HOCON-specific serializers
+
+These interfaces work in a way similar to `JsonEncoder` and `JsonDecoder`: they allow intercepting (de)serialization process,
+making writing if custom HOCON-specific serializers easier. New `ConfigMemorySizeSerializer` and `JavaDurationSerializer` already make use of them.
+See more details in the [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2094).
+Big thanks to [Alexander Mikhailov](https://github.com/alexmihailov) for contributing this!
+
+### Ability to read buffered huge strings in custom Json deserializers
+
+New interface `ChunkedDecoder` allows you to read huge strings that may not fit in memory by chunks.
+Currently, this interface is only implemented by Json decoder that works with strings and streams,
+but we may expand it later, if there's a demand for it.
+See more details in the [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2012) authored by [Alexey Sviridov](https://github.com/fred01).
+
+### Bugfixes
+
+ * Improve runtime exceptions messages (#2180)
+ * Added support for null values for nullable enums in lenient mode (#2176)
+ * Prevent class loaders from leaking when using ClassValue cache (#2175)
+
+1.5.0-RC / 2023-01-25
+==================
+
+This is a release candidate for the next version with many new features to try.
+It uses Kotlin 1.8.0 by default.
+
+### Json naming strategies
+
+A long-awaited feature (#33) is available in this release.
+A new interface, `JsonNamingStrategy` and Json configuration property `namingStrategy` allow
+defining a transformation that is applied to all properties' names serialized by a Json instance.
+There's also a predefined implementation for the most common use case: `Json { namingStrategy = JsonNamingStrategy.SnakeCase }`.
+Check out the [PR](https://github.com/Kotlin/kotlinx.serialization/pull/2111) for more details and documentation.
+
+### Json unquoted literals
+
+kotlinx-serialization-json has an API for manipulating raw Json values: functions and classes `JsonObject`, `JsonPrimitive`, etc.
+In this release, there is a new addition to this API: `JsonUnquotedLiteral` constructor function.
+It allows to produce a string that is not quoted in the Json output. This function has a lot of valuable
+applications: from writing unsigned or large numbers to embedding whole Json documents without the need for re-parsing.
+For an example, read the [Encoding literal Json content docs](https://github.com/Kotlin/kotlinx.serialization/blob/v1.5.0-RC/docs/json.md#encoding-literal-json-content-experimental).
+This huge feature was contributed to us by [aSemy](https://github.com/aSemy): [#2041](https://github.com/Kotlin/kotlinx.serialization/pull/2041).
+
+### Stabilization of serializer(java.lang.Type) function family
+
+Functions `serializer`, `serializerOrNull` and extensions `SerializersModule.serializer`, `SerializersModule.serializerOrNull`
+have JVM-only overloads that accept `java.lang.Type`. These overloads are crucial for interoperability: with them, third-party Java frameworks
+like Spring, which usually rely on Java's reflection and type tokens, can retrieve `KSerializer` instance and use kotlinx.serialization properly.
+We've removed `@ExperimentalSerializationApi` from these functions, and starting from 1.5.0-RC they're considered stable with all backward compatibility guarantees.
+This change should improve third-party support for kotlinx.serialization in various frameworks.
+See the [PR](https://github.com/Kotlin/kotlinx.serialization/issues/2069) for details.
+
+### Deprecations in module builders for polymorphism
+
+Some time ago, in 1.3.2, new functions `SerializersModuleBuilder.polymorphicDefaultSerializer/polymorphicDefaultDeserializer` and `PolymorphicModuleBuilder.defaultDeserializer` were introduced
+— better names allow an easier understanding of which serializers affect what part of the process.
+In 1.5.0-RC, we finish the migration path: these functions are no longer experimental.
+And old functions, namely `SerializersModuleCollector.polymorphicDefault` and `PolymorphicModuleBuilder.default`, are now deprecated.
+See the [PR](https://github.com/Kotlin/kotlinx.serialization/issues/2076) for details.
+
+### Bundled Proguard rules
+
+The `kotlinx-serialization-core-jvm` JAR file now includes consumer Proguard rules,
+so manual Proguard configuration is no longer necessary for most of the setups.
+See updated [Android setup section](https://github.com/Kotlin/kotlinx.serialization/blob/169a14558ca13cfd731283a854d825d1f19ef195/README.md#android)
+and corresponding PRs: [#2092](https://github.com/Kotlin/kotlinx.serialization/issues/2092), [#2123](https://github.com/Kotlin/kotlinx.serialization/issues/2123).
+
+### Support for kotlin.Duration in HOCON format
+
+HOCON specifies its own formatting for duration values. Starting with this release,
+kotlinx-serialization-hocon is able to serialize and deserialize `kotlin.Duration`
+using proper representation instead of the default one. Big thanks to [Alexander Mikhailov](https://github.com/alexmihailov)
+and his PRs: [#2080](https://github.com/Kotlin/kotlinx.serialization/issues/2080), [#2073](https://github.com/Kotlin/kotlinx.serialization/issues/2073).
+
+### Functional and performance improvements
+
+ * Make DeserializationStrategy covariant at declaration-site (#1897) (thanks to [Lukellmann](https://github.com/Lukellmann))
+ * Added support for the `kotlin.Nothing` class as built-in (#1991, #2150)
+ * Further improve stream decoding performance (#2101)
+ * Introduce CharArray pooling for InputStream decoding (#2100)
+ * Consolidate exception messages and improve them (#2068)
+
+### Bugfixes
+
+ * Add stable hashCode()/equals() calculation to PrimitiveSerialDescriptor (#2136) (thanks to [Vasily Vasilkov](https://github.com/vgv))
+ * Added a factory that creates an enum serializer with annotations on the class (#2125)
+ * Correctly handle situation where different serializers can be provided for the same KClass in SealedClassSerializer (#2113)
+ * Fixed serializers caching for parametrized types from different class loaders (#2070)
+
+
+1.4.1 / 2022-10-14
+==================
+
+This is patch release contains several bugfixes and improvements.
+Kotlin 1.7.20 is used by default.
+
+### Improvements
+
+ * Add @MustBeDocumented to certain annotations (#2059)
+ * Deprecate .isNullable in SerialDescriptor builder (#2040)
+ * Unsigned primitives and unsigned arrays serializers can be retrieved as built-ins (#1992)
+ * Serializers are now cached inside reflective lookup, leading to faster serializer retrieval (#2015)
+ * Compiler plugin can create enum serializers using static factories for better speed (#1851) (Kotlin 1.7.20 required)
+ * Provide foundation for compiler plugin intrinsics available in Kotlin 1.8.0 (#2031)
+
+### Bugfixes
+
+ * Support polymorphism in Properties format (#2052) (thanks to [Rodrigo Vedovato](https://github.com/rodrigovedovato))
+ * Added support of UTF-16 surrogate pairs to okio streams (#2033)
+ * Fix dependency on core module from HOCON module (#2020) (thanks to [Osip Fatkullin](https://github.com/osipxd))
+
+
+1.4.0 / 2022-08-18
+==================
+
+This release contains all features and bugfixes from 1.4.0-RC plus some bugfixes on its own (see below).
+Kotlin 1.7.10 is used as a default.
+
+### Bugfixes
+ * Fixed decoding of huge JSON data for okio streams (#2006)
+
+
+1.4.0-RC / 2022-07-20
+==================
+
+This is a candidate for the next big release with many new exciting features to try.
+It uses Kotlin 1.7.10 by default.
+
+### Integration with Okio's BufferedSource and BufferedSink
+
+[Okio library by Square](https://square.github.io/okio/) is a popular solution for fast and efficient IO operations on JVM, K/N and K/JS.
+In this version, we have added functions that parse/write JSON directly to Okio's input/output classes, saving you the overhead of copying data to `String` beforehand.
+These functions are called `Json.decodeFromBufferedSource` and `Json.encodeToBufferedSink`, respectively.
+There's also `decodeBufferedSourceToSequence` that behaves similarly to `decodeToSequence` from Java streams integration, so you can lazily decode multiple objects the same way as before.
+
+Note that these functions are located in a separate new artifact, so users who don't need them wouldn't find themselves dependent on Okio.
+To include this artifact in your project, use the same group id `org.jetbrains.kotlinx` and artifact id `kotlinx-serialization-json-okio`.
+To find out more about this integration, check new functions' documentation and corresponding pull requests:
+[#1901](https://github.com/Kotlin/kotlinx.serialization/pull/1901) and [#1982](https://github.com/Kotlin/kotlinx.serialization/pull/1982).
+
+### Inline classes and unsigned numbers do not require experimental annotations anymore
+
+Inline classes and unsigned number types have been promoted to a Stable feature in Kotlin 1.5,
+and now we are promoting support for them in kotlinx.serialization to Stable status, too.
+To be precise, [we've removed all](https://github.com/Kotlin/kotlinx.serialization/pull/1963) `@ExperimentalSerializationApi` annotations from functions related to inline classes encoding and decoding,
+namely `SerialDescriptor.isInline`, `Encoder.encodeInline`, and some others. We've also updated related [documentation article](docs/value-classes.md).
+
+Additionally, all `@ExperimentalUnsignedTypes` annotations [were removed](https://github.com/Kotlin/kotlinx.serialization/pull/1962) completely,
+so you can freely use types such as `UInt` and their respective serializers as a stable feature
+without opt-in requirement.
+
+### Part of SerializationException's hierarchy is public now
+
+When kotlinx.serialization 1.0 was released, all subclasses of `SerializationException` were made internal,
+since they didn't provide helpful information besides the standard message.
+Since then, we've received a lot of feature requests with compelling use-cases for exposing some of these internal types to the public.
+In this release, we are starting to fulfilling these requests by making `MissingFieldException` public.
+One can use it in the `catch` clause to better understand the reasons of failure — for example, to return 400 instead of 500 from an HTTP server —
+and then use its `fields` property to communicate the message better.
+See the details in the corresponding [PR](https://github.com/Kotlin/kotlinx.serialization/pull/1983).
+
+In future releases, we'll continue work in this direction, and we aim to provide more useful public exception types & properties.
+In the meantime, we've [revamped KDoc](https://github.com/Kotlin/kotlinx.serialization/pull/1980) for some methods regarding the exceptions — all of them now properly declare which exception types are allowed to be thrown.
+For example, `KSerializer.deserialize` is documented to throw `IllegalStateException` to indicate problems unrelated to serialization, such as data validation in classes' constructors.
+
+### @MetaSerializable annotation
+
+This release introduces a new `@MetaSerializable` annotation that adds `@Serializable` behavior to user-defined annotations — i.e., those annotations would also instruct the compiler plugin to generate a serializer for class. In addition, all annotations marked with `@MetaSerializable` are saved in the generated `@SerialDescriptor`
+as if they are annotated with `@SerialInfo`.
+
+This annotation will be particularly useful for various format authors who require adding some metadata to the serializable class — this can now be done using a single annotation instead of two, and without the risk of forgetting `@Serializable`. Check out details & examples in the KDoc and corresponding [PR](https://github.com/Kotlin/kotlinx.serialization/pull/1979).
+
+> Note: Kotlin 1.7.0 or higher is required for this feature to work.
+
+### Moving documentation from GitHub pages to kotlinlang.org
+
+As a part of a coordinated effort to unify kotlinx libraries users' experience, Dokka-generated documentation pages (KDoc) were moved from https://kotlin.github.io/kotlinx.serialization/ to https://kotlinlang.org/api/kotlinx.serialization/. No action from you is required — there are proper redirects at the former address, so there is no need to worry about links in your blogpost getting obsolete or broken.
+
+Note that this move does not affect guides written in Markdown in the `docs` folder. We aim to move them later, enriching text with runnable examples as in the Kotlin language guides.
+
+### Other improvements
+
+ * Allow Kotlin's null literal in JSON DSL (#1907) (thanks to [Lukellmann](https://github.com/Lukellmann))
+ * Stabilize EmptySerializersModule (#1921)
+ * Boost performance of polymorphic deserialization in optimistic scenario (#1919)
+ * Added serializer for the `kotlin.time.Duration` class (plugin support comes in Kotlin 1.7.20) (#1960)
+ * Support tagged not null marks in TaggedEncoder/Decoder (#1954) (thanks to [EdwarDDay](https://github.com/EdwarDDay))
+
+### Bugfixes
+
+ * Support quoting unsigned integers when used as map keys (#1969)
+ * Fix protocol buffer enum schema generation (#1967) (thanks to [mogud](https://github.com/mogud))
+ * Support diamond inheritance of sealed interfaces in SealedClassSerializer (#1958)
+ * Support retrieving serializer for sealed interface (#1968)
+ * Fix misleading token description in JSON errors (#1941) (thanks to [TheMrMilchmann](https://github.com/TheMrMilchmann))
+
1.3.3 / 2022-05-11
==================
@@ -26,7 +386,7 @@
* Iterate over element indices in ObjectSerializer in order to let the format skip unknown keys (#1916)
* Correctly support registering both default polymorphic serializer & deserializer (#1849)
* Make error message for captured generic type parameters much more straightforward (#1863)
-
+
1.3.2 / 2021-12-23
==================
@@ -297,7 +657,7 @@
Unsigned integer types (`UByte`, `UShort`, `UInt` and `ULong`) are serializable as well and have special support in JSON.
This feature requires Kotlin compiler 1.4.30-RC and enabling new IR compilers for [JS](https://kotlinlang.org/docs/reference/js-ir-compiler.html) and [JVM](https://kotlinlang.org/docs/reference/whatsnew14.html#new-jvm-ir-backend).
-You can learn more in the [documentation](docs/inline-classes.md)
+You can learn more in the [documentation](docs/value-classes.md)
and corresponding [pull request](https://github.com/Kotlin/kotlinx.serialization/pull/1244).
### Other features
@@ -343,7 +703,7 @@
The first public stable release, yay!
The definitions of stability and backwards compatibility guarantees are located in the [corresponding document](docs/compatibility.md).
-We now also have a GitHub Pages site with [full API reference](https://kotlin.github.io/kotlinx.serialization/).
+We now also have a GitHub Pages site with [full API reference](https://kotlinlang.org/api/kotlinx.serialization/).
Compared to RC2, no new features apart from #947 were added and all previously deprecated declarations and migrations were deleted.
If you are using RC/RC2 along with deprecated declarations, please, migrate before updating to 1.0.0.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6cd235d..34621f7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,7 +22,7 @@
* Use a 'feature request' template when creating a new issue.
* Explain why you need the feature — what's your use-case, what's your domain.
* Explaining the problem you face is more important than suggesting a solution.
- Report your problem even if you don't have any proposed solution.
+ Even if you don't have a proposed solution, please report your problem.
* If there is an alternative way to do what you need, then show the code of the alternative.
## Submitting PRs
@@ -40,7 +40,7 @@
* If you fix documentation:
* After fixing/changing code examples in the [`docs`](docs) folder or updating any references in the markdown files
run the [Knit tool](#running-the-knit-tool) and commit the resulting changes as well.
- It will not pass the tests otherwise.
+ Your changes will not pass the tests otherwise.
* If you plan extensive rewrites/additions to the docs, then please [contact the maintainers](#contacting-maintainers)
to coordinate the work in advance.
* If you make any code changes:
@@ -49,7 +49,7 @@
* Use imports with '*'.
* [Build the project](#building) to make sure it all works and passes the tests.
* If you fix a bug:
- * Write the test the reproduces the bug.
+ * Write the test that reproduces the bug.
* Fixes without tests are accepted only in exceptional circumstances if it can be shown that writing the
corresponding test is too hard or otherwise impractical.
* Follow the style of writing tests that is used in this project:
@@ -69,7 +69,7 @@
* You can submit a PR to the [list of community-supported formats](formats/README.md#other-community-supported-formats)
with a description of your library.
* Comment on the existing issue if you want to work on it. Ensure that the issue not only describes a problem,
- but also describes a solution that had received a positive feedback. Propose a solution if there isn't any.
+ but also describes a solution that has received positive feedback. Propose a solution if there isn't any.
## Building
diff --git a/README.md b/README.md
index 31a198a..d69420a 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,12 @@
# Kotlin multiplatform / multi-format reflectionless serialization
-[![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+[![Kotlin Stable](https://kotl.in/badges/stable.svg)](https://kotlinlang.org/docs/components-stability.html)
+[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0)
[![TeamCity build](https://img.shields.io/teamcity/http/teamcity.jetbrains.com/s/KotlinTools_KotlinxSerialization_Ko.svg)](https://teamcity.jetbrains.com/viewType.html?buildTypeId=KotlinTools_KotlinxSerialization_Ko&guest=1)
-[![Kotlin](https://img.shields.io/badge/kotlin-1.6.10-blue.svg?logo=kotlin)](http://kotlinlang.org)
-[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-serialization-core/1.3.3)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-serialization-core/1.3.3/pom)
-[![KDoc link](https://img.shields.io/badge/API_reference-KDoc-blue)](https://kotlin.github.io/kotlinx.serialization/)
+[![Kotlin](https://img.shields.io/badge/kotlin-1.9.22-blue.svg?logo=kotlin)](http://kotlinlang.org)
+[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-serialization-core/1.6.3)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-serialization-core/1.6.3)
+[![KDoc link](https://img.shields.io/badge/API_reference-KDoc-blue)](https://kotlinlang.org/api/kotlinx.serialization/)
[![Slack channel](https://img.shields.io/badge/chat-slack-blue.svg?logo=slack)](https://kotlinlang.slack.com/messages/serialization/)
Kotlin serialization consists of a compiler plugin, that generates visitor code for serializable classes,
@@ -22,18 +23,20 @@
* [Introduction and references](#introduction-and-references)
* [Setup](#setup)
* [Gradle](#gradle)
- * [Using the `plugins` block](#using-the-plugins-block)
- * [Using `apply plugin` (the old way)](#using-apply-plugin-the-old-way)
- * [Dependency on the JSON library](#dependency-on-the-json-library)
+ * [1) Setting up the serialization plugin](#1-setting-up-the-serialization-plugin)
+ * [2) Dependency on the JSON library](#2-dependency-on-the-json-library)
* [Android](#android)
* [Multiplatform (Common, JS, Native)](#multiplatform-common-js-native)
* [Maven](#maven)
+ * [Bazel](#bazel)
<!--- END -->
* **Additional links**
* [Kotlin Serialization Guide](docs/serialization-guide.md)
- * [Full API reference](https://kotlin.github.io/kotlinx.serialization/)
+ * [Full API reference](https://kotlinlang.org/api/kotlinx.serialization/)
+ * [Submitting issues and PRs](CONTRIBUTING.md)
+ * [Building this library](docs/building.md)
## Introduction and references
@@ -68,28 +71,32 @@
**Read the [Kotlin Serialization Guide](docs/serialization-guide.md) for all details.**
-You can find auto-generated documentation website on [GitHub Pages](https://kotlin.github.io/kotlinx.serialization/).
+You can find auto-generated documentation website on [kotlinlang.org](https://kotlinlang.org/api/kotlinx.serialization/).
## Setup
-Kotlin serialization plugin is shipped with the Kotlin compiler distribution, and the IDEA plugin is bundled into the Kotlin plugin.
+[New versions](https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.serialization) of the serialization plugin are released in tandem with each new Kotlin compiler version.
Using Kotlin Serialization requires Kotlin compiler `1.4.0` or higher.
Make sure you have the corresponding Kotlin plugin installed in the IDE, no additional plugins for IDE are required.
### Gradle
-#### Using the `plugins` block
+To set up kotlinx.serialization, you have to do two things:
+1) Add the **[serialization plugin](#1-setting-up-the-serialization-plugin)**.
+2) Add the **[serialization library dependency](#2-dependency-on-the-json-library)**.
-You can set up the serialization plugin with the Kotlin plugin using
+#### 1) Setting up the serialization plugin
+
+You can set up the serialization plugin with the Kotlin plugin using the
[Gradle plugins DSL](https://docs.gradle.org/current/userguide/plugins.html#sec:plugins_block):
Kotlin DSL:
```kotlin
plugins {
- kotlin("jvm") version "1.6.10" // or kotlin("multiplatform") or any other kotlin plugin
- kotlin("plugin.serialization") version "1.6.10"
+ kotlin("jvm") version "1.9.22" // or kotlin("multiplatform") or any other kotlin plugin
+ kotlin("plugin.serialization") version "1.9.22"
}
```
@@ -97,14 +104,15 @@
```gradle
plugins {
- id 'org.jetbrains.kotlin.multiplatform' version '1.6.10'
- id 'org.jetbrains.kotlin.plugin.serialization' version '1.6.10'
+ id 'org.jetbrains.kotlin.multiplatform' version '1.9.22'
+ id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.22'
}
```
-> Kotlin versions before 1.4.0 are not supported by the stable release of Kotlin serialization
+> Kotlin versions before 1.4.0 are not supported by the stable release of Kotlin serialization.
-#### Using `apply plugin` (the old way)
+<details>
+ <summary>Using <code>apply plugin</code> (the old way)</summary>
First, you have to add the serialization plugin to your classpath as the other [compiler plugins](https://kotlinlang.org/docs/reference/compiler-plugins.html):
@@ -115,7 +123,7 @@
repositories { mavenCentral() }
dependencies {
- val kotlinVersion = "1.6.10"
+ val kotlinVersion = "1.9.22"
classpath(kotlin("gradle-plugin", version = kotlinVersion))
classpath(kotlin("serialization", version = kotlinVersion))
}
@@ -126,7 +134,7 @@
```gradle
buildscript {
- ext.kotlin_version = '1.6.10'
+ ext.kotlin_version = '1.9.22'
repositories { mavenCentral() }
dependencies {
@@ -141,10 +149,11 @@
apply plugin: 'kotlin' // or 'kotlin-multiplatform' for multiplatform projects
apply plugin: 'kotlinx-serialization'
```
+</details>
-#### Dependency on the JSON library
+#### 2) Dependency on the JSON library
-After setting up the plugin one way or another, you have to add a dependency on the serialization library.
+After setting up the plugin, you have to add a dependency on the serialization library.
Note that while the plugin has version the same as the compiler one, runtime library has different coordinates, repository and versioning.
Kotlin DSL:
@@ -155,7 +164,7 @@
}
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}
```
@@ -167,104 +176,76 @@
}
dependencies {
- implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3"
+ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
}
```
->We also provide `kotlinx-serialization-core` artifact that contains all serialization API but does not have bundled serialization format with it
+>We also provide `kotlinx-serialization-core` artifact that contains all serialization API but does not have a bundled serialization format with it
### Android
-The library works on Android, but, if you're using ProGuard,
-you need to add rules to your `proguard-rules.pro` configuration to cover all classes that are serialized at runtime.
+By default, proguard rules are supplied with the library.
+[These rules](rules/common.pro) keep serializers for _all_ serializable classes that are retained after shrinking,
+so you don't need additional setup.
-The following configuration keeps serializers for _all_ serializable classes that are retained after shrinking.
-Uncomment and modify the last section in case you're serializing classes with named companion objects.
+**However, these rules do not affect serializable classes if they have named companion objects.**
-```proguard
-# Keep `Companion` object fields of serializable classes.
-# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
--if @kotlinx.serialization.Serializable class **
--keepclassmembers class <1> {
- static <1>$Companion Companion;
-}
+If you want to serialize classes with named companion objects, you need to add and edit rules below to your `proguard-rules.pro` configuration.
-# Keep `serializer()` on companion objects (both default and named) of serializable classes.
--if @kotlinx.serialization.Serializable class ** {
- static **$* *;
-}
--keepclassmembers class <2>$<3> {
- kotlinx.serialization.KSerializer serializer(...);
-}
-
-# Keep `INSTANCE.serializer()` of serializable objects.
--if @kotlinx.serialization.Serializable class ** {
- public static ** INSTANCE;
-}
--keepclassmembers class <1> {
- public static <1> INSTANCE;
- kotlinx.serialization.KSerializer serializer(...);
-}
-
-# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
--keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-
-# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
-# If you have any, uncomment and replace classes with those containing named companion objects.
-#-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
-#-if @kotlinx.serialization.Serializable class
-#com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
-#com.example.myapplication.HasNamedCompanion2
-#{
-# static **$* *;
-#}
-#-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
-# static <1>$$serializer INSTANCE;
-#}
-```
-
-In case you want to exclude serializable classes that are used, but never serialized at runtime,
-you will need to write custom rules with narrower [class specifications](https://www.guardsquare.com/manual/configuration/usage).
+Note that the rules for R8 differ depending on the [compatibility mode](https://r8.googlesource.com/r8/+/refs/heads/master/compatibility-faq.md) used.
<details>
-<summary>Example of custom rules</summary>
-
+<summary>Example of named companion rules for ProGuard and R8 compatibility mode</summary>
+
```proguard
--keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-
-# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
--keepclassmembers class kotlinx.serialization.json.** {
- *** Companion;
-}
--keepclasseswithmembers class kotlinx.serialization.json.** {
- kotlinx.serialization.KSerializer serializer(...);
-}
-
-# Application rules
-
-# Change here com.yourcompany.yourpackage
--keepclassmembers @kotlinx.serialization.Serializable class com.yourcompany.yourpackage.** {
- # lookup for plugin generated serializable classes
- *** Companion;
- # lookup for serializable objects
- *** INSTANCE;
- kotlinx.serialization.KSerializer serializer(...);
-}
-# lookup for plugin generated serializable classes
--if @kotlinx.serialization.Serializable class com.yourcompany.yourpackage.**
--keepclassmembers class com.yourcompany.yourpackage.<1>$Companion {
- kotlinx.serialization.KSerializer serializer(...);
-}
-
-# Serialization supports named companions but for such classes it is necessary to add an additional rule.
-# This rule keeps serializer and serializable class from obfuscation. Therefore, it is recommended not to use wildcards in it, but to write rules for each such class.
+# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
+# If you have any, replace classes with those containing named companion objects.
-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
--keep class com.yourcompany.yourpackage.SerializableClassWithNamedCompanion$$serializer {
- *** INSTANCE;
+
+-if @kotlinx.serialization.Serializable class
+com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
+com.example.myapplication.HasNamedCompanion2
+{
+ static **$* *;
+}
+-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
+ static <1>$$serializer INSTANCE;
}
```
</details>
+
+<details>
+<summary>Example of named companion rules for R8 full mode</summary>
+
+```proguard
+# Serializer for classes with named companion objects are retrieved using `getDeclaredClasses`.
+# If you have any, replace classes with those containing named companion objects.
+-keepattributes InnerClasses # Needed for `getDeclaredClasses`.
+
+-if @kotlinx.serialization.Serializable class
+com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
+com.example.myapplication.HasNamedCompanion2
+{
+ static **$* *;
+}
+-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
+ static <1>$$serializer INSTANCE;
+}
+
+# Keep both serializer and serializable classes to save the attribute InnerClasses
+-keepclasseswithmembers, allowshrinking, allowobfuscation, allowaccessmodification class
+com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
+com.example.myapplication.HasNamedCompanion2
+{
+ *;
+}
+```
+</details>
+
+In case you want to exclude serializable classes that are used, but never serialized at runtime,
+you will need to write custom rules with narrower [class specifications](https://www.guardsquare.com/manual/configuration/usage).
+
### Multiplatform (Common, JS, Native)
Most of the modules are also available for Kotlin/JS and Kotlin/Native.
@@ -285,8 +266,8 @@
```xml
<properties>
- <kotlin.version>1.6.10</kotlin.version>
- <serialization.version>1.3.3</serialization.version>
+ <kotlin.version>1.9.22</kotlin.version>
+ <serialization.version>1.6.3</serialization.version>
</properties>
```
@@ -334,3 +315,9 @@
<version>${serialization.version}</version>
</dependency>
```
+
+### Bazel
+
+To setup the Kotlin compiler plugin for Bazel, follow [the
+example](https://github.com/bazelbuild/rules_kotlin/tree/master/examples/plugin/src/serialization)
+from the `rules_kotlin` repository.
diff --git a/RELEASING.md b/RELEASING.md
index 5b95f0d..f8b90e9 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -41,8 +41,8 @@
* Close the repository and wait for it to verify.
* Release it.
-5. Update documentation website:<br>
- `./update_docs.sh <version> push`
+5. Set a new value for [`KOTLINX_SERIALIZATION_RELEASE_TAG`](https://github.com/JetBrains/kotlin-web-site/blob/master/.teamcity/BuildParams.kt),
+ creating a pull request in the website's repository. To find out why it is needed, [read this](#kotlinxserializationreleasetag).
6. Create a new release in [Github releases](https://github.com/Kotlin/kotlinx.serialization/releases). Use created git tag for title and changelog message for body.
@@ -54,3 +54,25 @@
```
5. Announce new release in [Slack](https://kotlinlang.slack.com).
+
+# API reference documentation
+
+The [API reference documentation](https://kotlinlang.org/api/kotlinx.serialization/) is built and deployed automatically
+for every commit in `master`, typically within the same day.
+
+**Note**: KDoc / API reference changes targeting `master` should not contain information which is irrelevant to or is
+incorrect in relation to the latest release, because these changes will be deployed live automatically, and they might
+confuse readers.
+
+The build configuration responsible for assembling the documentation can be found
+[on TeamCity](https://buildserver.labs.intellij.net/buildConfiguration/Kotlin_KotlinSites_KotlinlangTeamcityDsl_KotlinxSerializationBuildApiReference).
+
+### KOTLINX_SERIALIZATION_RELEASE_TAG
+
+The generated API reference documentation has the library version specified in the header. By default, the value
+of the `version` project property is taken. However, this property usually contains the upcoming version with
+the `-SNAPSHOT` suffix, so it cannot be used if you want to publish the updated documentation of the latest release.
+
+For this reason, the [`KOTLINX_SERIALIZATION_RELEASE_TAG`](https://github.com/JetBrains/kotlin-web-site/blob/master/.teamcity/BuildParams.kt)
+property must be set during every release: its value will be used for all subsequent publications of the API docs to kotlinlang.org,
+and it will appear in the header.
diff --git a/benchmark/build.gradle b/benchmark/build.gradle
index 8e0e492..751ad78 100644
--- a/benchmark/build.gradle
+++ b/benchmark/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -6,27 +8,45 @@
apply plugin: 'kotlin'
apply plugin: 'kotlinx-serialization'
apply plugin: 'idea'
-apply plugin: 'net.ltgt.apt'
apply plugin: 'com.github.johnrengelman.shadow'
-apply plugin: 'me.champeau.gradle.jmh'
+apply plugin: 'me.champeau.jmh'
sourceCompatibility = 1.8
targetCompatibility = 1.8
-jmh.jmhVersion = 1.22
+jmh.jmhVersion = "1.35"
+
+processJmhResources {
+ doFirst {
+ duplicatesStrategy(DuplicatesStrategy.EXCLUDE)
+ }
+}
jmhJar {
- baseName 'benchmarks'
- classifier = null
- version = null
- destinationDir = file("$rootDir")
+ archiveBaseName.set('benchmarks')
+ archiveVersion.set('')
+ destinationDirectory = file("$rootDir")
+}
+
+// to include benchmark-module jmh source set compilation during build to verify that it is also compiled succesfully
+assemble.dependsOn jmhClasses
+
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ if (rootProject.ext.kotlin_lv_override != null) {
+ languageVersion = rootProject.ext.kotlin_lv_override
+ freeCompilerArgs += "-Xsuppress-version-warnings"
+ }
+ }
}
dependencies {
- implementation 'org.openjdk.jmh:jmh-core:1.22'
- implementation 'com.google.guava:guava:24.1.1-jre'
- implementation 'com.fasterxml.jackson.core:jackson-databind:2.12.1'
- implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.12.1'
+ implementation 'org.openjdk.jmh:jmh-core:1.35'
+ implementation 'com.google.guava:guava:31.1-jre'
+ implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.3'
+ implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.13.3'
+ implementation "com.squareup.okio:okio:$okio_version"
implementation project(':kotlinx-serialization-core')
implementation project(':kotlinx-serialization-json')
+ implementation project(':kotlinx-serialization-json-okio')
implementation project(':kotlinx-serialization-protobuf')
}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt
new file mode 100644
index 0000000..2773cfc
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt
@@ -0,0 +1,72 @@
+package kotlinx.benchmarks.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.descriptors.element
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class ContextualOverheadBenchmark {
+ @Serializable
+ data class Holder(val data: @Contextual Data)
+
+ class Data(val a: Int, val b: String)
+
+ object DataSerializer: KSerializer<Data> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Serializer") {
+ element<Int>("a")
+ element<String>("b")
+ }
+
+ override fun deserialize(decoder: Decoder): Data {
+ return decoder.decodeStructure(descriptor) {
+ var a = 0
+ var b = ""
+ while (true) {
+ when (val index = decodeElementIndex(descriptor)) {
+ 0 -> a = decodeIntElement(descriptor, 0)
+ 1 -> b = decodeStringElement(descriptor, 1)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ Data(a, b)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: Data) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.a)
+ encodeStringElement(descriptor, 1, value.b)
+ }
+ }
+
+ }
+
+ private val module = SerializersModule {
+ contextual(DataSerializer)
+ }
+
+ private val json = Json { serializersModule = module }
+
+ private val holder = Holder(Data(1, "abc"))
+ private val holderString = json.encodeToString(holder)
+ private val holderSerializer = serializer<Holder>()
+
+ @Benchmark
+ fun decode() = json.decodeFromString(holderSerializer, holderString)
+
+ @Benchmark
+ fun encode() = json.encodeToString(holderSerializer, holder)
+
+}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt
index b812500..d162418 100644
--- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt
@@ -4,7 +4,12 @@
import com.fasterxml.jackson.module.kotlin.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
+import kotlinx.serialization.json.okio.encodeToBufferedSink
+import okio.blackholeSink
+import okio.buffer
import org.openjdk.jmh.annotations.*
+import java.io.ByteArrayInputStream
+import java.io.OutputStream
import java.util.concurrent.*
@Warmup(iterations = 7, time = 1)
@@ -63,7 +68,15 @@
cookies = "_ga=GA1.2.971852807.1546968515"
)
+ private val devNullSink = blackholeSink().buffer()
+ private val devNullStream = object : OutputStream() {
+ override fun write(b: Int) {}
+ override fun write(b: ByteArray) {}
+ override fun write(b: ByteArray, off: Int, len: Int) {}
+ }
+
private val stringData = Json.encodeToString(DefaultPixelEvent.serializer(), data)
+ private val utf8BytesData = stringData.toByteArray()
@Serializable
private class SmallDataClass(val id: Int, val name: String)
@@ -83,12 +96,27 @@
fun kotlinToString(): String = Json.encodeToString(DefaultPixelEvent.serializer(), data)
@Benchmark
+ fun kotlinToStream() = Json.encodeToStream(DefaultPixelEvent.serializer(), data, devNullStream)
+
+ @Benchmark
+ fun kotlinFromStream() = Json.decodeFromStream(DefaultPixelEvent.serializer(), ByteArrayInputStream(utf8BytesData))
+
+ @Benchmark
+ fun kotlinToOkio() = Json.encodeToBufferedSink(DefaultPixelEvent.serializer(), data, devNullSink)
+
+ @Benchmark
fun kotlinToStringWithEscapes(): String = Json.encodeToString(DefaultPixelEvent.serializer(), dataWithEscapes)
@Benchmark
fun kotlinSmallToString(): String = Json.encodeToString(SmallDataClass.serializer(), smallData)
@Benchmark
+ fun kotlinSmallToStream() = Json.encodeToStream(SmallDataClass.serializer(), smallData, devNullStream)
+
+ @Benchmark
+ fun kotlinSmallToOkio() = Json.encodeToBufferedSink(SmallDataClass.serializer(), smallData, devNullSink)
+
+ @Benchmark
fun jacksonFromString(): DefaultPixelEvent = objectMapper.readValue(stringData, DefaultPixelEvent::class.java)
@Benchmark
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt
new file mode 100644
index 0000000..ddfc579
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.benchmarks.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.json.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 7, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Benchmark)
+@Fork(2)
+open class LookupOverheadBenchmark {
+
+ @Serializable
+ class Holder(val a: String)
+
+ @Serializable
+ class Generic<T>(val a: T)
+
+ @Serializable
+ class DoubleGeneric<T1, T2>(val a: T1, val b: T2)
+
+ @Serializable
+ class PentaGeneric<T1, T2, T3, T4, T5>(val a: T1, val b: T2, val c: T3, val d: T4, val e: T5)
+
+ private val data = """{"a":""}"""
+ private val doubleData = """{"a":"","b":0}"""
+ private val pentaData = """{"a":"","b":0,"c":1,"d":true,"e":" "}"""
+
+ @Serializable
+ object Object
+
+ @Benchmark
+ fun dataReified() = Json.decodeFromString<Holder>(data)
+
+ @Benchmark
+ fun dataPlain() = Json.decodeFromString(Holder.serializer(), data)
+
+ @Benchmark
+ fun genericReified() = Json.decodeFromString<Generic<String>>(data)
+
+ @Benchmark
+ fun genericPlain() = Json.decodeFromString(Generic.serializer(String.serializer()), data)
+
+ @Benchmark
+ fun doubleGenericReified() = Json.decodeFromString<DoubleGeneric<String, Int>>(doubleData)
+
+ @Benchmark
+ fun doubleGenericPlain() = Json.decodeFromString(DoubleGeneric.serializer(String.serializer(), Int.serializer()), doubleData)
+
+ @Benchmark
+ fun pentaGenericReified() = Json.decodeFromString<PentaGeneric<String, Int, Long, Boolean, Char>>(pentaData)
+
+ @Benchmark
+ fun pentaGenericPlain() = Json.decodeFromString(PentaGeneric.serializer(String.serializer(), Int.serializer(), Long.serializer(), Boolean.serializer(), Char.serializer()), pentaData)
+
+ @Benchmark
+ fun objectReified() = Json.decodeFromString<Object>("{}")
+
+ @Benchmark
+ fun objectPlain() = Json.decodeFromString(Object.serializer(), "{}")
+}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt
new file mode 100644
index 0000000..9af856d
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt
@@ -0,0 +1,70 @@
+package kotlinx.benchmarks.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class PolymorphismOverheadBenchmark {
+
+ @Serializable
+ @JsonClassDiscriminator("poly")
+ data class PolymorphicWrapper(val i: @Polymorphic Poly, val i2: Impl) // amortize the cost a bit
+
+ @Serializable
+ data class SimpleWrapper(val poly: @Polymorphic Poly)
+
+ @Serializable
+ data class BaseWrapper(val i: Impl, val i2: Impl)
+
+ @JsonClassDiscriminator("poly")
+ interface Poly
+
+ @Serializable
+ @JsonClassDiscriminator("poly")
+ class Impl(val a: Int, val b: String) : Poly
+
+ private val impl = Impl(239, "average_size_string")
+ private val module = SerializersModule {
+ polymorphic(Poly::class) {
+ subclass(Impl.serializer())
+ }
+ }
+
+ private val json = Json { serializersModule = module }
+ private val implString = json.encodeToString(impl)
+ private val polyString = json.encodeToString<Poly>(impl)
+ private val serializer = serializer<Poly>()
+
+ private val wrapper = SimpleWrapper(Impl(1, "abc"))
+ private val wrapperString = json.encodeToString(wrapper)
+ private val wrapperSerializer = serializer<SimpleWrapper>()
+
+
+ // 5000
+ @Benchmark
+ fun base() = json.decodeFromString(Impl.serializer(), implString)
+
+ // As of 1.3.x
+ // Baseline -- 1500
+ // v1, no skip -- 2000
+ // v2, with skip -- 3000 [withdrawn]
+ @Benchmark
+ fun poly() = json.decodeFromString(serializer, polyString)
+
+ // test for child polymorphic serializer in decoding
+ @Benchmark
+ fun polyChildDecode() = json.decodeFromString(wrapperSerializer, wrapperString)
+
+ // test for child polymorphic serializer in encoding
+ @Benchmark
+ fun polyChildEncode() = json.encodeToString(wrapperSerializer, wrapper)
+
+}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt
index 5c930ec..90889fe 100644
--- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt
@@ -3,6 +3,7 @@
import kotlinx.benchmarks.model.*
import kotlinx.serialization.json.*
import org.openjdk.jmh.annotations.*
+import java.io.OutputStream
import java.util.concurrent.*
@Warmup(iterations = 7, time = 1)
@@ -24,6 +25,12 @@
private val jsonImplicitNulls = Json { explicitNulls = false }
+ private val devNullStream = object : OutputStream() {
+ override fun write(b: Int) {}
+ override fun write(b: ByteArray) {}
+ override fun write(b: ByteArray, off: Int, len: Int) {}
+ }
+
@Setup
fun init() {
require(twitter == Json.decodeFromString(Twitter.serializer(), Json.encodeToString(Twitter.serializer(), twitter)))
@@ -38,4 +45,7 @@
@Benchmark
fun encodeTwitter() = Json.encodeToString(Twitter.serializer(), twitter)
+
+ @Benchmark
+ fun encodeTwitterStream() = Json.encodeToStream(Twitter.serializer(), twitter, devNullStream)
}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt
index e015ad9..837a8ba 100644
--- a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt
@@ -3,8 +3,6 @@
import kotlinx.benchmarks.model.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.json.Json.Default.decodeFromString
-import kotlinx.serialization.json.Json.Default.encodeToString
import org.openjdk.jmh.annotations.*
import java.util.concurrent.*
@@ -24,19 +22,25 @@
*/
private val input = TwitterFeedBenchmark::class.java.getResource("/twitter_macro.json").readBytes().decodeToString()
private val twitter = Json.decodeFromString(MacroTwitterFeed.serializer(), input)
+
private val jsonNoAltNames = Json { useAlternativeNames = false }
private val jsonIgnoreUnknwn = Json { ignoreUnknownKeys = true }
- private val jsonIgnoreUnknwnNoAltNames = Json { ignoreUnknownKeys = true; useAlternativeNames = false}
+ private val jsonIgnoreUnknwnNoAltNames = Json { ignoreUnknownKeys = true; useAlternativeNames = false }
+ private val jsonNamingStrategy = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
+ private val jsonNamingStrategyIgnoreUnknwn = Json(jsonNamingStrategy) { ignoreUnknownKeys = true }
+
+ private val twitterKt = jsonNamingStrategy.decodeFromString(MacroTwitterFeedKt.serializer(), input)
@Setup
fun init() {
require(twitter == Json.decodeFromString(MacroTwitterFeed.serializer(), Json.encodeToString(MacroTwitterFeed.serializer(), twitter)))
}
- // Order of magnitude: ~400 op/s
+ // Order of magnitude: ~500 op/s
@Benchmark
fun decodeTwitter() = Json.decodeFromString(MacroTwitterFeed.serializer(), input)
+ // Should be the same as decodeTwitter, since decodeTwitter never hit unknown name and therefore should never build deserializationNamesMap anyway
@Benchmark
fun decodeTwitterNoAltNames() = jsonNoAltNames.decodeFromString(MacroTwitterFeed.serializer(), input)
@@ -46,7 +50,20 @@
@Benchmark
fun decodeMicroTwitter() = jsonIgnoreUnknwn.decodeFromString(MicroTwitterFeed.serializer(), input)
+ // Should be faster than decodeMicroTwitter, as we explicitly opt-out from deserializationNamesMap on unknown name
@Benchmark
fun decodeMicroTwitterNoAltNames() = jsonIgnoreUnknwnNoAltNames.decodeFromString(MicroTwitterFeed.serializer(), input)
+ // Should be just a bit slower than decodeMicroTwitter, because alternative names map is created in both cases
+ @Benchmark
+ fun decodeMicroTwitterWithNamingStrategy(): MicroTwitterFeedKt = jsonNamingStrategyIgnoreUnknwn.decodeFromString(MicroTwitterFeedKt.serializer(), input)
+
+ // Can be slower than decodeTwitter, as we always build deserializationNamesMap when naming strategy is used
+ @Benchmark
+ fun decodeTwitterWithNamingStrategy(): MacroTwitterFeedKt = jsonNamingStrategy.decodeFromString(MacroTwitterFeedKt.serializer(), input)
+
+ // 15-20% slower than without the strategy. Without serializationNamesMap (invoking strategy on every write), up to 50% slower
+ @Benchmark
+ fun encodeTwitterWithNamingStrategy(): String = jsonNamingStrategy.encodeToString(MacroTwitterFeedKt.serializer(), twitterKt)
+
}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt
new file mode 100644
index 0000000..bc3c89d
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt
@@ -0,0 +1,123 @@
+@file:UseSerializers(UseSerializerOverheadBenchmark.DataClassSerializer::class, UseSerializerOverheadBenchmark.DataObjectSerializer::class)
+package kotlinx.benchmarks.json
+
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.buildClassSerialDescriptor
+import kotlinx.serialization.descriptors.element
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import org.openjdk.jmh.annotations.*
+import java.util.concurrent.*
+
+@Warmup(iterations = 7, time = 1)
+@Measurement(iterations = 5, time = 1)
+@BenchmarkMode(Mode.Throughput)
+@OutputTimeUnit(TimeUnit.MILLISECONDS)
+@State(Scope.Benchmark)
+@Fork(1)
+open class UseSerializerOverheadBenchmark {
+ @Serializable
+ data class HolderForClass(val data: DataForClass)
+
+ @Serializable
+ data class HolderForObject(val data: DataForObject)
+
+ class DataForClass(val a: Int, val b: String)
+
+ class DataForObject(val a: Int, val b: String)
+
+ object DataClassSerializer: KSerializer<DataForClass> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ClassSerializer") {
+ element<Int>("a")
+ element<String>("b")
+ }
+
+ override fun deserialize(decoder: Decoder): DataForClass {
+ return decoder.decodeStructure(descriptor) {
+ var a = 0
+ var b = ""
+ while (true) {
+ when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) {
+ 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0)
+ 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ DataForClass(a, b)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: DataForClass) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.a)
+ encodeStringElement(descriptor, 1, value.b)
+ }
+ }
+ }
+
+ object DataObjectSerializer: KSerializer<DataForObject> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("ObjectSerializer") {
+ element<Int>("a")
+ element<String>("b")
+ }
+
+ override fun deserialize(decoder: Decoder): DataForObject {
+ return decoder.decodeStructure(descriptor) {
+ var a = 0
+ var b = ""
+ while (true) {
+ when (val index = decodeElementIndex(ContextualOverheadBenchmark.DataSerializer.descriptor)) {
+ 0 -> a = decodeIntElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 0)
+ 1 -> b = decodeStringElement(ContextualOverheadBenchmark.DataSerializer.descriptor, 1)
+ CompositeDecoder.DECODE_DONE -> break
+ else -> error("Unexpected index: $index")
+ }
+ }
+ DataForObject(a, b)
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: DataForObject) {
+ encoder.encodeStructure(descriptor) {
+ encodeIntElement(descriptor, 0, value.a)
+ encodeStringElement(descriptor, 1, value.b)
+ }
+ }
+ }
+
+ private val module = SerializersModule {
+ contextual(DataClassSerializer)
+ }
+
+ private val json = Json { serializersModule = module }
+
+ private val classHolder = HolderForClass(DataForClass(1, "abc"))
+ private val classHolderString = json.encodeToString(classHolder)
+ private val classHolderSerializer = serializer<HolderForClass>()
+
+ private val objectHolder = HolderForObject(DataForObject(1, "abc"))
+ private val objectHolderString = json.encodeToString(objectHolder)
+ private val objectHolderSerializer = serializer<HolderForObject>()
+
+ @Benchmark
+ fun decodeForClass() = json.decodeFromString(classHolderSerializer, classHolderString)
+
+ @Benchmark
+ fun encodeForClass() = json.encodeToString(classHolderSerializer, classHolder)
+
+ /*
+ Any optimizations should not affect the speed of these tests.
+ It doesn't make sense to cache singleton (`object`) serializer, because the object is accessed instantly
+ */
+
+ @Benchmark
+ fun decodeForObject() = json.decodeFromString(objectHolderSerializer, objectHolderString)
+
+ @Benchmark
+ fun encodeForObject() = json.encodeToString(objectHolderSerializer, objectHolder)
+
+}
diff --git a/benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt
new file mode 100644
index 0000000..fca69cc
--- /dev/null
+++ b/benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt
@@ -0,0 +1,170 @@
+package kotlinx.benchmarks.model
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+/**
+ * All model classes are the same as in MacroTwitter.kt but named accordingly to Kotlin naming policies to test JsonNamingStrategy performance.
+ * Only Size, SizeType and Urls are not copied
+ */
+
+@Serializable
+data class MacroTwitterFeedKt(
+ val statuses: List<TwitterStatusKt>,
+ val searchMetadata: SearchMetadata
+)
+
+@Serializable
+data class MicroTwitterFeedKt(
+ val statuses: List<TwitterTrimmedStatusKt>
+)
+
+@Serializable
+data class TwitterTrimmedStatusKt(
+ val metadata: MetadataKt,
+ val createdAt: String,
+ val id: Long,
+ val idStr: String,
+ val text: String,
+ val source: String,
+ val truncated: Boolean,
+ val user: TwitterTrimmedUserKt,
+ val retweetedStatus: TwitterTrimmedStatusKt? = null,
+)
+
+@Serializable
+data class TwitterStatusKt(
+ val metadata: MetadataKt,
+ val createdAt: String,
+ val id: Long,
+ val idStr: String,
+ val text: String,
+ val source: String,
+ val truncated: Boolean,
+ val inReplyToStatusId: Long?,
+ val inReplyToStatusIdStr: String?,
+ val inReplyToUserId: Long?,
+ val inReplyToUserIdStr: String?,
+ val inReplyToScreenName: String?,
+ val user: TwitterUserKt,
+ val geo: String?,
+ val coordinates: String?,
+ val place: String?,
+ val contributors: List<String>?,
+ val retweetedStatus: TwitterStatusKt? = null,
+ val retweetCount: Int,
+ val favoriteCount: Int,
+ val entities: StatusEntitiesKt,
+ val favorited: Boolean,
+ val retweeted: Boolean,
+ val lang: String,
+ val possiblySensitive: Boolean? = null
+)
+
+@Serializable
+data class StatusEntitiesKt(
+ val hashtags: List<Hashtag>,
+ val symbols: List<String>,
+ val urls: List<Url>,
+ val userMentions: List<TwitterUserMentionKt>,
+ val media: List<TwitterMediaKt>? = null
+)
+
+@Serializable
+data class TwitterMediaKt(
+ val id: Long,
+ val idStr: String,
+ val url: String,
+ val mediaUrl: String,
+ val mediaUrlHttps: String,
+ val expandedUrl: String,
+ val displayUrl: String,
+ val indices: List<Int>,
+ val type: String,
+ val sizes: SizeType,
+ val sourceStatusId: Long? = null,
+ val sourceStatusIdStr: String? = null
+)
+
+@Serializable
+data class TwitterUserMentionKt(
+ val screenName: String,
+ val name: String,
+ val id: Long,
+ val idStr: String,
+ val indices: List<Int>
+)
+
+@Serializable
+data class MetadataKt(
+ val resultType: String,
+ val isoLanguageCode: String
+)
+
+@Serializable
+data class TwitterTrimmedUserKt(
+ val id: Long,
+ val idStr: String,
+ val name: String,
+ val screenName: String,
+ val location: String,
+ val description: String,
+ val url: String?,
+ val entities: UserEntitiesKt,
+ val protected: Boolean,
+ val followersCount: Int,
+ val friendsCount: Int,
+ val listedCount: Int,
+ val createdAt: String,
+ val favouritesCount: Int,
+)
+
+@Serializable
+data class TwitterUserKt(
+ val id: Long,
+ val idStr: String,
+ val name: String,
+ val screenName: String,
+ val location: String,
+ val description: String,
+ val url: String?,
+ val entities: UserEntitiesKt,
+ val protected: Boolean,
+ val followersCount: Int,
+ val friendsCount: Int,
+ val listedCount: Int,
+ val createdAt: String,
+ val favouritesCount: Int,
+ val utcOffset: Int?,
+ val timeZone: String?,
+ val geoEnabled: Boolean,
+ val verified: Boolean,
+ val statusesCount: Int,
+ val lang: String,
+ val contributorsEnabled: Boolean,
+ val isTranslator: Boolean,
+ val isTranslationEnabled: Boolean,
+ val profileBackgroundColor: String,
+ val profileBackgroundImageUrl: String,
+ val profileBackgroundImageUrlHttps: String,
+ val profileBackgroundTile: Boolean,
+ val profileImageUrl: String,
+ val profileImageUrlHttps: String,
+ val profileBannerUrl: String? = null,
+ val profileLinkColor: String,
+ val profileSidebarBorderColor: String,
+ val profileSidebarFillColor: String,
+ val profileTextColor: String,
+ val profileUseBackgroundImage: Boolean,
+ val defaultProfile: Boolean,
+ val defaultProfileImage: Boolean,
+ val following: Boolean,
+ val followRequestSent: Boolean,
+ val notifications: Boolean
+)
+
+@Serializable
+data class UserEntitiesKt(
+ val url: Urls? = null,
+ val description: Urls
+)
diff --git a/build.gradle b/build.gradle
index 69aa68d..34765ef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,9 +1,22 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
buildscript {
- if (project.hasProperty("bootstrap")) {
+ /**
+ * Overrides for Teamcity 'K2 User Projects' + 'Aggregate build / Kotlinx libraries compilation' configuration:
+ * kotlin_repo_url - local repository with snapshot Kotlin compiler
+ * kotlin_version - kotlin version to use
+ * kotlin_language_version - LV to use
+ */
+ ext.snapshotRepoUrl = rootProject.properties["kotlin_repo_url"]
+ ext.kotlin_lv_override = rootProject.properties["kotlin_language_version"]
+ if (snapshotRepoUrl != null && snapshotRepoUrl != "") {
+ ext.kotlin_version = rootProject.properties["kotlin_version"]
+ repositories {
+ maven { url snapshotRepoUrl }
+ }
+ } else if (project.hasProperty("bootstrap")) {
ext.kotlin_version = property('kotlin.version.snapshot')
ext["kotlin.native.home"] = System.getenv("KONAN_LOCAL_DIST")
} else {
@@ -12,19 +25,24 @@
if (project.hasProperty("library.version")) {
ext.overriden_version = property('library.version')
}
- ext.experimentalsEnabled = ["-progressive", "-opt-in=kotlin.Experimental",
+ ext.experimentalsEnabled = ["-progressive",
"-opt-in=kotlin.ExperimentalMultiplatform",
- "-opt-in=kotlinx.serialization.InternalSerializationApi"
+ "-opt-in=kotlinx.serialization.InternalSerializationApi",
+ "-P", "plugin:org.jetbrains.kotlinx.serialization:disableIntrinsic=false"
]
- ext.experimentalsInTestEnabled = ["-progressive", "-opt-in=kotlin.Experimental",
+ ext.experimentalsInTestEnabled = ["-progressive",
"-opt-in=kotlin.ExperimentalMultiplatform",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
"-opt-in=kotlinx.serialization.InternalSerializationApi",
- "-opt-in=kotlin.ExperimentalUnsignedTypes"
+ "-P", "plugin:org.jetbrains.kotlinx.serialization:disableIntrinsic=false"
]
ext.koverEnabled = property('kover.enabled') ?: true
+ def noTeamcityInteractionFlag = rootProject.hasProperty("no_teamcity_interaction")
+ def buildSnapshotUPFlag = rootProject.hasProperty("build_snapshot_up")
+ ext.teamcityInteractionDisabled = noTeamcityInteractionFlag || buildSnapshotUPFlag
+
/*
* This property group is used to build kotlinx.serialization against Kotlin compiler snapshot.
* When build_snapshot_train is set to true, kotlin_version property is overridden with kotlin_snapshot_version.
@@ -74,8 +92,7 @@
// Various benchmarking stuff
classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
- classpath "me.champeau.gradle:jmh-gradle-plugin:0.5.3"
- classpath "net.ltgt.gradle:gradle-apt-plugin:0.21"
+ classpath "me.champeau.jmh:jmh-gradle-plugin:0.6.6"
}
}
@@ -92,7 +109,7 @@
}
knit {
- siteRoot = "https://kotlin.github.io/kotlinx.serialization"
+ siteRoot = "https://kotlinlang.org/api/kotlinx.serialization"
moduleDocs = "build/dokka/htmlMultiModule"
}
@@ -125,6 +142,13 @@
}
}
+ if (snapshotRepoUrl != null && snapshotRepoUrl != "") {
+ // Snapshot-specific for K2 CI configurations
+ repositories {
+ maven { url snapshotRepoUrl }
+ }
+ }
+
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
if (details.requested.group == 'org.jetbrains.kotlin') {
@@ -144,8 +168,19 @@
mavenLocal()
}
+
+ tasks.withType(org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile).configureEach {
+ compilerOptions { freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR") }
+ }
+ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile).configureEach {
+ compilerOptions { freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR") }
+ }
}
+def unpublishedProjects = ["benchmark", "guide", "kotlinx-serialization-json-tests"] as Set
+def excludedFromBomProjects = unpublishedProjects + "kotlinx-serialization-bom" as Set
+def uncoveredProjects = ["kotlinx-serialization-bom", "benchmark", "guide"] as Set
+
subprojects {
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all { task ->
if (task.name.contains("Test") || task.name.contains("Jmh")) {
@@ -157,33 +192,64 @@
apply from: rootProject.file('gradle/teamcity.gradle')
// Configure publishing for some artifacts
- if (project.name != "benchmark" && project.name != "guide") {
+ if (!unpublishedProjects.contains(project.name)) {
apply from: rootProject.file('gradle/publishing.gradle')
}
-
}
subprojects {
// Can't be applied to BOM
- if (project.name == "kotlinx-serialization-bom" || project.name == "benchmark" || project.name == "guide") return
+ if (excludedFromBomProjects.contains(project.name)) return
// Animalsniffer setup
+ // Animalsniffer requires java plugin to be applied, but Kotlin 1.9.20
+ // relies on `java-base` for Kotlin Multiplatforms `withJava` implementation
+ // https://github.com/xvik/gradle-animalsniffer-plugin/issues/84
+ // https://youtrack.jetbrains.com/issue/KT-59595
+ JavaPluginUtil.applyJavaPlugin(project)
apply plugin: 'ru.vyarus.animalsniffer'
afterEvaluate { // Can be applied only when the project is evaluated
animalsniffer {
sourceSets = [sourceSets.main]
+ def annotationValue = "kotlinx.serialization.json.internal.SuppressAnimalSniffer"
+ switch (name) {
+ case "kotlinx-serialization-core":
+ annotationValue = "kotlinx.serialization.internal.SuppressAnimalSniffer"
+ break
+ case "kotlinx-serialization-hocon":
+ annotationValue = "kotlinx.serialization.hocon.internal.SuppressAnimalSniffer"
+ break
+ case "kotlinx-serialization-protobuf":
+ annotationValue = "kotlinx.serialization.protobuf.internal.SuppressAnimalSniffer"
+ }
+ annotation = annotationValue
}
dependencies {
signature 'net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature'
signature 'org.codehaus.mojo.signature:java18:1.0@signature'
}
- }
- // Kover setup
+ // Add dependency on kotlinx-serialization-bom inside other kotlinx-serialization modules themselves, so they have same versions
+ BomKt.addBomApiDependency(project, ":kotlinx-serialization-bom")
+ }
+}
+
+// Kover setup
+subprojects {
+ if (uncoveredProjects.contains(project.name)) return
+
apply from: rootProject.file("gradle/kover.gradle")
}
apply from: rootProject.file('gradle/compiler-version.gradle')
apply from: rootProject.file("gradle/dokka.gradle")
apply from: rootProject.file("gradle/benchmark-parsing.gradle")
+
+tasks.named("dokkaHtmlMultiModule") {
+ pluginsMapConfiguration.set(["org.jetbrains.dokka.base.DokkaBase": """{ "templatesDir": "${projectDir.toString().replace('\\', '/')}/dokka-templates" }"""])
+}
+
+tasks.withType(org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask).configureEach {
+ args.add("--ignore-engines")
+}
\ No newline at end of file
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index 994e674..c999bcd 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -1,3 +1,7 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
import java.util.*
import java.io.FileInputStream
@@ -7,18 +11,34 @@
repositories {
mavenCentral()
+ mavenLocal()
+ if (project.hasProperty("kotlin_repo_url")) {
+ maven(project.properties["kotlin_repo_url"] as String)
+ }
+ // kotlin-dev with space redirector
+ maven("https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
}
-val kotlinVersion = FileInputStream(file("../gradle.properties")).use { propFile ->
- val ver = Properties().apply { load(propFile) }["kotlin.version"]
- require(ver is String) { "kotlin.version must be string in ../gradle.properties, got $ver instead" }
- ver
+val kotlinVersion = run {
+ if (project.hasProperty("build_snapshot_train")) {
+ val ver = project.properties["kotlin_snapshot_version"] as? String
+ require(!ver.isNullOrBlank()) {"kotlin_snapshot_version must be present if build_snapshot_train is used" }
+ return@run ver
+ }
+ if (project.hasProperty("kotlin_repo_url")) {
+ val ver = project.properties["kotlin_version"] as? String
+ require(!ver.isNullOrBlank()) {"kotlin_version must be present if kotlin_repo_url is used" }
+ return@run ver
+ }
+ val targetProp = if (project.hasProperty("bootstrap")) "kotlin.version.snapshot" else "kotlin.version"
+ FileInputStream(file("../gradle.properties")).use { propFile ->
+ val ver = project.findProperty("kotlin.version")?.toString() ?: Properties().apply { load(propFile) }[targetProp]
+ require(ver is String) { "$targetProp must be string in ../gradle.properties, got $ver instead" }
+ ver
+ }
}
dependencies {
implementation(kotlin("gradle-plugin", kotlinVersion))
}
-kotlinDslPluginOptions {
- experimentalWarning.set(false)
-}
diff --git a/buildSrc/src/main/kotlin/Bom.kt b/buildSrc/src/main/kotlin/Bom.kt
new file mode 100644
index 0000000..7f93ed3
--- /dev/null
+++ b/buildSrc/src/main/kotlin/Bom.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import org.gradle.api.Project
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.*
+
+fun Project.addBomApiDependency(bomProjectPath: String) {
+ val isMultiplatform = plugins.hasPlugin("kotlin-multiplatform")
+
+ if (isMultiplatform) {
+ kotlinExtension.sourceSets.getByName("jvmMain").dependencies {
+ api(project.dependencies.platform(project(bomProjectPath)))
+ }
+ } else {
+ dependencies {
+ "api"(platform(project(bomProjectPath)))
+ }
+ }
+}
+
diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt
index 0505297..2743b00 100644
--- a/buildSrc/src/main/kotlin/Java9Modularity.kt
+++ b/buildSrc/src/main/kotlin/Java9Modularity.kt
@@ -1,20 +1,41 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
import org.gradle.api.*
+import org.gradle.api.file.*
+import org.gradle.api.provider.*
+import org.gradle.api.tasks.*
import org.gradle.api.tasks.bundling.*
import org.gradle.api.tasks.compile.*
+import org.gradle.jvm.toolchain.*
import org.gradle.kotlin.dsl.*
+import org.gradle.language.base.plugins.LifecycleBasePlugin.*
+import org.gradle.process.*
+import org.jetbrains.kotlin.gradle.*
import org.jetbrains.kotlin.gradle.dsl.*
+import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.*
-import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.*
+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 {
@JvmStatic
@JvmOverloads
fun Project.configureJava9ModuleInfo(multiRelease: Boolean = true) {
+ val disableJPMS = this.rootProject.extra.has("disableJPMS")
+ val ideaActive = System.getProperty("idea.active") == "true"
+ if (disableJPMS || ideaActive) return
val kotlin = extensions.findByType<KotlinProjectExtension>() ?: return
- val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*> }
+ val jvmTargets = kotlin.targets.filter { it is KotlinJvmTarget || it is KotlinWithJavaTarget<*, *> }
if (jvmTargets.isEmpty()) {
logger.warn("No Kotlin JVM targets found, can't configure compilation of module-info!")
}
@@ -28,75 +49,180 @@
}
target.compilations.forEach { compilation ->
- val compileKotlinTask = compilation.compileKotlinTask as AbstractCompile
+ val compileKotlinTask = compilation.compileKotlinTask as KotlinCompile
val defaultSourceSet = compilation.defaultSourceSet
// derive the names of the source set and compile module task
val sourceSetName = defaultSourceSet.name + "Module"
- val compileModuleTaskName = compileKotlinTask.name + "Module"
kotlin.sourceSets.create(sourceSetName) {
val sourceFile = this.kotlin.find { it.name == "module-info.java" }
- val targetFile = compileKotlinTask.destinationDirectory.file("../module-info.class").get().asFile
+ val targetDirectory = compileKotlinTask.destinationDirectory.map {
+ it.dir("../${it.asFile.name}Module")
+ }
// only configure the compilation if necessary
if (sourceFile != null) {
- // the default source set depends on this new source set
- defaultSourceSet.dependsOn(this)
+ // register and wire a task to verify module-info.java content
+ //
+ // this will compile the whole sources again with a JPMS-aware target Java version,
+ // so that the Kotlin compiler can do the necessary verifications
+ // while compiling with `jdk-release=1.8` those verifications are not done
+ //
+ // this task is only going to be executed when running with `check` or explicitly,
+ // not during normal build operations
+ val verifyModuleTask = registerVerifyModuleTask(
+ compileKotlinTask,
+ sourceFile
+ )
+ tasks.named("check") {
+ dependsOn(verifyModuleTask)
+ }
// register a new compile module task
- val compileModuleTask = registerCompileModuleTask(compileModuleTaskName, compileKotlinTask, sourceFile, targetFile)
+ val compileModuleTask = registerCompileModuleTask(
+ compileKotlinTask,
+ sourceFile,
+ targetDirectory
+ )
// add the resulting module descriptor to this target's artifact
- artifactTask.dependsOn(compileModuleTask)
- artifactTask.from(targetFile) {
+ artifactTask.from(compileModuleTask.map { it.destinationDirectory }) {
if (multiRelease) {
into("META-INF/versions/9/")
}
}
} else {
logger.info("No module-info.java file found in ${this.kotlin.srcDirs}, can't configure compilation of module-info!")
- // remove the source set to prevent Gradle warnings
- kotlin.sourceSets.remove(this)
}
+
+ // remove the source set to prevent Gradle warnings
+ kotlin.sourceSets.remove(this)
}
}
}
}
- private fun Project.registerCompileModuleTask(taskName: String, compileTask: AbstractCompile, sourceFile: File, targetFile: File) =
- tasks.register(taskName, JavaCompile::class) {
- // Also add the module-info.java source file to the Kotlin compile task;
- // the Kotlin compiler will parse and check module dependencies,
- // but it currently won't compile to a module-info.class file.
- compileTask.source(sourceFile)
-
-
- // Configure the module compile task.
- dependsOn(compileTask)
+ /**
+ * Add a Kotlin compile task that compiles `module-info.java` source file and Kotlin sources together,
+ * the Kotlin compiler will parse and check module dependencies,
+ * but it currently won't compile to a module-info.class file.
+ */
+ private fun Project.registerVerifyModuleTask(
+ compileTask: KotlinCompile,
+ sourceFile: File
+ ): TaskProvider<out KotlinJvmCompile> {
+ apply<KotlinApiPlugin>()
+ val verifyModuleTaskName = "verify${compileTask.name.removePrefix("compile").capitalize()}Module"
+ // work-around for https://youtrack.jetbrains.com/issue/KT-60542
+ 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"
+ libraries.from(compileTask.libraries)
+ source(compileTask.sources)
+ source(compileTask.javaSources)
+ // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
+ @Suppress("INVISIBLE_MEMBER")
+ source(compileTask.scriptSources)
source(sourceFile)
- outputs.file(targetFile)
- classpath = files()
- destinationDirectory.set(compileTask.destinationDirectory)
- sourceCompatibility = JavaVersion.VERSION_1_9.toString()
- targetCompatibility = JavaVersion.VERSION_1_9.toString()
+ destinationDirectory.set(temporaryDir)
+ multiPlatformEnabled.set(compileTask.multiPlatformEnabled)
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_9)
+ // To support LV override when set in aggregate builds
+ 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(
+ libraries.asFileTree.elements.map { libs ->
+ libs
+ .filter { it.asFile.exists() }
+ .map {
+ zipTree(it.asFile).filter { it.name == "module-info.class" }
+ }
+ }
+ ).withPropertyName("moduleInfosOfLibraries")
+ this as KotlinCompile
+ 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)
+ }
- doFirst {
+ val taskKotlinLanguageVersion = compilerOptions.languageVersion.orElse(KotlinVersion.DEFAULT)
+ @OptIn(InternalKotlinGradlePluginApi::class)
+ 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)
+ }
+ // part of work-around for https://youtrack.jetbrains.com/issue/KT-60541
+ // and work-around for https://youtrack.jetbrains.com/issue/KT-60582
+ incremental = false
+ }
+ return verifyModuleTask
+ }
+
+ private fun Project.registerCompileModuleTask(
+ compileTask: KotlinCompile,
+ sourceFile: File,
+ targetDirectory: Provider<out Directory>
+ ) = tasks.register("${compileTask.name}Module", JavaCompile::class) {
+ // Configure the module compile task.
+ source(sourceFile)
+ classpath = files()
+ destinationDirectory.set(targetDirectory)
+ // use a Java 11 toolchain with release 9 option
+ // because for some OS / architecture combinations
+ // there are no Java 9 builds available
+ javaCompiler.set(
+ this@registerCompileModuleTask.the<JavaToolchainService>().compilerFor {
+ languageVersion.set(JavaLanguageVersion.of(11))
+ }
+ )
+ options.release.set(9)
+
+ options.compilerArgumentProviders.add(object : CommandLineArgumentProvider {
+ @get:CompileClasspath
+ val compileClasspath = compileTask.libraries
+
+ @get:CompileClasspath
+ val compiledClasses = compileTask.destinationDirectory
+
+ @get:Input
+ val moduleName = sourceFile
+ .readLines()
+ .single { it.contains("module ") }
+ .substringAfter("module ")
+ .substringBefore(' ')
+ .trim()
+
+ override fun asArguments() = mutableListOf(
// Provide the module path to the compiler instead of using a classpath.
// The module path should be the same as the classpath of the compiler.
- options.compilerArgs = listOf(
- "--release", "9",
- "--module-path", compileTask.classpath.asPath,
- "-Xlint:-requires-transitive-automatic"
- )
- }
-
- doLast {
- // Move the compiled file out of the Kotlin compile task's destination dir,
- // so it won't disturb Gradle's caching mechanisms.
- val compiledFile = destinationDirectory.file(targetFile.name).get().asFile
- targetFile.parentFile.mkdirs()
- compiledFile.renameTo(targetFile)
- }
- }
+ "--module-path",
+ compileClasspath.asPath,
+ "--patch-module",
+ "$moduleName=${compiledClasses.get()}",
+ "-Xlint:-requires-transitive-automatic"
+ )
+ })
+ }
}
diff --git a/buildSrc/src/main/kotlin/KotlinVersion.kt b/buildSrc/src/main/kotlin/KotlinVersion.kt
new file mode 100644
index 0000000..5ac051e
--- /dev/null
+++ b/buildSrc/src/main/kotlin/KotlinVersion.kt
@@ -0,0 +1,14 @@
+@file:JvmName("KotlinVersion")
+
+fun isKotlinVersionAtLeast(kotlinVersion: String, atLeastMajor: Int, atLeastMinor: Int, atLeastPatch: Int): Boolean {
+ val (major, minor) = kotlinVersion
+ .split('.')
+ .take(2)
+ .map { it.toInt() }
+ val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt()
+ return when {
+ major > atLeastMajor -> true
+ major < atLeastMajor -> false
+ else -> (minor == atLeastMinor && patch >= atLeastPatch) || minor > atLeastMinor
+ }
+}
diff --git a/buildSrc/src/main/kotlin/setupJavaPlugin.kt b/buildSrc/src/main/kotlin/setupJavaPlugin.kt
new file mode 100644
index 0000000..40db563
--- /dev/null
+++ b/buildSrc/src/main/kotlin/setupJavaPlugin.kt
@@ -0,0 +1,42 @@
+import org.gradle.api.*
+import org.gradle.api.file.*
+import org.gradle.api.plugins.*
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.testing.*
+import org.gradle.jvm.tasks.*
+import org.jetbrains.kotlin.gradle.plugin.*
+
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+object JavaPluginUtil {
+
+ @JvmStatic
+ fun Project.applyJavaPlugin() {
+ plugins.apply("java")
+
+ plugins.withId("org.jetbrains.kotlin.multiplatform") {
+ listOf(
+ JavaPlugin.API_ELEMENTS_CONFIGURATION_NAME,
+ JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME
+ ).forEach { outputConfigurationName ->
+ configurations.findByName(outputConfigurationName)?.isCanBeConsumed = false
+ }
+
+ disableJavaPluginTasks(extensions.getByName("sourceSets") as SourceSetContainer)
+ }
+ }
+}
+
+private fun Project.disableJavaPluginTasks(javaSourceSet: SourceSetContainer) {
+ project.tasks.withType(Jar::class.java).named(javaSourceSet.getByName("main").jarTaskName).configure {
+ dependsOn("jvmTest")
+ enabled = false
+ }
+
+ project.tasks.withType(Test::class.java).named(JavaPlugin.TEST_TASK_NAME) {
+ dependsOn("jvmJar")
+ enabled = false
+ }
+}
diff --git a/core/api/kotlinx-serialization-core.api b/core/api/kotlinx-serialization-core.api
index ede49f6..720e584 100644
--- a/core/api/kotlinx-serialization-core.api
+++ b/core/api/kotlinx-serialization-core.api
@@ -26,6 +26,7 @@
public final class kotlinx/serialization/EncodeDefault$Mode : java/lang/Enum {
public static final field ALWAYS Lkotlinx/serialization/EncodeDefault$Mode;
public static final field NEVER Lkotlinx/serialization/EncodeDefault$Mode;
+ public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lkotlinx/serialization/EncodeDefault$Mode;
public static fun values ()[Lkotlinx/serialization/EncodeDefault$Mode;
}
@@ -43,8 +44,18 @@
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 {
+}
+
public final class kotlinx/serialization/MissingFieldException : kotlinx/serialization/SerializationException {
public fun <init> (Ljava/lang/String;)V
+ public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
+ public fun <init> (Ljava/util/List;Ljava/lang/String;)V
+ public fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/Throwable;)V
+ public final fun getMissingFields ()Ljava/util/List;
}
public abstract interface annotation class kotlinx/serialization/Polymorphic : java/lang/annotation/Annotation {
@@ -112,10 +123,15 @@
}
public final class kotlinx/serialization/SerializersKt {
+ public static final fun noCompiledSerializer (Ljava/lang/String;)Lkotlinx/serialization/KSerializer;
+ public static final fun noCompiledSerializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer;
+ public static final fun noCompiledSerializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;[Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer;
+ public static final fun serializer (Lkotlin/reflect/KClass;Ljava/util/List;Z)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer;
+ public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KClass;Ljava/util/List;Z)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlinx/serialization/modules/SerializersModule;Lkotlin/reflect/KType;)Lkotlinx/serialization/KSerializer;
public static final fun serializerOrNull (Ljava/lang/reflect/Type;)Lkotlinx/serialization/KSerializer;
public static final fun serializerOrNull (Lkotlin/reflect/KClass;)Lkotlinx/serialization/KSerializer;
@@ -156,10 +172,15 @@
public static final fun LongArraySerializer ()Lkotlinx/serialization/KSerializer;
public static final fun MapEntrySerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun MapSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
+ public static final fun NothingSerializer ()Lkotlinx/serialization/KSerializer;
public static final fun PairSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun SetSerializer (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun ShortArraySerializer ()Lkotlinx/serialization/KSerializer;
public static final fun TripleSerializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
+ public static final fun UByteArraySerializer ()Lkotlinx/serialization/KSerializer;
+ public static final fun UIntArraySerializer ()Lkotlinx/serialization/KSerializer;
+ public static final fun ULongArraySerializer ()Lkotlinx/serialization/KSerializer;
+ public static final fun UShortArraySerializer ()Lkotlinx/serialization/KSerializer;
public static final fun getNullable (Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/UByte$Companion;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/UInt$Companion;)Lkotlinx/serialization/KSerializer;
@@ -175,6 +196,7 @@
public static final fun serializer (Lkotlin/jvm/internal/LongCompanionObject;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/jvm/internal/ShortCompanionObject;)Lkotlinx/serialization/KSerializer;
public static final fun serializer (Lkotlin/jvm/internal/StringCompanionObject;)Lkotlinx/serialization/KSerializer;
+ public static final fun serializer (Lkotlin/time/Duration$Companion;)Lkotlinx/serialization/KSerializer;
}
public final class kotlinx/serialization/builtins/LongAsStringSerializer : kotlinx/serialization/KSerializer {
@@ -398,6 +420,10 @@
public fun shouldEncodeElementDefault (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Z
}
+public abstract interface class kotlinx/serialization/encoding/ChunkedDecoder {
+ public abstract fun decodeStringChunked (Lkotlin/jvm/functions/Function1;)V
+}
+
public abstract interface class kotlinx/serialization/encoding/CompositeDecoder {
public static final field Companion Lkotlinx/serialization/encoding/CompositeDecoder$Companion;
public static final field DECODE_DONE I
@@ -662,6 +688,15 @@
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}
+public final class kotlinx/serialization/internal/DurationSerializer : kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/internal/DurationSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun deserialize-5sfh64U (Lkotlinx/serialization/encoding/Decoder;)J
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun serialize-HG0u8IE (Lkotlinx/serialization/encoding/Encoder;J)V
+}
+
public final class kotlinx/serialization/internal/ElementMarker {
public fun <init> (Lkotlinx/serialization/descriptors/SerialDescriptor;Lkotlin/jvm/functions/Function2;)V
public final fun mark (I)V
@@ -750,6 +785,10 @@
public fun isInline ()Z
}
+public final class kotlinx/serialization/internal/InlineClassDescriptorKt {
+ public static final fun InlinePrimitiveDescriptor (Ljava/lang/String;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/descriptors/SerialDescriptor;
+}
+
public final class kotlinx/serialization/internal/IntArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
}
@@ -856,6 +895,9 @@
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
}
+public abstract interface annotation class kotlinx/serialization/internal/NamedCompanion : java/lang/annotation/Annotation {
+}
+
public abstract class kotlinx/serialization/internal/NamedValueDecoder : kotlinx/serialization/internal/TaggedDecoder {
public fun <init> ()V
protected fun composeName (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
@@ -874,6 +916,15 @@
protected final fun nested (Ljava/lang/String;)Ljava/lang/String;
}
+public final class kotlinx/serialization/internal/NothingSerializer : kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/internal/NothingSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Void;
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Void;)V
+}
+
public final class kotlinx/serialization/internal/NullableSerializer : kotlinx/serialization/KSerializer {
public fun <init> (Lkotlinx/serialization/KSerializer;)V
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@@ -1025,7 +1076,7 @@
public final fun decodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;)I
public final fun decodeFloat ()F
public final fun decodeFloatElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)F
- public final fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder;
+ public fun decodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Decoder;
public final fun decodeInlineElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/Decoder;
public final fun decodeInt ()I
public final fun decodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)I
@@ -1081,13 +1132,13 @@
public final fun encodeEnum (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V
public final fun encodeFloat (F)V
public final fun encodeFloatElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IF)V
- public final fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder;
+ public fun encodeInline (Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder;
public final fun encodeInlineElement (Lkotlinx/serialization/descriptors/SerialDescriptor;I)Lkotlinx/serialization/encoding/Encoder;
public final fun encodeInt (I)V
public final fun encodeIntElement (Lkotlinx/serialization/descriptors/SerialDescriptor;II)V
public final fun encodeLong (J)V
public final fun encodeLongElement (Lkotlinx/serialization/descriptors/SerialDescriptor;IJ)V
- public final fun encodeNotNullMark ()V
+ public fun encodeNotNullMark ()V
public fun encodeNull ()V
public fun encodeNullableSerializableElement (Lkotlinx/serialization/descriptors/SerialDescriptor;ILkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
public fun encodeNullableSerializableValue (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
@@ -1106,6 +1157,7 @@
protected fun encodeTaggedInline (Ljava/lang/Object;Lkotlinx/serialization/descriptors/SerialDescriptor;)Lkotlinx/serialization/encoding/Encoder;
protected fun encodeTaggedInt (Ljava/lang/Object;I)V
protected fun encodeTaggedLong (Ljava/lang/Object;J)V
+ protected fun encodeTaggedNonNullMark (Ljava/lang/Object;)V
protected fun encodeTaggedNull (Ljava/lang/Object;)V
protected fun encodeTaggedShort (Ljava/lang/Object;S)V
protected fun encodeTaggedString (Ljava/lang/Object;Ljava/lang/String;)V
@@ -1130,6 +1182,20 @@
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lkotlin/Triple;)V
}
+public final class kotlinx/serialization/internal/UByteArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
+ public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
+}
+
+public final class kotlinx/serialization/internal/UByteArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/internal/UByteArraySerializer;
+ public synthetic fun collectionSize (Ljava/lang/Object;)I
+ public synthetic fun empty ()Ljava/lang/Object;
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V
+ public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
+ public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V
+}
+
public final class kotlinx/serialization/internal/UByteSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/internal/UByteSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@@ -1139,6 +1205,20 @@
public fun serialize-EK-6454 (Lkotlinx/serialization/encoding/Encoder;B)V
}
+public final class kotlinx/serialization/internal/UIntArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
+ public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
+}
+
+public final class kotlinx/serialization/internal/UIntArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/internal/UIntArraySerializer;
+ public synthetic fun collectionSize (Ljava/lang/Object;)I
+ public synthetic fun empty ()Ljava/lang/Object;
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V
+ public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
+ public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V
+}
+
public final class kotlinx/serialization/internal/UIntSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/internal/UIntSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@@ -1148,6 +1228,20 @@
public fun serialize-Qn1smSk (Lkotlinx/serialization/encoding/Encoder;I)V
}
+public final class kotlinx/serialization/internal/ULongArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
+ public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
+}
+
+public final class kotlinx/serialization/internal/ULongArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/internal/ULongArraySerializer;
+ public synthetic fun collectionSize (Ljava/lang/Object;)I
+ public synthetic fun empty ()Ljava/lang/Object;
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V
+ public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
+ public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V
+}
+
public final class kotlinx/serialization/internal/ULongSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/internal/ULongSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@@ -1157,6 +1251,20 @@
public fun serialize-2TYgG_w (Lkotlinx/serialization/encoding/Encoder;J)V
}
+public final class kotlinx/serialization/internal/UShortArrayBuilder : kotlinx/serialization/internal/PrimitiveArrayBuilder {
+ public synthetic fun build$kotlinx_serialization_core ()Ljava/lang/Object;
+}
+
+public final class kotlinx/serialization/internal/UShortArraySerializer : kotlinx/serialization/internal/PrimitiveArraySerializer, kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/internal/UShortArraySerializer;
+ public synthetic fun collectionSize (Ljava/lang/Object;)I
+ public synthetic fun empty ()Ljava/lang/Object;
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILjava/lang/Object;Z)V
+ public synthetic fun readElement (Lkotlinx/serialization/encoding/CompositeDecoder;ILkotlinx/serialization/internal/PrimitiveArrayBuilder;Z)V
+ public synthetic fun toBuilder (Ljava/lang/Object;)Ljava/lang/Object;
+ public synthetic fun writeContent (Lkotlinx/serialization/encoding/CompositeEncoder;Ljava/lang/Object;I)V
+}
+
public final class kotlinx/serialization/internal/UShortSerializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lkotlinx/serialization/internal/UShortSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
@@ -1177,6 +1285,7 @@
public final class kotlinx/serialization/modules/PolymorphicModuleBuilder {
public fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;)V
+ public synthetic fun <init> (Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun buildTo (Lkotlinx/serialization/modules/SerializersModuleBuilder;)V
public final fun default (Lkotlin/jvm/functions/Function1;)V
public final fun defaultDeserializer (Lkotlin/jvm/functions/Function1;)V
@@ -1205,6 +1314,7 @@
}
public final class kotlinx/serialization/modules/SerializersModuleBuildersKt {
+ public static final fun EmptySerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
public static final fun SerializersModule (Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/modules/SerializersModule;
public static final fun polymorphic (Lkotlinx/serialization/modules/SerializersModuleBuilder;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun polymorphic$default (Lkotlinx/serialization/modules/SerializersModuleBuilder;Lkotlin/reflect/KClass;Lkotlinx/serialization/KSerializer;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
diff --git a/core/build.gradle b/core/build.gradle
index 900c494..f52837a 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
+
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -25,7 +27,7 @@
These manifest values help kotlinx.serialization compiler plugin determine if it is compatible with a given runtime library.
Plugin reads them during compilation.
- Implementation-Version is used to determine whether runtime library supports a given plugin feature (e.g. inline classes serialization
+ Implementation-Version is used to determine whether runtime library supports a given plugin feature (e.g. value classes serialization
in Kotlin 1.x may require runtime library version 1.y to work).
Compiler plugin may enable or disable features by looking on Implementation-Version.
@@ -35,6 +37,26 @@
to reject runtime if runtime's Require-Kotlin-Version is greater then the current compiler.
*/
tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) {
+
+ // adding the ProGuard rules to the jar
+ from(rootProject.file("rules/common.pro")) {
+ rename { "kotlinx-serialization-common.pro" }
+ into("META-INF/proguard")
+ }
+ from(rootProject.file("rules/common.pro")) {
+ rename { "kotlinx-serialization-common.pro" }
+ into("META-INF/com.android.tools/proguard")
+ }
+ from(rootProject.file("rules/common.pro")) {
+ rename { "kotlinx-serialization-common.pro" }
+ into("META-INF/com.android.tools/r8")
+ }
+ from(rootProject.file("rules/r8.pro")) {
+ rename { "kotlinx-serialization-r8.pro" }
+ into("META-INF/com.android.tools/r8")
+ }
+
+
manifest {
attributes(
"Implementation-Version": version,
@@ -44,3 +66,7 @@
}
Java9Modularity.configureJava9ModuleInfo(project)
+
+tasks.withType(org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink.class).configureEach {
+ kotlinOptions.freeCompilerArgs += "-Xwasm-enable-array-range-checks"
+}
diff --git a/core/commonMain/src/kotlinx/serialization/Annotations.kt b/core/commonMain/src/kotlinx/serialization/Annotations.kt
index 45b5c1c..081ee82 100644
--- a/core/commonMain/src/kotlinx/serialization/Annotations.kt
+++ b/core/commonMain/src/kotlinx/serialization/Annotations.kt
@@ -1,9 +1,7 @@
/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING") // Parameters of annotations should probably be ignored, too
-
package kotlinx.serialization
import kotlinx.serialization.descriptors.*
@@ -67,6 +65,7 @@
* @see UseSerializers
* @see Serializer
*/
+@MustBeDocumented
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS, AnnotationTarget.TYPE)
//@Retention(AnnotationRetention.RUNTIME) // Runtime is the default retention, also see KT-41082
public annotation class Serializable(
@@ -74,6 +73,36 @@
)
/**
+ * The meta-annotation for adding [Serializable] behaviour to user-defined annotations.
+ *
+ * Applying [MetaSerializable] to the annotation class `A` instructs the serialization plugin to treat annotation A
+ * as [Serializable]. In addition, all annotations marked with [MetaSerializable] are saved in the generated [SerialDescriptor]
+ * as if they are annotated with [SerialInfo].
+ *
+ * ```
+ * @MetaSerializable
+ * @Target(AnnotationTarget.CLASS)
+ * annotation class MySerializable(val data: String)
+ *
+ * @MySerializable("some_data")
+ * class MyData(val myData: AnotherData, val intProperty: Int, ...)
+ *
+ * val serializer = MyData.serializer()
+ * serializer.descriptor.annotations.filterIsInstance<MySerializable>().first().data // <- returns "some_data"
+ * ```
+ *
+ * @see Serializable
+ * @see SerialInfo
+ * @see UseSerializers
+ * @see Serializer
+ */
+@MustBeDocumented
+@Target(AnnotationTarget.ANNOTATION_CLASS)
+//@Retention(AnnotationRetention.RUNTIME) // Runtime is the default retention, also see KT-41082
+@ExperimentalSerializationApi
+public annotation class MetaSerializable
+
+/**
* Instructs the serialization plugin to turn this class into serializer for specified class [forClass].
* However, it would not be used automatically. To apply it on particular class or property,
* use [Serializable] or [UseSerializers], or [Contextual] with runtime registration.
@@ -82,6 +111,7 @@
* Changes may include additional constraints on classes and objects marked with this annotation,
* behavioural changes and even serialized shape of the class.
*/
+@MustBeDocumented
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
@ExperimentalSerializationApi
@@ -113,7 +143,11 @@
* // Prints "{"int":42}"
* println(Json.encodeToString(CustomName(42)))
* ```
+ *
+ * If a name of class or property is overridden with this annotation, original source code name is not available for the library.
+ * Tools like `JsonNamingStrategy` and `ProtoBufSchemaGenerator` would see and transform [value] from [SerialName] annotation.
*/
+@MustBeDocumented
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
// @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082
public annotation class SerialName(val value: String)
@@ -121,14 +155,16 @@
/**
* Indicates that property must be present during deserialization process, despite having a default value.
*/
+@MustBeDocumented
@Target(AnnotationTarget.PROPERTY)
// @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082
public annotation class Required
/**
* Marks this property invisible for the whole serialization process, including [serial descriptors][SerialDescriptor].
- * Transient properties should have default values.
+ * Transient properties must have default values.
*/
+@MustBeDocumented
@Target(AnnotationTarget.PROPERTY)
// @Retention(AnnotationRetention.RUNTIME) still runtime, but KT-41082
public annotation class Transient
@@ -154,6 +190,7 @@
* @see EncodeDefault.Mode.ALWAYS
* @see EncodeDefault.Mode.NEVER
*/
+@MustBeDocumented
@Target(AnnotationTarget.PROPERTY)
@ExperimentalSerializationApi
public annotation class EncodeDefault(val mode: Mode = Mode.ALWAYS) {
@@ -188,6 +225,7 @@
* Keep in mind that Kotlin compiler prioritizes [function parameter target][AnnotationTarget.VALUE_PARAMETER] over [property target][AnnotationTarget.PROPERTY],
* so serial info annotations used on constructor-parameters-as-properties without explicit declaration-site or use-site target are not preserved.
*/
+@MustBeDocumented
@Target(AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.BINARY)
@ExperimentalSerializationApi
@@ -225,12 +263,12 @@
* fun foo(): Int = Derived.serializer().descriptor.annotations.filterIsInstance<A>().single().value
* ```
*/
+@MustBeDocumented
@Target(AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.BINARY)
@ExperimentalSerializationApi
public annotation class InheritableSerialInfo
-
/**
* Instructs the plugin to use [ContextualSerializer] on a given property or type.
* Context serializer is usually used when serializer for type can only be found in runtime.
@@ -287,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/ContextualSerializer.kt b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt
index 53fd4c3..20e9ce1 100644
--- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt
@@ -29,7 +29,7 @@
* @Serializable
* class ClassWithDate(val data: String, @Contextual val timestamp: Date)
*
- * val moduleForDate = serializersModule(MyISO8601DateSerializer)
+ * val moduleForDate = serializersModuleOf(MyISO8601DateSerializer)
* val json = Json { serializersModule = moduleForDate }
* json.encodeToString(ClassWithDate("foo", Date())
* ```
diff --git a/core/commonMain/src/kotlinx/serialization/KSerializer.kt b/core/commonMain/src/kotlinx/serialization/KSerializer.kt
index 3b6c869..89107bb 100644
--- a/core/commonMain/src/kotlinx/serialization/KSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/KSerializer.kt
@@ -5,7 +5,6 @@
package kotlinx.serialization
import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.descriptors.elementNames
import kotlinx.serialization.encoding.*
/**
@@ -51,6 +50,16 @@
* ```
*
* Deserialization process is symmetric and uses [Decoder].
+ *
+ * ### Exception types for `KSerializer` implementation
+ *
+ * Implementations of [serialize] and [deserialize] methods are allowed to throw
+ * any subtype of [IllegalArgumentException] in order to indicate serialization
+ * and deserialization errors.
+ *
+ * For serializer implementations, it is recommended to throw subclasses of [SerializationException] for
+ * any serialization-specific errors related to invalid or unsupported format of the data
+ * and [IllegalStateException] for errors during validation of the data.
*/
public interface KSerializer<T> : SerializationStrategy<T>, DeserializationStrategy<T> {
/**
@@ -106,6 +115,10 @@
* // don't encode 'alwaysZero' property because we decided to do so
* } // end of the structure
* ```
+ *
+ * @throws SerializationException in case of any serialization-specific error
+ * @throws IllegalArgumentException if the supplied input does not comply encoder's specification
+ * @see KSerializer for additional information about general contracts and exception specifics
*/
public fun serialize(encoder: Encoder, value: T)
}
@@ -126,7 +139,7 @@
*
* For a more detailed explanation of the serialization process, please refer to [KSerializer] documentation.
*/
-public interface DeserializationStrategy<T> {
+public interface DeserializationStrategy<out T> {
/**
* Describes the structure of the serializable representation of [T], that current
* deserializer is able to deserialize.
@@ -171,7 +184,11 @@
* return MyData(int, list, alwaysZero = 0L)
* }
* ```
+ *
+ * @throws MissingFieldException if non-optional fields were not found during deserialization
+ * @throws SerializationException in case of any deserialization-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
+ * @see KSerializer for additional information about general contracts and exception specifics
*/
public fun deserialize(decoder: Decoder): T
}
-
diff --git a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
index 311f809..6ee7071 100644
--- a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
@@ -98,7 +98,7 @@
public fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer(
decoder: CompositeDecoder,
klassName: String?
-): DeserializationStrategy<out T> =
+): DeserializationStrategy<T> =
findPolymorphicSerializerOrNull(decoder, klassName) ?: throwSubtypeNotRegistered(klassName, baseClass)
@InternalSerializationApi
diff --git a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
index 354229f..52b7c05 100644
--- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
@@ -105,9 +105,9 @@
element("type", String.serializer().descriptor)
val elementDescriptor =
buildSerialDescriptor("kotlinx.serialization.Sealed<${baseClass.simpleName}>", SerialKind.CONTEXTUAL) {
- subclassSerializers.forEach {
- val d = it.descriptor
- element(d.serialName, d)
+ // serialName2Serializer is guaranteed to have no duplicates — checked in `init`.
+ serialName2Serializer.forEach { (name, serializer) ->
+ element(name, serializer.descriptor)
}
}
element("value", elementDescriptor)
@@ -123,6 +123,9 @@
throw IllegalArgumentException("All subclasses of sealed class ${baseClass.simpleName} should be marked @Serializable")
}
+ // Note: we do not check whether different serializers are provided if the same KClass duplicated in the `subclasses`.
+ // Plugin should produce identical serializers, although they are not always strictly equal (e.g. new ObjectSerializer
+ // may be created every time)
class2Serializer = subclasses.zip(subclassSerializers).toMap()
serialName2Serializer = class2Serializer.entries.groupingBy { it.value.descriptor.serialName }
.aggregate<Map.Entry<KClass<out T>, KSerializer<out T>>, String, Map.Entry<KClass<*>, KSerializer<out T>>>
@@ -137,7 +140,10 @@
}.mapValues { it.value.value }
}
- override fun findPolymorphicSerializerOrNull(decoder: CompositeDecoder, klassName: String?): DeserializationStrategy<out T>? {
+ override fun findPolymorphicSerializerOrNull(
+ decoder: CompositeDecoder,
+ klassName: String?
+ ): DeserializationStrategy<T>? {
return serialName2Serializer[klassName] ?: super.findPolymorphicSerializerOrNull(decoder, klassName)
}
diff --git a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt
index e4801a0..11234b2 100644
--- a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt
+++ b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt
@@ -19,6 +19,16 @@
* Typically, formats have their specific [Encoder] and [Decoder] implementations
* as private classes and do not expose them.
*
+ * ### Exception types for `SerialFormat` implementation
+ *
+ * Methods responsible for format-specific encoding and decoding are allowed to throw
+ * any subtype of [IllegalArgumentException] in order to indicate serialization
+ * and deserialization errors. It is recommended to throw subtypes of [SerializationException]
+ * for encoder and decoder specific errors and [IllegalArgumentException] for input
+ * and output validation-specific errors.
+ *
+ * For formats
+ *
* ### Not stable for inheritance
*
* `SerialFormat` interface is not stable for inheritance in 3rd party libraries, as new methods
@@ -49,11 +59,17 @@
/**
* Serializes and encodes the given [value] to byte array using the given [serializer].
+ *
+ * @throws SerializationException in case of any encoding-specific error
+ * @throws IllegalArgumentException if the encoded input does not comply format's specification
*/
public fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray
/**
- * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using the given [deserializer]
+ * Decodes and deserializes the given [byte array][bytes] to the value of type [T] using the given [deserializer].
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
*/
public fun <T> decodeFromByteArray(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T
}
@@ -72,27 +88,37 @@
/**
* Serializes and encodes the given [value] to string using the given [serializer].
+ *
+ * @throws SerializationException in case of any encoding-specific error
+ * @throws IllegalArgumentException if the encoded input does not comply format's specification
*/
public fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String
/**
- * Decodes and deserializes the given [string] to the value of type [T] using the given [deserializer]
+ * Decodes and deserializes the given [string] to the value of type [T] using the given [deserializer].
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
*/
public fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T
}
/**
* Serializes and encodes the given [value] to string using serializer retrieved from the reified type parameter.
+ *
+ * @throws SerializationException in case of any encoding-specific error
+ * @throws IllegalArgumentException if the encoded input does not comply format's specification
*/
-@OptIn(ExperimentalSerializationApi::class)
public inline fun <reified T> StringFormat.encodeToString(value: T): String =
encodeToString(serializersModule.serializer(), value)
/**
* Decodes and deserializes the given [string] to the value of type [T] using deserializer
* retrieved from the reified type parameter.
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
*/
-@OptIn(ExperimentalSerializationApi::class)
public inline fun <reified T> StringFormat.decodeFromString(string: String): T =
decodeFromString(serializersModule.serializer(), string)
@@ -104,8 +130,10 @@
* Hex representation does not interfere with serialization and encoding process of the format and
* only applies transformation to the resulting array. It is recommended to use for debugging and
* testing purposes.
+ *
+ * @throws SerializationException in case of any encoding-specific error
+ * @throws IllegalArgumentException if the encoded input does not comply format's specification
*/
-@OptIn(ExperimentalSerializationApi::class)
public fun <T> BinaryFormat.encodeToHexString(serializer: SerializationStrategy<T>, value: T): String =
InternalHexConverter.printHexBinary(encodeToByteArray(serializer, value), lowerCase = true)
@@ -113,9 +141,11 @@
* Decodes byte array from the given [hex] string and the decodes and deserializes it
* to the value of type [T], delegating it to the [BinaryFormat].
*
- * This method is a counterpart to [encodeToHexString]
+ * This method is a counterpart to [encodeToHexString].
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
*/
-@OptIn(ExperimentalSerializationApi::class)
public fun <T> BinaryFormat.decodeFromHexString(deserializer: DeserializationStrategy<T>, hex: String): T =
decodeFromByteArray(deserializer, InternalHexConverter.parseHexBinary(hex))
@@ -126,8 +156,10 @@
* Hex representation does not interfere with serialization and encoding process of the format and
* only applies transformation to the resulting array. It is recommended to use for debugging and
* testing purposes.
+ *
+ * @throws SerializationException in case of any encoding-specific error
+ * @throws IllegalArgumentException if the encoded input does not comply format's specification
*/
-@OptIn(ExperimentalSerializationApi::class)
public inline fun <reified T> BinaryFormat.encodeToHexString(value: T): String =
encodeToHexString(serializersModule.serializer(), value)
@@ -135,24 +167,30 @@
* Decodes byte array from the given [hex] string and the decodes and deserializes it
* to the value of type [T], delegating it to the [BinaryFormat].
*
- * This method is a counterpart to [encodeToHexString]
+ * This method is a counterpart to [encodeToHexString].
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
*/
-@OptIn(ExperimentalSerializationApi::class)
public inline fun <reified T> BinaryFormat.decodeFromHexString(hex: String): T =
decodeFromHexString(serializersModule.serializer(), hex)
/**
* Serializes and encodes the given [value] to byte array using serializer
* retrieved from the reified type parameter.
+ *
+ * @throws SerializationException in case of any encoding-specific error
+ * @throws IllegalArgumentException if the encoded input does not comply format's specification
*/
-@OptIn(ExperimentalSerializationApi::class)
public inline fun <reified T> BinaryFormat.encodeToByteArray(value: T): ByteArray =
encodeToByteArray(serializersModule.serializer(), value)
/**
* Decodes and deserializes the given [byte array][bytes] to the value of type [T] using deserializer
* retrieved from the reified type parameter.
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
*/
-@OptIn(ExperimentalSerializationApi::class)
public inline fun <reified T> BinaryFormat.decodeFromByteArray(bytes: ByteArray): T =
decodeFromByteArray(serializersModule.serializer(), bytes)
diff --git a/core/commonMain/src/kotlinx/serialization/SerializationException.kt b/core/commonMain/src/kotlinx/serialization/SerializationException.kt
deleted file mode 100644
index 41631f5..0000000
--- a/core/commonMain/src/kotlinx/serialization/SerializationException.kt
+++ /dev/null
@@ -1,84 +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
-
-/**
- * A generic exception indicating the problem in serialization or deserialization process.
- * This is a generic exception type that can be thrown during the problem at any stage of the serialization,
- * including encoding, decoding, serialization, deserialization.
- * [SerialFormat] implementors should throw subclasses of this exception at any unexpected event,
- * whether it is a malformed input or unsupported class layout.
- */
-public open class SerializationException : IllegalArgumentException {
- /*
- * Rationale behind making it IllegalArgumentException:
- * Any serialization exception is triggered by the illegal argument, whether
- * it is a serializer that does not support specific structure or an invalid input.
- * Making it IAE just aligns the implementation with this fact.
- *
- * Another point is input validation. The simplest way to validate
- * deserialized data is `require` in `init` block:
- * ```
- * @Serializable class Foo(...) {
- * init {
- * required(age > 0) { ... }
- * require(name.isNotBlank()) { ... }
- * }
- * }
- * ```
- * While clearly being serialization error (when compromised data was deserialized),
- * Kotlin way is to throw IAE here instead of using library-specific SerializationException.
- *
- * Also, any production-grade system has a general try-catch around deserialization of potentially
- * untrusted/invalid/corrupted data with the corresponding logging, error reporting and diagnostic.
- * Such handling should catch some subtype of exception (e.g. it's unlikely that catching OOM is desirable).
- * Taking it into account, it becomes clear that SE should be subtype of IAE.
- */
-
- /**
- * Creates an instance of [SerializationException] without any details.
- */
- public constructor()
-
- /**
- * Creates an instance of [SerializationException] with the specified detail [message].
- */
- public constructor(message: String?) : super(message)
-
- /**
- * Creates an instance of [SerializationException] with the specified detail [message], and the given [cause].
- */
- public constructor(message: String?, cause: Throwable?) : super(message, cause)
-
- /**
- * Creates an instance of [SerializationException] with the specified [cause].
- */
- public constructor(cause: Throwable?) : super(cause)
-}
-
-/**
- * Thrown when [KSerializer] did not receive property from [Decoder], and this property was not optional.
- */
-@PublishedApi
-internal class MissingFieldException
-// This constructor is used by coroutines exception recovery
-internal constructor(message: String?, cause: Throwable?) : SerializationException(message, cause) {
- // This constructor is used by the generated serializers
- constructor(fieldName: String) : this("Field '$fieldName' is required, but it was missing", null)
- internal constructor(fieldNames: List<String>, serialName: String) : this(if (fieldNames.size == 1) "Field '${fieldNames[0]}' is required for type with serial name '$serialName', but it was missing" else "Fields $fieldNames are required for type with serial name '$serialName', but they were missing", null)
-}
-
-/**
- * Thrown when [KSerializer] received unknown property index from [CompositeDecoder.decodeElementIndex].
- *
- * This exception means that data schema has changed in backwards-incompatible way.
- */
-@PublishedApi
-internal class UnknownFieldException
-// This constructor is used by coroutines exception recovery
-internal constructor(message: String?) : SerializationException(message) {
- // This constructor is used by the generated serializers
- constructor(index: Int) : this("An unknown field for index $index")
-}
diff --git a/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt
new file mode 100644
index 0000000..99f7d0a
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.descriptors.*
+
+/**
+ * A generic exception indicating the problem in serialization or deserialization process.
+ *
+ * This is a generic exception type that can be thrown during problems at any stage of the serialization,
+ * including encoding, decoding, serialization, deserialization, and validation.
+ * [SerialFormat] implementors should throw subclasses of this exception at any unexpected event,
+ * whether it is a malformed input or unsupported class layout.
+ *
+ * [SerializationException] is a subclass of [IllegalArgumentException] for the sake of consistency and user-defined validation:
+ * Any serialization exception is triggered by the illegal input, whether
+ * it is a serializer that does not support specific structure or an invalid input.
+ *
+ * It is also an established pattern to validate input in user's classes in the following manner:
+ * ```
+ * @Serializable
+ * class Foo(...) {
+ * init {
+ * required(age > 0) { ... }
+ * require(name.isNotBlank()) { ... }
+ * }
+ * }
+ * ```
+ * While clearly being serialization error (when compromised data was deserialized),
+ * Kotlin way is to throw `IllegalArgumentException` here instead of using library-specific `SerializationException`.
+ *
+ * For general "catch-all" patterns around deserialization of potentially
+ * untrusted/invalid/corrupted data it is recommended to catch `IllegalArgumentException` type
+ * to avoid catching irrelevant to serialization errors such as `OutOfMemoryError` or domain-specific ones.
+ */
+public open class SerializationException : IllegalArgumentException {
+
+ /**
+ * Creates an instance of [SerializationException] without any details.
+ */
+ public constructor()
+
+ /**
+ * Creates an instance of [SerializationException] with the specified detail [message].
+ */
+ public constructor(message: String?) : super(message)
+
+ /**
+ * Creates an instance of [SerializationException] with the specified detail [message], and the given [cause].
+ */
+ public constructor(message: String?, cause: Throwable?) : super(message, cause)
+
+ /**
+ * Creates an instance of [SerializationException] with the specified [cause].
+ */
+ public constructor(cause: Throwable?) : super(cause)
+}
+
+/**
+ * Thrown when [KSerializer] did not receive a non-optional property from [CompositeDecoder] and [CompositeDecoder.decodeElementIndex]
+ * had already returned [CompositeDecoder.DECODE_DONE].
+ *
+ * [MissingFieldException] is thrown on missing field from all [auto-generated][Serializable] serializers and it
+ * is recommended to throw this exception from user-defined serializers.
+ *
+ * [MissingFieldException] is constructed from the following properties:
+ * - [missingFields] -- fields that were required for the deserialization but have not been found.
+ * They are always non-empty and their names match the corresponding names in [SerialDescriptor.elementNames]
+ * - Optional `serialName` -- serial name of the enclosing class that failed to get deserialized.
+ * Matches the corresponding [SerialDescriptor.serialName].
+ *
+ * @see SerializationException
+ * @see KSerializer
+ */
+@ExperimentalSerializationApi
+public class MissingFieldException(
+ missingFields: List<String>, message: String?, cause: Throwable?
+) : SerializationException(message, cause) {
+
+ /**
+ * List of fields that were required but not found during deserialization.
+ * Contains at least one element.
+ */
+ public val missingFields: List<String> = missingFields
+
+ /**
+ * Creates an instance of [MissingFieldException] for the given [missingFields] and [serialName] of
+ * the corresponding serializer.
+ */
+ public constructor(
+ missingFields: List<String>,
+ serialName: String
+ ) : this(
+ missingFields,
+ if (missingFields.size == 1) "Field '${missingFields[0]}' is required for type with serial name '$serialName', but it was missing"
+ else "Fields $missingFields are required for type with serial name '$serialName', but they were missing",
+ null
+ )
+
+ /**
+ * Creates an instance of [MissingFieldException] for the given [missingField] and [serialName] of
+ * the corresponding serializer.
+ */
+ public constructor(
+ missingField: String,
+ serialName: String
+ ) : this(
+ listOf(missingField),
+ "Field '$missingField' is required for type with serial name '$serialName', but it was missing",
+ null
+ )
+
+ @PublishedApi // Constructor used by the generated serializers
+ internal constructor(missingField: String) : this(
+ listOf(missingField),
+ "Field '$missingField' is required, but it was missing",
+ null
+ )
+}
+
+/**
+ * Thrown when [KSerializer] received unknown property index from [CompositeDecoder.decodeElementIndex].
+ *
+ * This exception means that data schema has changed in backwards-incompatible way.
+ */
+@PublishedApi
+internal class UnknownFieldException
+// This constructor is used by coroutines exception recovery
+internal constructor(message: String?) : SerializationException(message) {
+ // This constructor is used by the generated serializers
+ constructor(index: Int) : this("An unknown field for index $index")
+}
diff --git a/core/commonMain/src/kotlinx/serialization/Serializers.kt b/core/commonMain/src/kotlinx/serialization/Serializers.kt
index a521187..2489be2 100644
--- a/core/commonMain/src/kotlinx/serialization/Serializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt
@@ -18,15 +18,37 @@
/**
* Retrieves a serializer for the given type [T].
- * This method is a reified version of `serializer(KType)`.
+ * This overload is a reified version of `serializer(KType)`.
+ *
+ * This overload works with full type information, including type arguments and nullability,
+ * and is a recommended way to retrieve a serializer.
+ * For example, `serializer<List<String?>>()` returns [KSerializer] that is able
+ * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`.
+ *
+ * Variance of [T]'s type arguments is not used by the serialization and is not taken into account.
+ * Star projections in [T]'s type arguments are prohibited.
+ *
+ * @throws SerializationException if serializer cannot be created (provided [T] or its type argument is not serializable).
+ * @throws IllegalArgumentException if any of [T]'s type arguments contains star projection
*/
public inline fun <reified T> serializer(): KSerializer<T> {
return serializer(typeOf<T>()).cast()
}
/**
- * Retrieves serializer for the given type [T] from the current [SerializersModule] and,
- * if not found, fallbacks to plain [serializer] method.
+ * Retrieves default serializer for the given type [T] and,
+ * if [T] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup.
+ *
+ * This overload works with full type information, including type arguments and nullability,
+ * and is a recommended way to retrieve a serializer.
+ * For example, `serializer<List<String?>>()` returns [KSerializer] that is able
+ * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`.
+ *
+ * Variance of [T]'s type arguments is not used by the serialization and is not taken into account.
+ * Star projections in [T]'s type arguments are prohibited.
+ *
+ * @throws SerializationException if serializer cannot be created (provided [T] or its type argument is not serializable).
+ * @throws IllegalArgumentException if any of [T]'s type arguments contains star projection
*/
public inline fun <reified T> SerializersModule.serializer(): KSerializer<T> {
return serializer(typeOf<T>()).cast()
@@ -34,41 +56,129 @@
/**
* Creates a serializer for the given [type].
- * [type] argument can be obtained with experimental [typeOf] method.
- * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * [type] argument is usually obtained with [typeOf] method.
+ *
+ * This overload works with full type information, including type arguments and nullability,
+ * and is a recommended way to retrieve a serializer.
+ * For example, `serializer<typeOf<List<String?>>>()` returns [KSerializer] that is able
+ * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`.
+ *
+ * Variance of [type]'s type arguments is not used by the serialization and is not taken into account.
+ * Star projections in [type]'s arguments are prohibited.
+ *
+ * @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * @throws IllegalArgumentException if any of [type]'s arguments contains star projection
*/
-@OptIn(ExperimentalSerializationApi::class)
-public fun serializer(type: KType): KSerializer<Any?> = EmptySerializersModule.serializer(type)
+public fun serializer(type: KType): KSerializer<Any?> = EmptySerializersModule().serializer(type)
+
/**
- * Creates a serializer for the given [type].
- * [type] argument can be obtained with experimental [typeOf] method.
- * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * Retrieves serializer for the given [kClass].
+ * This method uses platform-specific reflection available.
+ *
+ * If [kClass] is a parametrized type then it is necessary to pass serializers for generic parameters in the [typeArgumentsSerializers].
+ * The nullability of returned serializer is specified using the [isNullable].
+ *
+ * Note that it is impossible to create an array serializer with this method,
+ * as array serializer needs additional information: type token for an element type.
+ * To create array serializer, use overload with [KType] or [ArraySerializer] directly.
+ *
+ * Caching on JVM platform is disabled for this function, so it may work slower than an overload with [KType].
+ *
+ * @throws SerializationException if serializer cannot be created (provided [kClass] or its type argument is not serializable)
+ * @throws SerializationException if [kClass] is a `kotlin.Array`
+ * @throws SerializationException if size of [typeArgumentsSerializers] does not match the expected generic parameters count
*/
-@OptIn(ExperimentalSerializationApi::class)
-public fun serializerOrNull(type: KType): KSerializer<Any?>? = EmptySerializersModule.serializerOrNull(type)
+@ExperimentalSerializationApi
+public fun serializer(
+ kClass: KClass<*>,
+ typeArgumentsSerializers: List<KSerializer<*>>,
+ isNullable: Boolean
+): KSerializer<Any?> = EmptySerializersModule().serializer(kClass, typeArgumentsSerializers, isNullable)
/**
- * Attempts to create a serializer for the given [type] and fallbacks to [contextual][SerializersModule.getContextual]
- * lookup for non-serializable types.
- * [type] argument can be obtained with experimental [typeOf] method.
+ * Creates a serializer for the given [type] if possible.
+ * [type] argument is usually obtained with [typeOf] method.
+ *
+ * This overload works with full type information, including type arguments and nullability,
+ * and is a recommended way to retrieve a serializer.
+ * For example, `serializerOrNull<typeOf<List<String?>>>()` returns [KSerializer] that is able
+ * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`.
+ *
+ * Variance of [type]'s arguments is not used by the serialization and is not taken into account.
+ * Star projections in [type]'s arguments are prohibited.
+ *
+ * @return [KSerializer] for the given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable).
+ * @throws IllegalArgumentException if any of [type]'s arguments contains star projection
+ */
+public fun serializerOrNull(type: KType): KSerializer<Any?>? = EmptySerializersModule().serializerOrNull(type)
+
+/**
+ * Retrieves default serializer for the given [type] and,
+ * if [type] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup.
+ * [type] argument is usually obtained with [typeOf] method.
+ *
+ * This overload works with full type information, including type arguments and nullability,
+ * and is a recommended way to retrieve a serializer.
+ * For example, `serializer<typeOf<List<String?>>>()` returns [KSerializer] that is able
+ * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`.
+ *
+ * Variance of [type]'s arguments is not used by the serialization and is not taken into account.
+ * Star projections in [type]'s arguments are prohibited.
+ *
* @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module).
+ * @throws IllegalArgumentException if any of [type]'s arguments contains star projection
*/
-@OptIn(ExperimentalSerializationApi::class)
public fun SerializersModule.serializer(type: KType): KSerializer<Any?> =
serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.kclass()
.platformSpecificSerializerNotRegistered()
+
/**
- * Attempts to create a serializer for the given [type] and fallbacks to [contextual][SerializersModule.getContextual]
- * lookup for non-serializable types.
- * [type] argument can be obtained with experimental [typeOf] method.
- * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable and is not registered in [this] module).
+ * Retrieves serializer for the given [kClass] and,
+ * if [kClass] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup.
+ * This method uses platform-specific reflection available.
+ *
+ * If [kClass] is a parametrized type then it is necessary to pass serializers for generic parameters in the [typeArgumentsSerializers].
+ * The nullability of returned serializer is specified using the [isNullable].
+ *
+ * Note that it is impossible to create an array serializer with this method,
+ * as array serializer needs additional information: type token for an element type.
+ * To create array serializer, use overload with [KType] or [ArraySerializer] directly.
+ *
+ * Caching on JVM platform is disabled for this function, so it may work slower than an overload with [KType].
+ *
+ * @throws SerializationException if serializer cannot be created (provided [kClass] or its type argument is not serializable and is not registered in [this] module)
+ * @throws SerializationException if [kClass] is a `kotlin.Array`
+ * @throws SerializationException if size of [typeArgumentsSerializers] does not match the expected generic parameters count
*/
-@OptIn(ExperimentalSerializationApi::class)
-public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? {
- return serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
-}
+@ExperimentalSerializationApi
+public fun SerializersModule.serializer(
+ kClass: KClass<*>,
+ typeArgumentsSerializers: List<KSerializer<*>>,
+ isNullable: Boolean
+): KSerializer<Any?> =
+ serializerByKClassImpl(kClass as KClass<Any>, typeArgumentsSerializers as List<KSerializer<Any?>>, isNullable)
+ ?: kClass.platformSpecificSerializerNotRegistered()
+
+/**
+ * Retrieves default serializer for the given [type] and,
+ * if [type] is not serializable, fallbacks to [contextual][SerializersModule.getContextual] lookup.
+ * [type] argument is usually obtained with [typeOf] method.
+ *
+ * This overload works with full type information, including type arguments and nullability,
+ * and is a recommended way to retrieve a serializer.
+ * For example, `serializerOrNull<typeOf<List<String?>>>()` returns [KSerializer] that is able
+ * to serialize and deserialize list of nullable strings — i.e. `ListSerializer(String.serializer().nullable)`.
+ *
+ * Variance of [type]'s arguments is not used by the serialization and is not taken into account.
+ * Star projections in [type]'s arguments are prohibited.
+ *
+ * @return [KSerializer] for the given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable and is not registered in [this] module).
+ * @throws IllegalArgumentException if any of [type]'s arguments contains star projection
+ */
+public fun SerializersModule.serializerOrNull(type: KType): KSerializer<Any?>? =
+ serializerByKTypeImpl(type, failOnMissingTypeArgSerializer = false)
@OptIn(ExperimentalSerializationApi::class)
private fun SerializersModule.serializerByKTypeImpl(
@@ -77,53 +187,67 @@
): 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 result: KSerializer<Any>? = when {
- typeArguments.isEmpty() -> rootClass.serializerOrNull() ?: getContextual(rootClass)
- else -> builtinSerializer(typeArguments, rootClass, failOnMissingTypeArgSerializer)
- }?.cast()
- return result?.nullable(isNullable)
+ val typeArguments = type.arguments.map(KTypeProjection::typeOrThrow)
+
+ val cachedSerializer = if (typeArguments.isEmpty()) {
+ findCachedSerializer(rootClass, isNullable)
+ } else {
+ findParametrizedCachedSerializer(rootClass, typeArguments, isNullable).getOrNull()
+ }
+ cachedSerializer?.let { return it }
+
+ // slow path to find contextual serializers in serializers module
+ val contextualSerializer: KSerializer<out Any?>? = if (typeArguments.isEmpty()) {
+ getContextual(rootClass)
+ } else {
+ val serializers = serializersForParameters(typeArguments, failOnMissingTypeArgSerializer) ?: return null
+ // first, we look among the built-in serializers, because the parameter could be contextual
+ rootClass.parametrizedSerializerOrNull(serializers) { typeArguments[0].classifier }
+ ?: getContextual(
+ rootClass,
+ serializers
+ )
+ }
+ return contextualSerializer?.cast<Any>()?.nullable(isNullable)
}
@OptIn(ExperimentalSerializationApi::class)
-private fun SerializersModule.builtinSerializer(
- typeArguments: List<KType>,
+private fun SerializersModule.serializerByKClassImpl(
rootClass: KClass<Any>,
- failOnMissingTypeArgSerializer: Boolean
-): KSerializer<out Any>? {
- val serializers = if (failOnMissingTypeArgSerializer)
- typeArguments.map(::serializer)
- else {
- typeArguments.map { serializerOrNull(it) ?: return null }
- }
- // Array is not supported, see KT-32839
- return when (rootClass) {
- Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
- HashSet::class -> HashSetSerializer(serializers[0])
- Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
- HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
- Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
- serializers[0],
- serializers[1]
- )
- Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
- Pair::class -> PairSerializer(serializers[0], serializers[1])
- Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
- else -> {
- if (isReferenceArray(rootClass)) {
- return ArraySerializer<Any, Any?>(typeArguments[0].classifier as KClass<Any>, serializers[0]).cast()
- }
- val args = serializers.toTypedArray()
- rootClass.constructSerializerForGivenTypeArgs(*args)
- ?: reflectiveOrContextual(rootClass, serializers)
+ typeArgumentsSerializers: List<KSerializer<Any?>>,
+ isNullable: Boolean
+): KSerializer<Any?>? {
+ val serializer = if (typeArgumentsSerializers.isEmpty()) {
+ rootClass.serializerOrNull() ?: getContextual(rootClass)
+ } else {
+ try {
+ rootClass.parametrizedSerializerOrNull(typeArgumentsSerializers) {
+ throw SerializationException("It is not possible to retrieve an array serializer using KClass alone, use KType instead or ArraySerializer factory")
+ } ?: getContextual(
+ rootClass,
+ typeArgumentsSerializers
+ )
+ } catch (e: IndexOutOfBoundsException) {
+ throw SerializationException("Unable to retrieve a serializer, the number of passed type serializers differs from the actual number of generic parameters", e)
}
}
+
+ return serializer?.cast<Any>()?.nullable(isNullable)
}
-@OptIn(ExperimentalSerializationApi::class)
-internal fun <T : Any> SerializersModule.reflectiveOrContextual(kClass: KClass<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? {
- return kClass.serializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
+/**
+ * Returns null only if `failOnMissingTypeArgSerializer == false` and at least one parameter serializer not found.
+ */
+internal fun SerializersModule.serializersForParameters(
+ typeArguments: List<KType>,
+ failOnMissingTypeArgSerializer: Boolean
+): List<KSerializer<Any?>>? {
+ val serializers = if (failOnMissingTypeArgSerializer) {
+ typeArguments.map { serializer(it) }
+ } else {
+ typeArguments.map { serializerOrNull(it) ?: return null }
+ }
+ return serializers
}
/**
@@ -176,7 +300,79 @@
public fun <T : Any> KClass<T>.serializerOrNull(): KSerializer<T>? =
compiledSerializerImpl() ?: builtinSerializerOrNull()
+internal fun KClass<Any>.parametrizedSerializerOrNull(
+ serializers: List<KSerializer<Any?>>,
+ elementClassifierIfArray: () -> KClassifier?
+): KSerializer<out Any>? {
+ // builtin first because some standard parametrized interfaces (e.g. Map) must use builtin serializer but not polymorphic
+ return builtinParametrizedSerializer(serializers, elementClassifierIfArray) ?: compiledParametrizedSerializer(serializers)
+}
+
+
+private fun KClass<Any>.compiledParametrizedSerializer(serializers: List<KSerializer<Any?>>): KSerializer<out Any>? {
+ return constructSerializerForGivenTypeArgs(*serializers.toTypedArray())
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+private fun KClass<Any>.builtinParametrizedSerializer(
+ serializers: List<KSerializer<Any?>>,
+ elementClassifierIfArray: () -> KClassifier?
+): KSerializer<out Any>? {
+ return when (this) {
+ Collection::class, List::class, MutableList::class, ArrayList::class -> ArrayListSerializer(serializers[0])
+ HashSet::class -> HashSetSerializer(serializers[0])
+ Set::class, MutableSet::class, LinkedHashSet::class -> LinkedHashSetSerializer(serializers[0])
+ HashMap::class -> HashMapSerializer(serializers[0], serializers[1])
+ Map::class, MutableMap::class, LinkedHashMap::class -> LinkedHashMapSerializer(
+ serializers[0],
+ serializers[1]
+ )
+
+ Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1])
+ Pair::class -> PairSerializer(serializers[0], serializers[1])
+ Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2])
+ else -> {
+ if (isReferenceArray(this)) {
+ ArraySerializer(elementClassifierIfArray() as KClass<Any>, serializers[0])
+ } else {
+ null
+ }
+ }
+ }
+}
+
private fun <T : Any> KSerializer<T>.nullable(shouldBeNullable: Boolean): KSerializer<T?> {
if (shouldBeNullable) return nullable
return this as KSerializer<T?>
}
+
+
+/**
+ * Overloads of [noCompiledSerializer] should never be called directly.
+ * Instead, compiler inserts calls to them when intrinsifying [serializer] function.
+ *
+ * If no serializer has been found in compile time, call to [noCompiledSerializer] inserted instead.
+ */
+@Suppress("unused")
+@PublishedApi
+internal fun noCompiledSerializer(forClass: String): KSerializer<*> =
+ throw SerializationException(notRegisteredMessage(forClass))
+
+// Used when compiler intrinsic is inserted
+@OptIn(ExperimentalSerializationApi::class)
+@Suppress("unused")
+@PublishedApi
+internal fun noCompiledSerializer(module: SerializersModule, kClass: KClass<*>): KSerializer<*> {
+ return module.getContextual(kClass) ?: kClass.serializerNotRegistered()
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+@Suppress("unused")
+@PublishedApi
+internal fun noCompiledSerializer(
+ module: SerializersModule,
+ kClass: KClass<*>,
+ argSerializers: Array<KSerializer<*>>
+): KSerializer<*> {
+ return module.getContextual(kClass, argSerializers.asList()) ?: kClass.serializerNotRegistered()
+}
diff --git a/core/commonMain/src/kotlinx/serialization/SerializersCache.kt b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt
new file mode 100644
index 0000000..cc86e43
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/SerializersCache.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.builtins.nullable
+import kotlinx.serialization.internal.cast
+import kotlinx.serialization.internal.createCache
+import kotlinx.serialization.internal.createParametrizedCache
+import kotlinx.serialization.modules.EmptySerializersModule
+import kotlin.native.concurrent.ThreadLocal
+import kotlin.reflect.KClass
+import kotlin.reflect.KType
+
+
+/**
+ * Cache for non-null non-parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val SERIALIZERS_CACHE = createCache { it.serializerOrNull() }
+
+/**
+ * Cache for nullable non-parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val SERIALIZERS_CACHE_NULLABLE = createCache<Any?> { it.serializerOrNull()?.nullable?.cast() }
+
+/**
+ * Cache for non-null parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val PARAMETRIZED_SERIALIZERS_CACHE = createParametrizedCache { clazz, types ->
+ val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
+ clazz.parametrizedSerializerOrNull(serializers) { types[0].classifier }
+}
+
+/**
+ * Cache for nullable parametrized and non-contextual serializers.
+ */
+@ThreadLocal
+private val PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE = createParametrizedCache<Any?> { clazz, types ->
+ val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
+ clazz.parametrizedSerializerOrNull(serializers) { types[0].classifier }?.nullable?.cast()
+}
+
+/**
+ * Find cacheable serializer in the cache.
+ * If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned.
+ */
+internal fun findCachedSerializer(clazz: KClass<Any>, isNullable: Boolean): KSerializer<Any?>? {
+ return if (!isNullable) {
+ SERIALIZERS_CACHE.get(clazz)?.cast()
+ } else {
+ SERIALIZERS_CACHE_NULLABLE.get(clazz)
+ }
+}
+
+/**
+ * Find cacheable parametrized serializer in the cache.
+ * If serializer is cacheable but missed in cache - it will be created, placed into the cache and returned.
+ */
+internal fun findParametrizedCachedSerializer(
+ clazz: KClass<Any>,
+ types: List<KType>,
+ isNullable: Boolean
+): Result<KSerializer<Any?>?> {
+ return if (!isNullable) {
+ @Suppress("UNCHECKED_CAST")
+ PARAMETRIZED_SERIALIZERS_CACHE.get(clazz, types) as Result<KSerializer<Any?>?>
+ } else {
+ PARAMETRIZED_SERIALIZERS_CACHE_NULLABLE.get(clazz, types)
+ }
+}
diff --git a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt
index 147f25d..4bd8101 100644
--- a/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt
@@ -9,6 +9,7 @@
import kotlinx.serialization.internal.*
import kotlin.reflect.*
import kotlinx.serialization.descriptors.*
+import kotlin.time.Duration
/**
* Returns a nullable serializer for the given serializer of non-null type.
@@ -33,7 +34,7 @@
* Returns built-in serializer for [Map.Entry].
* Resulting serializer represents entry as a structure with a single key-value pair.
* E.g. `Pair(1, 2)` and `Map.Entry(1, 2)` will be serialized to JSON as
- * `{"first": 1, "second": 2}` and {"1": 2} respectively.
+ * `{"first": 1, "second": 2}` and `{"1": 2}` respectively.
*/
public fun <K, V> MapEntrySerializer(
keySerializer: KSerializer<K>,
@@ -74,6 +75,14 @@
public fun ByteArraySerializer(): KSerializer<ByteArray> = ByteArraySerializer
/**
+ * Returns serializer for [UByteArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind.
+ * Each element of the array is serialized one by one with [UByte.Companion.serializer].
+ */
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+public fun UByteArraySerializer(): KSerializer<UByteArray> = UByteArraySerializer
+
+/**
* Returns serializer for [Short] with [descriptor][SerialDescriptor] of [PrimitiveKind.SHORT] kind.
*/
public fun Short.Companion.serializer(): KSerializer<Short> = ShortSerializer
@@ -85,6 +94,14 @@
public fun ShortArraySerializer(): KSerializer<ShortArray> = ShortArraySerializer
/**
+ * Returns serializer for [UShortArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind.
+ * Each element of the array is serialized one by one with [UShort.Companion.serializer].
+ */
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+public fun UShortArraySerializer(): KSerializer<UShortArray> = UShortArraySerializer
+
+/**
* Returns serializer for [Int] with [descriptor][SerialDescriptor] of [PrimitiveKind.INT] kind.
*/
public fun Int.Companion.serializer(): KSerializer<Int> = IntSerializer
@@ -96,6 +113,14 @@
public fun IntArraySerializer(): KSerializer<IntArray> = IntArraySerializer
/**
+ * Returns serializer for [UIntArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind.
+ * Each element of the array is serialized one by one with [UInt.Companion.serializer].
+ */
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+public fun UIntArraySerializer(): KSerializer<UIntArray> = UIntArraySerializer
+
+/**
* Returns serializer for [Long] with [descriptor][SerialDescriptor] of [PrimitiveKind.LONG] kind.
*/
public fun Long.Companion.serializer(): KSerializer<Long> = LongSerializer
@@ -107,6 +132,14 @@
public fun LongArraySerializer(): KSerializer<LongArray> = LongArraySerializer
/**
+ * Returns serializer for [ULongArray] with [descriptor][SerialDescriptor] of [StructureKind.LIST] kind.
+ * Each element of the array is serialized one by one with [ULong.Companion.serializer].
+ */
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+public fun ULongArraySerializer(): KSerializer<ULongArray> = ULongArraySerializer
+
+/**
* Returns serializer for [Float] with [descriptor][SerialDescriptor] of [PrimitiveKind.FLOAT] kind.
*/
public fun Float.Companion.serializer(): KSerializer<Float> = FloatSerializer
@@ -193,27 +226,36 @@
/**
* Returns serializer for [UInt].
*/
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
public fun UInt.Companion.serializer(): KSerializer<UInt> = UIntSerializer
/**
* Returns serializer for [ULong].
*/
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
public fun ULong.Companion.serializer(): KSerializer<ULong> = ULongSerializer
/**
* Returns serializer for [UByte].
*/
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
public fun UByte.Companion.serializer(): KSerializer<UByte> = UByteSerializer
/**
* Returns serializer for [UShort].
*/
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
public fun UShort.Companion.serializer(): KSerializer<UShort> = UShortSerializer
+
+/**
+ * Returns serializer for [Duration].
+ * It is serialized as a string that represents a duration in the ISO-8601-2 format.
+ *
+ * The result of serialization is similar to calling [Duration.toIsoString], for deserialization is [Duration.parseIsoString].
+ */
+public fun Duration.Companion.serializer(): KSerializer<Duration> = DurationSerializer
+
+/**
+ * Returns serializer for [Nothing].
+ * Throws an exception when trying to encode or decode.
+ *
+ * It is used as a dummy in case it is necessary to pass a type to a parameterized class. At the same time, it is expected that this generic type will not participate in serialization.
+ */
+@ExperimentalSerializationApi
+public fun NothingSerializer(): KSerializer<Nothing> = NothingSerializer
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
index 136df8c..17fdbfe 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt
@@ -5,11 +5,12 @@
package kotlinx.serialization.descriptors
import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
import kotlinx.serialization.encoding.*
/**
* Serial descriptor is an inherent property of [KSerializer] that describes the structure of the serializable type.
- * The structure of the serializable type is not only the property of the type, but also of the serializer as well,
+ * The structure of the serializable type is not only the characteristic of the type itself, but also of the serializer as well,
* meaning that one type can have multiple descriptors that have completely different structure.
*
* For example, the class `class Color(val rgb: Int)` can have multiple serializable representations,
@@ -25,19 +26,19 @@
* For generic types, the actual type substitution is omitted from the string representation and the name
* identifies the family of the serializers without type substitutions. However, type substitution is accounted
* in [equals] and [hashCode] operations, meaning that descriptors of generic classes with the same name, but different type
- * parameters, are not equal to each other.
+ * arguments, are not equal to each other.
* [serialName] is typically used to specify the type of the target class during serialization of polymorphic and sealed
* classes, for observability and diagnostics.
- * * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection et cetera.
+ * * [Kind][SerialKind] defines what this descriptor represents: primitive, enum, object, collection etc.
* * Children elements are represented as serial descriptors as well and define the structure of the type's elements.
- * * Metadata carries additional potentially useful information, such as [nullability][nullable], [optionality][isElementOptional]
+ * * Metadata carries additional information, such as [nullability][nullable], [optionality][isElementOptional]
* and [serial annotations][getElementAnnotations].
*
* ### Usages
* There are two general usages of the descriptors: THE serialization process and serialization introspection.
*
* #### Serialization
- * Serial descriptor is used as bridge between decoders/encoders and serializers.
+ * Serial descriptor is used as a bridge between decoders/encoders and serializers.
* When asking for a next element, the serializer provides an expected descriptor to the decoder, and,
* based on the descriptor content, decoder decides how to parse its input.
* In JSON, for example, when the encoder is asked to encode the next element and this element
@@ -59,15 +60,15 @@
* the range from zero to [elementsCount] and represent and index of the property in this class.
* Consequently, primitives do not have children and their element count is zero.
*
- * For collections and maps, though, indices does not have fixed bound. Regular collections descriptors usually
+ * For collections and maps indices don't have fixed bound. Regular collections descriptors usually
* have one element (`T`, maps have two, one for keys and one for values), but potentially unlimited
* number of actual children values. Valid indices range is not known statically
- * and implementations of descriptor should provide consistent and unbounded names and indices.
+ * and implementations of such descriptor should provide consistent and unbounded names and indices.
*
* In practice, for regular classes it is allowed to invoke `getElement*(index)` methods
- * with an index within `0 until elementsCount` range and element at the particular index corresponds to the
+ * with an index from `0` to [elementsCount] range and element at the particular index corresponds to the
* serializable property at the given position.
- * For collections and maps, index parameter for `getElement*(index)` methods is effectively bound
+ * For collections and maps, index parameter for `getElement*(index)` methods is effectively bounded
* by the maximal number of collection/map elements.
*
* ### Thread-safety and mutability
@@ -76,7 +77,6 @@
* ### Equality and caching
* Serial descriptor can be used as a unique identifier for format-specific data or schemas and
* this implies the following restrictions on its `equals` and `hashCode`:
- * *
*
* An [equals] implementation should use both [serialName] and elements structure.
* Comparing [elementDescriptors] directly is discouraged,
@@ -100,8 +100,8 @@
* [hashCode] implementation should use the same properties for computing the result.
*
* ### User-defined serial descriptors
- * The best way to define a custom descriptor is to use [SerialDescriptor] builder function, where
- * for each serializable property corresponding element is declared.
+ * The best way to define a custom descriptor is to use [buildClassSerialDescriptor] builder function, where
+ * for each serializable property the corresponding element is declared.
*
* Example:
* ```
@@ -113,15 +113,29 @@
* )
*
* // Descriptor for such class:
- * SerialDescriptor("my.package.Data") {
+ * buildClassSerialDescriptor("my.package.Data") {
* // intField is deliberately ignored by serializer -- not present in the descriptor as well
* element<Long>("_longField") // longField is named as _longField
- * element("stringField", listDescriptor<String>())
+ * element("stringField", listSerialDescriptor<String>())
+ * }
+ *
+ * // Example of 'serialize' function for such descriptor
+ * override fun serialize(encoder: Encoder, value: Data) {
+ * encoder.encodeStructure(descriptor) {
+ * encodeLongElement(descriptor, 0, value.longField) // Will be written as "_longField" because descriptor's child at index 0 says so
+ * encodeSerializableElement(descriptor, 1, ListSerializer(String.serializer()), value.stringList)
+ * }
* }
* ```
*
* For a classes that are represented as a single primitive value, [PrimitiveSerialDescriptor] builder function can be used instead.
*
+ * ### Consistency violations
+ * An implementation of [SerialDescriptor] should be consistent with the implementation of the corresponding [KSerializer].
+ * Yet it is not type-checked statically, thus making it possible to declare a non-consistent implementations of descriptor and serializer.
+ * In such cases, the behaviour of an underlying format is unspecified and may lead to both runtime errors and encoding of
+ * corrupted data that is impossible to decode back.
+ *
* ### Not stable for inheritance
*
* `SerialDescriptor` interface is not stable for inheritance in 3rd party libraries, as new methods
@@ -162,9 +176,9 @@
public val isNullable: Boolean get() = false
/**
- * Returns `true` if this descriptor describes a serializable inline class.
+ * Returns `true` if this descriptor describes a serializable value class which underlying value
+ * is serialized directly.
*/
- @ExperimentalSerializationApi
public val isInline: Boolean get() = false
/**
@@ -245,7 +259,7 @@
public fun getElementDescriptor(index: Int): SerialDescriptor
/**
- * Whether the element at the given [index] is optional (can be absent is serialized form).
+ * Whether the element at the given [index] is optional (can be absent in serialized form).
* For generated descriptors, all elements that have a corresponding default parameter value are
* marked as optional. Custom serializers can treat optional values in a serialization-specific manner
* without default parameters constraint.
diff --git a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
index f9e5173..cb380aa 100644
--- a/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
+++ b/core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt
@@ -5,6 +5,7 @@
package kotlinx.serialization.descriptors
import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.internal.*
import kotlin.reflect.*
@@ -24,22 +25,24 @@
* val nullableInt: Int?
* )
* // Descriptor for such class:
- * SerialDescriptor("my.package.Data") {
+ * buildClassSerialDescriptor("my.package.Data") {
* // intField is deliberately ignored by serializer -- not present in the descriptor as well
* element<Long>("_longField") // longField is named as _longField
- * element("stringField", listDescriptor<String>())
- * element("nullableInt", descriptor<Int>().nullable)
+ * element("stringField", listSerialDescriptor<String>()) // or ListSerializer(String.serializer()).descriptor
+ * element("nullableInt", serialDescriptor<Int>().nullable)
* }
* ```
*
* Example for generic classes:
* ```
+ * import kotlinx.serialization.builtins.*
+ *
* @Serializable(CustomSerializer::class)
* class BoxedList<T>(val list: List<T>)
*
* class CustomSerializer<T>(tSerializer: KSerializer<T>): KSerializer<BoxedList<T>> {
* // here we use tSerializer.descriptor because it represents T
- * override val descriptor = SerialDescriptor("pkg.BoxedList", CLASS, tSerializer.descriptor) {
+ * override val descriptor = buildClassSerialDescriptor("pkg.BoxedList", tSerializer.descriptor) {
* // here we have to wrap it with List first, because property has type List<T>
* element("list", ListSerializer(tSerializer).descriptor) // or listSerialDescriptor(tSerializer.descriptor)
* }
@@ -71,7 +74,7 @@
* ```
* object LongAsStringSerializer : KSerializer<Long> {
* override val descriptor: SerialDescriptor =
- * PrimitiveDescriptor("kotlinx.serialization.LongAsStringSerializer", PrimitiveKind.STRING)
+ * PrimitiveSerialDescriptor("kotlinx.serialization.LongAsStringSerializer", PrimitiveKind.STRING)
*
* override fun serialize(encoder: Encoder, value: Long) {
* encoder.encodeString(value.toString())
@@ -129,7 +132,7 @@
* This function is left public only for migration of pre-release users and is not intended to be used
* as generally-safe and stable mechanism. Beware that it can produce inconsistent or non spec-compliant instances.
*
- * If you end up using this builder, please file an issue with your use-case in kotlinx.serialization
+ * If you end up using this builder, please file an issue with your use-case in kotlinx.serialization issue tracker.
*/
@InternalSerializationApi
@OptIn(ExperimentalSerializationApi::class)
@@ -240,6 +243,7 @@
* in its [KSerializer] type parameter and handle nulls during encoding and decoding.
*/
@ExperimentalSerializationApi
+ @Deprecated("isNullable inside buildSerialDescriptor is deprecated. Please use SerialDescriptor.nullable extension on a builder result.", level = DeprecationLevel.ERROR)
public var isNullable: Boolean = false
/**
@@ -278,7 +282,7 @@
annotations: List<Annotation> = emptyList(),
isOptional: Boolean = false
) {
- require(uniqueNames.add(elementName)) { "Element with name '$elementName' is already registered" }
+ require(uniqueNames.add(elementName)) { "Element with name '$elementName' is already registered in $serialName" }
elementNames += elementName
elementDescriptors += descriptor
elementAnnotations += annotations
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
index 8e2799c..ffe6dd3 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
@@ -34,7 +34,7 @@
override fun decodeString(): String = decodeValue() as String
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeValue() as Int
- override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder = this
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder = this
// overwrite by default
public open fun <T : Any?> decodeSerializableValue(
@@ -74,8 +74,7 @@
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
- ): T? {
- val isNullabilitySupported = deserializer.descriptor.isNullable
- return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer, previousValue) else decodeNull()
+ ): T? = decodeIfNullable(deserializer) {
+ decodeSerializableValue(deserializer, previousValue)
}
}
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt
index f197c40..384cb8a 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt
@@ -51,7 +51,7 @@
override fun encodeString(value: String): Unit = encodeValue(value)
override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int): Unit = encodeValue(index)
- override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder = this
+ override fun encodeInline(descriptor: SerialDescriptor): Encoder = this
// Delegating implementation of CompositeEncoder
final override fun encodeBooleanElement(descriptor: SerialDescriptor, index: Int, value: Boolean) { if (encodeElement(descriptor, index)) encodeBoolean(value) }
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt b/core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt
new file mode 100644
index 0000000..016e07e
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt
@@ -0,0 +1,51 @@
+package kotlinx.serialization.encoding
+
+import kotlinx.serialization.ExperimentalSerializationApi
+
+/**
+ * This interface indicates that decoder supports consuming large strings by chunks via consumeChunk method.
+ * Currently, only streaming json decoder implements this interface.
+ * Please note that this interface is only applicable to streaming decoders. That means that it is not possible to use
+ * some JsonTreeDecoder features like polymorphism with this interface.
+ */
+@ExperimentalSerializationApi
+public interface ChunkedDecoder {
+ /**
+ * Method allows decoding a string value by fixed-size chunks.
+ * Usable for handling very large strings that may not fit in memory.
+ * Chunk size is guaranteed to not exceed 16384 chars (but it may be smaller than that).
+ * Feeds string chunks to the provided consumer.
+ *
+ * @param consumeChunk - lambda function to handle string chunks
+ *
+ * Example usage:
+ * ```
+ * @Serializable(with = LargeStringSerializer::class)
+ * data class LargeStringData(val largeString: String)
+ *
+ * @Serializable
+ * data class ClassWithLargeStringDataField(val largeStringField: LargeStringData)
+ *
+ * object LargeStringSerializer : KSerializer<LargeStringData> {
+ * override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LargeStringContent", PrimitiveKind.STRING)
+ *
+ * override fun deserialize(decoder: Decoder): LargeStringData {
+ * require(decoder is ChunkedDecoder) { "Only chunked decoder supported" }
+ *
+ * val tmpFile = createTempFile()
+ * val writer = FileWriter(tmpFile.toFile()).use {
+ * decoder.decodeStringChunked { chunk ->
+ * writer.append(chunk)
+ * }
+ * }
+ * return LargeStringData("file://${tmpFile.absolutePathString()}")
+ * }
+ * }
+ * ```
+ *
+ * In this sample, we need to be able to handle a huge string coming from json. Instead of storing it in memory,
+ * we offload it into a file and return the file name instead
+ */
+ @ExperimentalSerializationApi
+ public fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit)
+}
\ No newline at end of file
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
index 3e93e3d..dc4aa2a 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
@@ -108,7 +108,7 @@
*
* ### Not stable for inheritance
*
- * `Decoder` interface is not stable for inheritance in 3rd party libraries, as new methods
+ * `Decoder` interface is not stable for inheritance in 3rd-party libraries, as new methods
* might be added to this interface or contracts of the existing methods can be changed.
*/
public interface Decoder {
@@ -211,27 +211,24 @@
public fun decodeEnum(enumDescriptor: SerialDescriptor): Int
/**
- * Returns [Decoder] for decoding an underlying type of an inline class.
- * [inlineDescriptor] describes a target inline class.
+ * Returns [Decoder] for decoding an underlying type of a value class in an inline manner.
+ * [descriptor] describes a target value class.
*
- * Namely, for the `@Serializable inline class MyInt(val my: Int)`,
- * the following sequence is used:
+ * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`, the following sequence is used:
* ```
* thisDecoder.decodeInline(MyInt.serializer().descriptor).decodeInt()
* ```
*
- * Current decoder may return any other instance of [Decoder] class,
- * depending on the provided [inlineDescriptor].
- * For example, when this function is called on Json decoder with
- * `UInt.serializer().descriptor`, the returned decoder is able
- * to decode unsigned integers.
+ * Current decoder may return any other instance of [Decoder] class, depending on the provided [descriptor].
+ * For example, when this function is called on `Json` decoder with
+ * `UInt.serializer().descriptor`, the returned decoder is able to decode unsigned integers.
*
* Note that this function returns [Decoder] instead of the [CompositeDecoder]
- * because inline classes always have the single property.
- * Calling [Decoder.beginStructure] on returned instance leads to an undefined behavior.
+ * because value classes always have the single property.
+ *
+ * Calling [Decoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited.
*/
- @ExperimentalSerializationApi
- public fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder
+ public fun decodeInline(descriptor: SerialDescriptor): Decoder
/**
* Decodes the beginning of the nested structure in a serialized form
@@ -263,12 +260,17 @@
* Decodes the nullable value of type [T] by delegating the decoding process to the given [deserializer].
*/
@ExperimentalSerializationApi
- public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
- val isNullabilitySupported = deserializer.descriptor.isNullable
- return if (isNullabilitySupported || decodeNotNullMark()) decodeSerializableValue(deserializer) else decodeNull()
+ public fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? = decodeIfNullable(deserializer) {
+ decodeSerializableValue(deserializer)
}
}
+@OptIn(ExperimentalSerializationApi::class)
+internal inline fun <T : Any> Decoder.decodeIfNullable(deserializer: DeserializationStrategy<T?>, block: () -> T?): T? {
+ val isNullabilitySupported = deserializer.descriptor.isNullable
+ return if (isNullabilitySupported || decodeNotNullMark()) block() else decodeNull()
+}
+
/**
* [CompositeDecoder] is a part of decoding process that is bound to a particular structured part of
* the serialized form, described by the serial descriptor passed to [Decoder.beginStructure].
@@ -488,35 +490,34 @@
public fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String
/**
- * Returns [Decoder] for decoding an underlying type of an inline class.
- * Serializable inline class is described by the [child descriptor][SerialDescriptor.getElementDescriptor]
+ * Returns [Decoder] for decoding an underlying type of a value class in an inline manner.
+ * Serializable value class is described by the [child descriptor][SerialDescriptor.getElementDescriptor]
* of given [descriptor] at [index].
*
- * Namely, for the `@Serializable inline class MyInt(val my: Int)`,
- * and `@Serializable class MyData(val myInt: MyInt)`
- * the following sequence is used:
+ * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`,
+ * and `@Serializable class MyData(val myInt: MyInt)` the following sequence is used:
* ```
* thisDecoder.decodeInlineElement(MyData.serializer().descriptor, 0).decodeInt()
* ```
*
- * This method provides an opportunity for the optimization and its invocation should be identical to
+ * This method provides an opportunity for the optimization to avoid boxing of a carried value
+ * and its invocation should be equivalent to the following:
* ```
* thisDecoder.decodeSerializableElement(MyData.serializer.descriptor, 0, MyInt.serializer())
* ```
*
* Current decoder may return any other instance of [Decoder] class, depending on the provided descriptor.
- * For example, when this function is called on Json decoder with descriptor that has
+ * For example, when this function is called on `Json` decoder with descriptor that has
* `UInt.serializer().descriptor` at the given [index], the returned decoder is able
* to decode unsigned integers.
*
* Note that this function returns [Decoder] instead of the [CompositeDecoder]
- * because inline classes always have the single property.
- * Calling [Decoder.beginStructure] on returned instance leads to an undefined behavior.
+ * because value classes always have the single property.
+ * Calling [Decoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited.
*
* @see Decoder.decodeInline
* @see SerialDescriptor.getElementDescriptor
*/
- @ExperimentalSerializationApi
public fun decodeInlineElement(
descriptor: SerialDescriptor,
index: Int
@@ -571,6 +572,3 @@
composite.endStructure(descriptor)
return result
}
-
-private const val decodeMethodDeprecated = "Please migrate to decodeElement method which accepts old value." +
- "Feel free to ignore it if your format does not support updates."
diff --git a/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt b/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
index 1113b1c..2b1dd09 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
@@ -207,27 +207,24 @@
public fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int)
/**
- * Returns [Encoder] for encoding an underlying type of an inline class.
- * [inlineDescriptor] describes a serializable inline class.
+ * Returns [Encoder] for encoding an underlying type of a value class in an inline manner.
+ * [descriptor] describes a serializable value class.
*
- * Namely, for the `@Serializable inline class MyInt(val my: Int)`,
+ * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`,
* the following sequence is used:
* ```
* thisEncoder.encodeInline(MyInt.serializer().descriptor).encodeInt(my)
* ```
*
- * Current encoder may return any other instance of [Encoder] class,
- * depending on the provided [inlineDescriptor].
- * For example, when this function is called on Json encoder with
- * `UInt.serializer().descriptor`, the returned encoder is able
+ * Current encoder may return any other instance of [Encoder] class, depending on the provided [descriptor].
+ * For example, when this function is called on Json encoder with `UInt.serializer().descriptor`, the returned encoder is able
* to encode unsigned integers.
*
- * Note that this function returns [Encoder] instead of [CompositeEncoder]
- * because inline classes always have one property.
- * Calling [Encoder.beginStructure] on returned instance leads to an undefined behavior.
+ * Note that this function returns [Encoder] instead of the [CompositeEncoder]
+ * because value classes always have the single property.
+ * Calling [Encoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited.
*/
- @ExperimentalSerializationApi
- public fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder
+ public fun encodeInline(descriptor: SerialDescriptor): Encoder
/**
* Encodes the beginning of the nested structure in a serialized form
@@ -411,36 +408,34 @@
public fun encodeStringElement(descriptor: SerialDescriptor, index: Int, value: String)
/**
- * Returns [Encoder] for decoding an underlying type of an inline class.
- * Serializable inline class is described by the [child descriptor][SerialDescriptor.getElementDescriptor]
+ * Returns [Encoder] for decoding an underlying type of a value class in an inline manner.
+ * Serializable value class is described by the [child descriptor][SerialDescriptor.getElementDescriptor]
* of given [descriptor] at [index].
*
- * Namely, for the `@Serializable inline class MyInt(val my: Int)`,
- * and `@Serializable class MyData(val myInt: MyInt)`
- * the following sequence is used:
+ * Namely, for the `@Serializable @JvmInline value class MyInt(val my: Int)`,
+ * and `@Serializable class MyData(val myInt: MyInt)` the following sequence is used:
* ```
* thisEncoder.encodeInlineElement(MyData.serializer.descriptor, 0).encodeInt(my)
* ```
*
- * This method is an optimization and its invocation should have the exact same result as
+ * This method provides an opportunity for the optimization to avoid boxing of a carried value
+ * and its invocation should be equivalent to the following:
* ```
* thisEncoder.encodeSerializableElement(MyData.serializer.descriptor, 0, MyInt.serializer(), myInt)
* ```
*
- * Current encoder may return any other instance of [Encoder] class,
- * depending on provided descriptor.
+ * Current encoder may return any other instance of [Encoder] class, depending on provided descriptor.
* For example, when this function is called on Json encoder with descriptor that has
* `UInt.serializer().descriptor` at the given [index], the returned encoder is able
* to encode unsigned integers.
*
- * Note that this function returns [Encoder] instead of [CompositeEncoder]
- * because inline classes always have one property.
- * Calling [Encoder.beginStructure] on returned instance leads to an undefined behavior.
+ * Note that this function returns [Encoder] instead of the [CompositeEncoder]
+ * because value classes always have the single property.
+ * Calling [Encoder.beginStructure] on returned instance leads to an unspecified behavior and, in general, is prohibited.
*
* @see Encoder.encodeInline
* @see SerialDescriptor.getElementDescriptor
*/
- @ExperimentalSerializationApi
public fun encodeInlineElement(
descriptor: SerialDescriptor,
index: Int
diff --git a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
index a85b465..26d3b5e 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -58,8 +58,8 @@
}
else -> throw SerializationException(
"Invalid index in polymorphic deserialization of " +
- (klassName ?: "unknown class") +
- "\n Expected 0, 1 or DECODE_DONE(-1), but found $index"
+ (klassName ?: "unknown class") +
+ "\n Expected 0, 1 or DECODE_DONE(-1), but found $index"
)
}
}
@@ -81,7 +81,7 @@
public open fun findPolymorphicSerializerOrNull(
decoder: CompositeDecoder,
klassName: String?
- ): DeserializationStrategy<out T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName)
+ ): DeserializationStrategy<T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName)
/**
@@ -98,13 +98,14 @@
@JvmName("throwSubtypeNotRegistered")
internal fun throwSubtypeNotRegistered(subClassName: String?, baseClass: KClass<*>): Nothing {
- val scope = "in the scope of '${baseClass.simpleName}'"
+ val scope = "in the polymorphic scope of '${baseClass.simpleName}'"
throw SerializationException(
if (subClassName == null)
- "Class discriminator was missing and no default polymorphic serializers were registered $scope"
+ "Class discriminator was missing and no default serializers were registered $scope."
else
- "Class '$subClassName' is not registered for polymorphic serialization $scope.\n" +
- "Mark the base class as 'sealed' or register the serializer explicitly."
+ "Serializer for subclass '$subClassName' is not found $scope.\n" +
+ "Check if class with serial name '$subClassName' exists and serializer is registered in a corresponding SerializersModule.\n" +
+ "To be registered automatically, class '$subClassName' has to be '@Serializable', and the base class '${baseClass.simpleName}' has to be sealed and '@Serializable'."
)
}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt
new file mode 100644
index 0000000..2e64a77
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.KSerializer
+import kotlinx.serialization.SerializationException
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlin.time.Duration
+
+
+@PublishedApi
+internal object DurationSerializer : KSerializer<Duration> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("kotlin.time.Duration", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: Duration) {
+ encoder.encodeString(value.toIsoString())
+ }
+
+ override fun deserialize(decoder: Decoder): Duration {
+ return Duration.parseIsoString(decoder.decodeString())
+ }
+}
+
+@PublishedApi
+internal object NothingSerializer : KSerializer<Nothing> {
+ override val descriptor: SerialDescriptor = NothingSerialDescriptor
+
+ override fun serialize(encoder: Encoder, value: Nothing) {
+ throw SerializationException("'kotlin.Nothing' cannot be serialized")
+ }
+
+ override fun deserialize(decoder: Decoder): Nothing {
+ throw SerializationException("'kotlin.Nothing' does not have instances")
+ }
+}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt b/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
index fca9026..5e6736d 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt
@@ -4,13 +4,13 @@
package kotlinx.serialization.internal
-import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
@OptIn(ExperimentalSerializationApi::class)
-@PublishedApi
-internal class ElementMarker(
+@CoreFriendModuleApi
+public class ElementMarker(
private val descriptor: SerialDescriptor,
// Instead of inheritance and virtual function in order to keep cross-module internal modifier via suppresses
// Can be reworked via public + internal api if necessary
@@ -45,7 +45,7 @@
}
}
- fun mark(index: Int) {
+ public fun mark(index: Int) {
if (index < Long.SIZE_BITS) {
lowerMarks = lowerMarks or (1L shl index)
} else {
@@ -53,7 +53,7 @@
}
}
- fun nextUnmarkedIndex(): Int {
+ public fun nextUnmarkedIndex(): Int {
val elementsCount = descriptor.elementsCount
while (lowerMarks != -1L) {
val index = lowerMarks.inv().countTrailingZeroBits()
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Enums.kt b/core/commonMain/src/kotlinx/serialization/internal/Enums.kt
index 1e62015..90800d7 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Enums.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Enums.kt
@@ -49,20 +49,79 @@
}
}
-// Used for enums that are not explicitly serializable by the plugin
+@OptIn(ExperimentalSerializationApi::class)
+@InternalSerializationApi
+internal fun <T : Enum<T>> createSimpleEnumSerializer(serialName: String, values: Array<T>): KSerializer<T> {
+ return EnumSerializer(serialName, values)
+}
+
+/**
+ * The function has a bug (#2121) and should not be used by new (1.8.20+) plugins. It is preserved for backward compatibility with previously compiled enum classes.
+ */
+@OptIn(ExperimentalSerializationApi::class)
+@InternalSerializationApi
+internal fun <T : Enum<T>> createMarkedEnumSerializer(
+ serialName: String,
+ values: Array<T>,
+ names: Array<String?>,
+ annotations: Array<Array<Annotation>?>
+): KSerializer<T> {
+ val descriptor = EnumDescriptor(serialName, values.size)
+ values.forEachIndexed { i, v ->
+ val elementName = names.getOrNull(i) ?: v.name
+ descriptor.addElement(elementName)
+ annotations.getOrNull(i)?.forEach {
+ descriptor.pushAnnotation(it)
+ }
+ }
+
+ return EnumSerializer(serialName, values, descriptor)
+}
+
+@OptIn(ExperimentalSerializationApi::class)
+@InternalSerializationApi
+internal fun <T : Enum<T>> createAnnotatedEnumSerializer(
+ serialName: String,
+ values: Array<T>,
+ names: Array<String?>,
+ entryAnnotations: Array<Array<Annotation>?>,
+ classAnnotations: Array<Annotation>?
+): KSerializer<T> {
+ val descriptor = EnumDescriptor(serialName, values.size)
+ classAnnotations?.forEach {
+ descriptor.pushClassAnnotation(it)
+ }
+ values.forEachIndexed { i, v ->
+ val elementName = names.getOrNull(i) ?: v.name
+ descriptor.addElement(elementName)
+ entryAnnotations.getOrNull(i)?.forEach {
+ descriptor.pushAnnotation(it)
+ }
+ }
+
+ return EnumSerializer(serialName, values, descriptor)
+}
+
@PublishedApi
@OptIn(ExperimentalSerializationApi::class)
internal class EnumSerializer<T : Enum<T>>(
serialName: String,
private val values: Array<T>
) : KSerializer<T> {
+ private var overriddenDescriptor: SerialDescriptor? = null
- override val descriptor: SerialDescriptor = buildSerialDescriptor(serialName, SerialKind.ENUM) {
- values.forEach {
- val fqn = "$serialName.${it.name}"
- val enumMemberDescriptor = buildSerialDescriptor(fqn, StructureKind.OBJECT)
- element(it.name, enumMemberDescriptor)
- }
+ internal constructor(serialName: String, values: Array<T>, descriptor: SerialDescriptor) : this(serialName, values) {
+ overriddenDescriptor = descriptor
+ }
+
+ override val descriptor: SerialDescriptor by lazy {
+ overriddenDescriptor ?: createUnmarkedDescriptor(serialName)
+ }
+
+ private fun createUnmarkedDescriptor(serialName: String): SerialDescriptor {
+ val d = EnumDescriptor(serialName, values.size)
+ values.forEach { d.addElement(it.name) }
+ return d
}
override fun serialize(encoder: Encoder, value: T) {
diff --git a/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt
index 05fd92b..ec9edc9 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt
@@ -10,7 +10,6 @@
@Suppress("Unused")
@PublishedApi
-@OptIn(ExperimentalSerializationApi::class)
internal class InlineClassDescriptor(
name: String,
generatedSerializer: GeneratedSerializer<*>
@@ -26,7 +25,8 @@
}
}
-internal fun <T> InlinePrimitiveDescriptor(name: String, primitiveSerializer: KSerializer<T>): SerialDescriptor =
+@InternalSerializationApi
+public fun <T> InlinePrimitiveDescriptor(name: String, primitiveSerializer: KSerializer<T>): SerialDescriptor =
InlineClassDescriptor(name, object : GeneratedSerializer<T> {
// object needed only to pass childSerializers()
override fun childSerializers(): Array<KSerializer<*>> = arrayOf(primitiveSerializer)
diff --git a/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt b/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt
index d79cb8b..e733827 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt
@@ -1,14 +1,14 @@
package kotlinx.serialization.internal
-import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
/*
- * Methods that are required for kotlinx-serialization-json, but are not effectively public
- * and actually represent our own technical debt.
- * This methods are not intended for public use
+ * Methods that are required for kotlinx-serialization-json, but are not effectively public.
+ *
+ * Anything marker with this annotation is not intended for public use.
*/
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
+internal annotation class CoreFriendModuleApi
-@InternalSerializationApi
-@Deprecated(message = "Should not be used", level = DeprecationLevel.ERROR)
+@CoreFriendModuleApi
public fun SerialDescriptor.jsonCachedSerialNames(): Set<String> = cachedSerialNames()
diff --git a/core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt b/core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt
new file mode 100644
index 0000000..0756dc6
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.*
+
+/**
+ * An annotation added by the compiler to the companion object of [Serializable] class, if it has a non-default name.
+ */
+@InternalSerializationApi
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.RUNTIME)
+public annotation class NamedCompanion
diff --git a/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt b/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt
index 463e486..ce366bd 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt
@@ -14,7 +14,7 @@
*/
@OptIn(ExperimentalSerializationApi::class)
internal object NoOpEncoder : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
public override fun encodeValue(value: Any): Unit = Unit
diff --git a/core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt
new file mode 100644
index 0000000..aab492a
--- /dev/null
+++ b/core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:OptIn(ExperimentalSerializationApi::class)
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.descriptors.SerialKind
+import kotlinx.serialization.descriptors.StructureKind
+
+internal object NothingSerialDescriptor : SerialDescriptor {
+ public override val kind: SerialKind = StructureKind.OBJECT
+
+ public override val serialName: String = "kotlin.Nothing"
+
+ override val elementsCount: Int get() = 0
+ override fun getElementName(index: Int): String = error()
+ override fun getElementIndex(name: String): Int = error()
+ override fun isElementOptional(index: Int): Boolean = error()
+ override fun getElementDescriptor(index: Int): SerialDescriptor = error()
+ override fun getElementAnnotations(index: Int): List<Annotation> = error()
+ override fun toString(): String = "NothingSerialDescriptor"
+ override fun equals(other: Any?): Boolean {
+ return this === other
+ }
+
+ override fun hashCode(): Int = serialName.hashCode() + 31 * kind.hashCode()
+ private fun error(): Nothing =
+ throw IllegalStateException("Descriptor for type `kotlin.Nothing` does not have elements")
+}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt
index ebb9e2e..ac9ee8e 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt
@@ -41,6 +41,9 @@
override fun deserialize(decoder: Decoder): T {
decoder.decodeStructure(descriptor) {
+ if (decodeSequentially())
+ return@decodeStructure
+
when (val index = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> {
return@decodeStructure
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
index fde2c36..ef313cc 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt
@@ -21,7 +21,7 @@
while (i < len) {
val h = hexToInt(s[i])
val l = hexToInt(s[i + 1])
- require(!(h == -1 || l == -1)) { "Invalid hex chars: ${s[i]}${s[i+1]}" }
+ require(!(h == -1 || l == -1)) { "Invalid hex chars: ${s[i]}${s[i + 1]}" }
bytes[i / 2] = ((h shl 4) + l).toByte()
i += 2
@@ -65,7 +65,6 @@
return result
}
-@SharedImmutable
private val EMPTY_DESCRIPTOR_ARRAY: Array<SerialDescriptor> = arrayOf()
/**
@@ -85,28 +84,40 @@
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
@PublishedApi
-internal inline fun <T> DeserializationStrategy<*>.cast(): DeserializationStrategy<T> = this as DeserializationStrategy<T>
+internal inline fun <T> DeserializationStrategy<*>.cast(): DeserializationStrategy<T> =
+ this as DeserializationStrategy<T>
internal fun KClass<*>.serializerNotRegistered(): Nothing {
- throw SerializationException(
- "Serializer for class '${simpleName}' is not found.\n" +
- "Mark the class as @Serializable or provide the serializer explicitly."
- )
+ throw SerializationException(notRegisteredMessage())
}
+internal fun KClass<*>.notRegisteredMessage(): String = notRegisteredMessage(simpleName ?: "<local class name not available>")
+
+internal fun notRegisteredMessage(className: String): String = "Serializer for class '$className' is not found.\n" +
+ "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.\n"
+
internal expect fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing
@Suppress("UNCHECKED_CAST")
internal fun KType.kclass() = when (val t = classifier) {
is KClass<*> -> t
is KTypeParameter -> {
- error("Captured type paramerer $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")
+ // 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 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)
@@ -130,18 +141,41 @@
internal expect fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>?
-internal expect fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E>
+/**
+ * Create serializers cache for non-parametrized and non-contextual serializers.
+ * The activity and type of cache is determined for a specific platform and a specific environment.
+ */
+internal expect fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T>
/**
- * Checks whether the receiver is an instance of a given kclass.
- *
- * This check is a replacement for [KClass.isInstance] because on JVM it requires kotlin-reflect.jar in classpath (see KT-14720).
- *
- * On JS and Native, this function delegates to aforementioned [KClass.isInstance] since it is supported there out-of-the-box;
- * on JVM, it falls back to `java.lang.Class.isInstance`, which causes difference when applied to function types with big arity.
+ * Create serializers cache for parametrized and non-contextual serializers. Parameters also non-contextual.
+ * The activity and type of cache is determined for a specific platform and a specific environment.
*/
-internal expect fun Any.isInstanceOf(kclass: KClass<*>): Boolean
+internal expect fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T>
+
+internal expect fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E>
internal inline fun <T, K> Iterable<T>.elementsHashCodeBy(selector: (T) -> K): Int {
return fold(1) { hash, element -> 31 * hash + selector(element).hashCode() }
}
+
+/**
+ * Cache class for non-parametrized and non-contextual serializers.
+ */
+internal interface SerializerCache<T> {
+ /**
+ * Returns cached serializer or `null` if serializer not found.
+ */
+ fun get(key: KClass<Any>): KSerializer<T>?
+}
+
+/**
+ * Cache class for parametrized and non-contextual serializers.
+ */
+internal interface ParametrizedSerializerCache<T> {
+ /**
+ * Returns successful result with cached serializer or `null` if root serializer not found.
+ * If no serializer was found for the parameters, then result contains an exception.
+ */
+ fun get(key: KClass<Any>, types: List<KType> = emptyList()): Result<KSerializer<T>?>
+}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
index 7b6efe7..a954bda 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt
@@ -1,7 +1,7 @@
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE", "UNUSED")
+@file:Suppress("UNUSED")
package kotlinx.serialization.internal
diff --git a/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt b/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt
index f613c2a..61272d2 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt
@@ -8,7 +8,6 @@
import kotlin.jvm.*
import kotlin.native.concurrent.*
-@SharedImmutable
@JvmField
internal val EMPTY_SERIALIZER_ARRAY: Array<KSerializer<*>> = arrayOf()
diff --git a/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt b/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt
index 7427b16..1e6172f 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt
@@ -408,3 +408,223 @@
override fun build() = buffer.copyOf(position)
}
+
+
+// Unsigned arrays
+
+/**
+ * Serializer for [UByteArray].
+ *
+ * Encode elements one-by-one, as regular list,
+ * unless format's Encoder/Decoder have special handling for this serializer.
+ */
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal object UByteArraySerializer : KSerializer<UByteArray>,
+ PrimitiveArraySerializer<UByte, UByteArray, UByteArrayBuilder>(UByte.serializer()) {
+
+ override fun UByteArray.collectionSize(): Int = size
+ override fun UByteArray.toBuilder(): UByteArrayBuilder = UByteArrayBuilder(this)
+ override fun empty(): UByteArray = UByteArray(0)
+
+ override fun readElement(decoder: CompositeDecoder, index: Int, builder: UByteArrayBuilder, checkIndex: Boolean) {
+ builder.append(decoder.decodeInlineElement(descriptor, index).decodeByte().toUByte())
+ }
+
+ override fun writeContent(encoder: CompositeEncoder, content: UByteArray, size: Int) {
+ for (i in 0 until size)
+ encoder.encodeInlineElement(descriptor, i).encodeByte(content[i].toByte())
+ }
+}
+
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal class UByteArrayBuilder internal constructor(
+ bufferWithData: UByteArray
+) : PrimitiveArrayBuilder<UByteArray>() {
+
+ private var buffer: UByteArray = bufferWithData
+ override var position: Int = bufferWithData.size
+ private set
+
+ init {
+ ensureCapacity(INITIAL_SIZE)
+ }
+
+ override fun ensureCapacity(requiredCapacity: Int) {
+ if (buffer.size < requiredCapacity)
+ buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2))
+ }
+
+ internal fun append(c: UByte) {
+ ensureCapacity()
+ buffer[position++] = c
+ }
+
+ override fun build() = buffer.copyOf(position)
+}
+
+/**
+ * Serializer for [UShortArray].
+ *
+ * Encode elements one-by-one, as regular list,
+ * unless format's Encoder/Decoder have special handling for this serializer.
+ */
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal object UShortArraySerializer : KSerializer<UShortArray>,
+ PrimitiveArraySerializer<UShort, UShortArray, UShortArrayBuilder>(UShort.serializer()) {
+
+ override fun UShortArray.collectionSize(): Int = size
+ override fun UShortArray.toBuilder(): UShortArrayBuilder = UShortArrayBuilder(this)
+ override fun empty(): UShortArray = UShortArray(0)
+
+ override fun readElement(decoder: CompositeDecoder, index: Int, builder: UShortArrayBuilder, checkIndex: Boolean) {
+ builder.append(decoder.decodeInlineElement(descriptor, index).decodeShort().toUShort())
+ }
+
+ override fun writeContent(encoder: CompositeEncoder, content: UShortArray, size: Int) {
+ for (i in 0 until size)
+ encoder.encodeInlineElement(descriptor, i).encodeShort(content[i].toShort())
+ }
+}
+
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal class UShortArrayBuilder internal constructor(
+ bufferWithData: UShortArray
+) : PrimitiveArrayBuilder<UShortArray>() {
+
+ private var buffer: UShortArray = bufferWithData
+ override var position: Int = bufferWithData.size
+ private set
+
+ init {
+ ensureCapacity(INITIAL_SIZE)
+ }
+
+ override fun ensureCapacity(requiredCapacity: Int) {
+ if (buffer.size < requiredCapacity)
+ buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2))
+ }
+
+ internal fun append(c: UShort) {
+ ensureCapacity()
+ buffer[position++] = c
+ }
+
+ override fun build() = buffer.copyOf(position)
+}
+
+/**
+ * Serializer for [UIntArray].
+ *
+ * Encode elements one-by-one, as regular list,
+ * unless format's Encoder/Decoder have special handling for this serializer.
+ */
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal object UIntArraySerializer : KSerializer<UIntArray>,
+ PrimitiveArraySerializer<UInt, UIntArray, UIntArrayBuilder>(UInt.serializer()) {
+
+ override fun UIntArray.collectionSize(): Int = size
+ override fun UIntArray.toBuilder(): UIntArrayBuilder = UIntArrayBuilder(this)
+ override fun empty(): UIntArray = UIntArray(0)
+
+ override fun readElement(decoder: CompositeDecoder, index: Int, builder: UIntArrayBuilder, checkIndex: Boolean) {
+ builder.append(decoder.decodeInlineElement(descriptor, index).decodeInt().toUInt())
+ }
+
+ override fun writeContent(encoder: CompositeEncoder, content: UIntArray, size: Int) {
+ for (i in 0 until size)
+ encoder.encodeInlineElement(descriptor, i).encodeInt(content[i].toInt())
+ }
+}
+
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal class UIntArrayBuilder internal constructor(
+ bufferWithData: UIntArray
+) : PrimitiveArrayBuilder<UIntArray>() {
+
+ private var buffer: UIntArray = bufferWithData
+ override var position: Int = bufferWithData.size
+ private set
+
+ init {
+ ensureCapacity(INITIAL_SIZE)
+ }
+
+ override fun ensureCapacity(requiredCapacity: Int) {
+ if (buffer.size < requiredCapacity)
+ buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2))
+ }
+
+ internal fun append(c: UInt) {
+ ensureCapacity()
+ buffer[position++] = c
+ }
+
+ override fun build() = buffer.copyOf(position)
+}
+
+/**
+ * Serializer for [ULongArray].
+ *
+ * Encode elements one-by-one, as regular list,
+ * unless format's Encoder/Decoder have special handling for this serializer.
+ */
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal object ULongArraySerializer : KSerializer<ULongArray>,
+ PrimitiveArraySerializer<ULong, ULongArray, ULongArrayBuilder>(ULong.serializer()) {
+
+ override fun ULongArray.collectionSize(): Int = size
+ override fun ULongArray.toBuilder(): ULongArrayBuilder = ULongArrayBuilder(this)
+ override fun empty(): ULongArray = ULongArray(0)
+
+ override fun readElement(decoder: CompositeDecoder, index: Int, builder: ULongArrayBuilder, checkIndex: Boolean) {
+ builder.append(decoder.decodeInlineElement(descriptor, index).decodeLong().toULong())
+ }
+
+ override fun writeContent(encoder: CompositeEncoder, content: ULongArray, size: Int) {
+ for (i in 0 until size)
+ encoder.encodeInlineElement(descriptor, i).encodeLong(content[i].toLong())
+ }
+}
+
+@PublishedApi
+@ExperimentalSerializationApi
+@ExperimentalUnsignedTypes
+internal class ULongArrayBuilder internal constructor(
+ bufferWithData: ULongArray
+) : PrimitiveArrayBuilder<ULongArray>() {
+
+ private var buffer: ULongArray = bufferWithData
+ override var position: Int = bufferWithData.size
+ private set
+
+ init {
+ ensureCapacity(INITIAL_SIZE)
+ }
+
+ override fun ensureCapacity(requiredCapacity: Int) {
+ if (buffer.size < requiredCapacity)
+ buffer = buffer.copyOf(requiredCapacity.coerceAtLeast(buffer.size * 2))
+ }
+
+ internal fun append(c: ULong) {
+ ensureCapacity()
+ buffer[position++] = c
+ }
+
+ override fun build() = buffer.copyOf(position)
+}
+
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt b/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt
index ab127ff..2d9c528 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Primitives.kt
@@ -13,8 +13,9 @@
import kotlinx.serialization.encoding.*
import kotlin.native.concurrent.*
import kotlin.reflect.*
+import kotlin.time.Duration
-@SharedImmutable
+@OptIn(ExperimentalUnsignedTypes::class)
private val BUILTIN_SERIALIZERS = mapOf(
String::class to String.serializer(),
Char::class to Char.serializer(),
@@ -25,15 +26,25 @@
FloatArray::class to FloatArraySerializer(),
Long::class to Long.serializer(),
LongArray::class to LongArraySerializer(),
+ ULong::class to ULong.serializer(),
+ ULongArray::class to ULongArraySerializer(),
Int::class to Int.serializer(),
IntArray::class to IntArraySerializer(),
+ UInt::class to UInt.serializer(),
+ UIntArray::class to UIntArraySerializer(),
Short::class to Short.serializer(),
ShortArray::class to ShortArraySerializer(),
+ UShort::class to UShort.serializer(),
+ UShortArray::class to UShortArraySerializer(),
Byte::class to Byte.serializer(),
ByteArray::class to ByteArraySerializer(),
+ UByte::class to UByte.serializer(),
+ UByteArray::class to UByteArraySerializer(),
Boolean::class to Boolean.serializer(),
BooleanArray::class to BooleanArraySerializer(),
- Unit::class to Unit.serializer()
+ Unit::class to Unit.serializer(),
+ Nothing::class to NothingSerializer(),
+ Duration::class to Duration.serializer()
)
internal class PrimitiveSerialDescriptor(
@@ -47,6 +58,13 @@
override fun getElementDescriptor(index: Int): SerialDescriptor = error()
override fun getElementAnnotations(index: Int): List<Annotation> = error()
override fun toString(): String = "PrimitiveDescriptor($serialName)"
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is PrimitiveSerialDescriptor) return false
+ if (serialName == other.serialName && kind == other.kind) return true
+ return false
+ }
+ override fun hashCode() = serialName.hashCode() + 31 * kind.hashCode()
private fun error(): Nothing = throw IllegalStateException("Primitive descriptor does not have elements")
}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
index e2e8d8f..cf71388 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
@@ -24,12 +24,13 @@
protected abstract fun SerialDescriptor.getTag(index: Int): Tag
override val serializersModule: SerializersModule
- get() = EmptySerializersModule
+ get() = EmptySerializersModule()
// ---- API ----
protected open fun encodeTaggedValue(tag: Tag, value: Any): Unit =
throw SerializationException("Non-serializable ${value::class} is not supported by ${this::class} encoder")
+ protected open fun encodeTaggedNonNullMark(tag: Tag) {}
protected open fun encodeTaggedNull(tag: Tag): Unit = throw SerializationException("null is not supported")
protected open fun encodeTaggedInt(tag: Tag, value: Int): Unit = encodeTaggedValue(tag, value)
protected open fun encodeTaggedByte(tag: Tag, value: Byte): Unit = encodeTaggedValue(tag, value)
@@ -50,8 +51,8 @@
protected open fun encodeTaggedInline(tag: Tag, inlineDescriptor: SerialDescriptor): Encoder =
this.apply { pushTag(tag) }
- final override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder =
- encodeTaggedInline(popTag(), inlineDescriptor)
+ override fun encodeInline(descriptor: SerialDescriptor): Encoder =
+ encodeTaggedInline(popTag(), descriptor)
// ---- Implementation of low-level API ----
@@ -61,7 +62,7 @@
return true
}
- final override fun encodeNotNullMark() {} // Does nothing, open because is not really required
+ open override fun encodeNotNullMark(): Unit = encodeTaggedNonNullMark(currentTag)
open override fun encodeNull(): Unit = encodeTaggedNull(popTag())
final override fun encodeBoolean(value: Boolean): Unit = encodeTaggedBoolean(popTag(), value)
final override fun encodeByte(value: Byte): Unit = encodeTaggedByte(popTag(), value)
@@ -177,7 +178,7 @@
@InternalSerializationApi
public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
override val serializersModule: SerializersModule
- get() = EmptySerializersModule
+ get() = EmptySerializersModule()
protected abstract fun SerialDescriptor.getTag(index: Int): Tag
@@ -205,11 +206,10 @@
protected open fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>, previousValue: T?): T =
decodeSerializableValue(deserializer)
-
// ---- Implementation of low-level API ----
- final override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder =
- decodeTaggedInline(popTag(), inlineDescriptor)
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder =
+ decodeTaggedInline(popTag(), descriptor)
// TODO this method should be overridden by any sane format that supports top-level nulls
override fun decodeNotNullMark(): Boolean {
@@ -283,13 +283,11 @@
index: Int,
deserializer: DeserializationStrategy<T?>,
previousValue: T?
- ): T? =
- tagBlock(descriptor.getTag(index)) {
- if (decodeNotNullMark()) decodeSerializableValue(
- deserializer,
- previousValue
- ) else decodeNull()
+ ): T? = tagBlock(descriptor.getTag(index)) {
+ decodeIfNullable(deserializer) {
+ decodeSerializableValue(deserializer, previousValue)
}
+ }
private fun <E> tagBlock(tag: Tag, block: () -> E): E {
pushTag(tag)
@@ -330,7 +328,7 @@
final override fun SerialDescriptor.getTag(index: Int): String = nested(elementName(this, index))
protected fun nested(nestedName: String): String = composeName(currentTagOrNull ?: "", nestedName)
- protected open fun elementName(desc: SerialDescriptor, index: Int): String = desc.getElementName(index)
+ protected open fun elementName(descriptor: SerialDescriptor, index: Int): String = descriptor.getElementName(index)
protected open fun composeName(parentName: String, childName: String): String =
if (parentName.isEmpty()) childName else "$parentName.$childName"
}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt b/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt
index 1d30047..1bd21cb 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Tuples.kt
@@ -11,7 +11,6 @@
import kotlinx.serialization.encoding.*
import kotlin.native.concurrent.*
-@SharedImmutable
private val NULL = Any()
private const val deprecationMessage =
"This class is used only by the plugin in generated code and should not be used directly. Use corresponding factory functions instead"
@@ -33,35 +32,33 @@
structuredEncoder.endStructure(descriptor)
}
- override fun deserialize(decoder: Decoder): R {
- val composite = decoder.beginStructure(descriptor)
- if (composite.decodeSequentially()) {
- val key = composite.decodeSerializableElement(descriptor, 0, keySerializer)
- val value = composite.decodeSerializableElement(descriptor, 1, valueSerializer)
- return toResult(key, value)
+ override fun deserialize(decoder: Decoder): R = decoder.decodeStructure(descriptor) {
+ if (decodeSequentially()) {
+ val key = decodeSerializableElement(descriptor, 0, keySerializer)
+ val value = decodeSerializableElement(descriptor, 1, valueSerializer)
+ return@decodeStructure toResult(key, value)
}
var key: Any? = NULL
var value: Any? = NULL
mainLoop@ while (true) {
- when (val idx = composite.decodeElementIndex(descriptor)) {
+ when (val idx = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> {
break@mainLoop
}
0 -> {
- key = composite.decodeSerializableElement(descriptor, 0, keySerializer)
+ key = decodeSerializableElement(descriptor, 0, keySerializer)
}
1 -> {
- value = composite.decodeSerializableElement(descriptor, 1, valueSerializer)
+ value = decodeSerializableElement(descriptor, 1, valueSerializer)
}
else -> throw SerializationException("Invalid index: $idx")
}
}
- composite.endStructure(descriptor)
if (key === NULL) throw SerializationException("Element 'key' is missing")
if (value === NULL) throw SerializationException("Element 'value' is missing")
@Suppress("UNCHECKED_CAST")
- return toResult(key as K, value as V)
+ return@decodeStructure toResult(key as K, value as V)
}
}
diff --git a/core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt b/core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt
similarity index 90%
rename from core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt
rename to core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt
index b973890..90c1f28 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt
@@ -10,8 +10,6 @@
import kotlinx.serialization.encoding.*
@PublishedApi
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
internal object UIntSerializer : KSerializer<UInt> {
override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UInt", Int.serializer())
@@ -25,8 +23,6 @@
}
@PublishedApi
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
internal object ULongSerializer : KSerializer<ULong> {
override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.ULong", Long.serializer())
@@ -40,8 +36,6 @@
}
@PublishedApi
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
internal object UByteSerializer : KSerializer<UByte> {
override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UByte", Byte.serializer())
@@ -55,8 +49,6 @@
}
@PublishedApi
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
internal object UShortSerializer : KSerializer<UShort> {
override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UShort", Short.serializer())
diff --git a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
index 31ce457..1b8d431 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
@@ -21,7 +21,7 @@
) {
private val subclasses: MutableList<Pair<KClass<out Base>, KSerializer<out Base>>> = mutableListOf()
private var defaultSerializerProvider: ((Base) -> SerializationStrategy<Base>?)? = null
- private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<out Base>?)? = null
+ private var defaultDeserializerProvider: ((String?) -> DeserializationStrategy<Base>?)? = null
/**
* Registers a [subclass] [serializer] in the resulting module under the [base class][Base].
@@ -34,19 +34,20 @@
* Adds a default serializers provider associated with the given [baseClass] to the resulting module.
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
* were found. `className` could be `null` for formats that support nullable class discriminators
- * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`)
+ * (currently only `Json` with `JsonBuilder.useArrayPolymorphism` set to `false`)
+ *
+ * Default deserializers provider affects only deserialization process. To affect serialization process, use
+ * [SerializersModuleBuilder.polymorphicDefaultSerializer].
*
* [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
*
* Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown
* type and have a precise serializer, so the default serializer has limited capabilities.
- * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer]
- * or [JsonContentPolymorphicSerializer] classes.
+ * If you're using `Json` format, you can get a structural access to the unknown data using `JsonContentPolymorphicSerializer`.
*
- * Default deserializers provider affects only deserialization process.
+ * @see SerializersModuleBuilder.polymorphicDefaultSerializer
*/
- @ExperimentalSerializationApi
- public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
+ public fun defaultDeserializer(defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?) {
require(this.defaultDeserializerProvider == null) {
"Default deserializer provider is already registered for class $baseClass: ${this.defaultDeserializerProvider}"
}
@@ -55,27 +56,28 @@
/**
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
+ * This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [defaultDeserializer].
+ * To affect serialization process, use [SerializersModuleBuilder.polymorphicDefaultSerializer].
+ *
* [defaultSerializerProvider] is invoked when no polymorphic serializers associated with the `className`
* were found. `className` could be `null` for formats that support nullable class discriminators
- * (currently only [Json] with [useArrayPolymorphism][JsonBuilder.useArrayPolymorphism] set to `false`)
+ * (currently only `Json` with `JsonBuilder.useArrayPolymorphism` set to `false`)
*
* [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
*
- * [defaultSerializerProvider] is named as such for backwards compatibility reasons; it provides deserializers.
- *
* Typically, if the class is not registered in advance, it is not possible to know the structure of the unknown
* type and have a precise serializer, so the default serializer has limited capabilities.
- * To have a structural access to the unknown data, it is recommended to use [JsonTransformingSerializer]
- * or [JsonContentPolymorphicSerializer] classes.
- *
- * Default deserializers provider affects only deserialization process. To affect serialization process, use
- * [SerializersModuleBuilder.polymorphicDefaultSerializer].
+ * If you're using `Json` format, you can get a structural access to the unknown data using `JsonContentPolymorphicSerializer`.
*
* @see defaultDeserializer
+ * @see SerializersModuleBuilder.polymorphicDefaultSerializer
*/
- @OptIn(ExperimentalSerializationApi::class)
- // TODO: deprecate in 1.4
- public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?) {
+ @Deprecated(
+ "Deprecated in favor of function with more precise name: defaultDeserializer",
+ ReplaceWith("defaultDeserializer(defaultSerializerProvider)"),
+ DeprecationLevel.WARNING // Since 1.5.0. Raise to ERROR in 1.6.0, hide in 1.7.0
+ )
+ public fun default(defaultSerializerProvider: (className: String?) -> DeserializationStrategy<Base>?) {
defaultDeserializer(defaultSerializerProvider)
}
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
index 86f66d7..8a9126d 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
@@ -6,8 +6,8 @@
import kotlinx.serialization.*
import kotlinx.serialization.internal.*
+import kotlin.js.*
import kotlin.jvm.*
-import kotlin.native.concurrent.*
import kotlin.reflect.*
/**
@@ -18,6 +18,9 @@
* To enable runtime serializers resolution, one of the special annotations must be used on target types
* ([Polymorphic] or [Contextual]), and a serial module with serializers should be used during construction of [SerialFormat].
*
+ * Serializers module can be built with `SerializersModule {}` builder function.
+ * Empty module can be obtained with `EmptySerializersModule()` factory function.
+ *
* @see Contextual
* @see Polymorphic
*/
@@ -28,7 +31,7 @@
"Deprecated in favor of overload with default parameter",
ReplaceWith("getContextual(kclass)"),
DeprecationLevel.HIDDEN
- ) // Was stable since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner
+ ) // Was experimental since 1.0.0, HIDDEN in 1.2.0 in a backwards-compatible manner
public fun <T : Any> getContextual(kclass: KClass<T>): KSerializer<T>? =
getContextual(kclass, emptyList())
@@ -57,7 +60,7 @@
* or default value constructed from [serializedClassName] if a default serializer provider was registered.
*/
@ExperimentalSerializationApi
- public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>?
+ public abstract fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>?
/**
* Copies contents of this module to the given [collector].
@@ -69,8 +72,10 @@
/**
* A [SerializersModule] which is empty and always returns `null`.
*/
-@SharedImmutable
-@ExperimentalSerializationApi
+@Deprecated("Deprecated in the favour of 'EmptySerializersModule()'",
+ level = DeprecationLevel.WARNING,
+ replaceWith = ReplaceWith("EmptySerializersModule()"))
+@JsName("EmptySerializersModuleLegacyJs") // Compatibility with JS
public val EmptySerializersModule: SerializersModule = SerialModuleImpl(emptyMap(), emptyMap(), emptyMap(), emptyMap(), emptyMap())
/**
@@ -122,7 +127,7 @@
override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, allowOverwrite = true)
}
@@ -146,7 +151,7 @@
) : SerializersModule() {
override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, value: T): SerializationStrategy<T>? {
- if (!value.isInstanceOf(baseClass)) return null
+ if (!baseClass.isInstance(value)) return null
// Registered
val registered = polyBase2Serializers[baseClass]?.get(value::class) as? SerializationStrategy<T>
if (registered != null) return registered
@@ -154,7 +159,7 @@
return (polyBase2DefaultSerializerProvider[baseClass] as? PolymorphicSerializerProvider<T>)?.invoke(value)
}
- override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<out T>? {
+ override fun <T : Any> getPolymorphic(baseClass: KClass<in T>, serializedClassName: String?): DeserializationStrategy<T>? {
// Registered
val registered = polyBase2NamedSerializers[baseClass]?.get(serializedClassName) as? KSerializer<out T>
if (registered != null) return registered
@@ -197,7 +202,7 @@
}
}
-internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<out Base>?
+internal typealias PolymorphicDeserializerProvider<Base> = (className: String?) -> DeserializationStrategy<Base>?
internal typealias PolymorphicSerializerProvider<Base> = (value: Base) -> SerializationStrategy<Base>?
/** This class is needed to support re-registering the same static (argless) serializers:
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
index 4c14f4b..dfb9d81 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
@@ -34,6 +34,12 @@
}
/**
+ * A [SerializersModule] which is empty and returns `null` from each method.
+ */
+@Suppress("FunctionName")
+public fun EmptySerializersModule(): SerializersModule = @Suppress("DEPRECATION") EmptySerializersModule
+
+/**
* A builder class for [SerializersModule] DSL. To create an instance of builder, use [SerializersModule] factory function.
*/
@OptIn(ExperimentalSerializationApi::class)
@@ -92,11 +98,13 @@
/**
* Adds a default serializers provider associated with the given [baseClass] to the resulting module.
- * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` were found.
+ * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` in the scope of [baseClass] were found.
*
- * This will not affect deserialization.
+ * Default serializers provider affects only serialization process. To affect deserialization process, use
+ * [SerializersModuleBuilder.polymorphicDefaultDeserializer].
+ *
+ * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
*/
- @ExperimentalSerializationApi
public override fun <Base : Any> polymorphicDefaultSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
@@ -107,17 +115,19 @@
/**
* Adds a default deserializers provider associated with the given [baseClass] to the resulting module.
* [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
- * were found. `className` could be `null` for formats that support nullable class discriminators
+ * in the scope of [baseClass] were found. `className` could be `null` for formats that support nullable class discriminators
* (currently only `Json` with `useArrayPolymorphism` set to `false`).
*
- * This will not affect serialization.
+ * Default deserializers provider affects only deserialization process. To affect serialization process, use
+ * [SerializersModuleBuilder.polymorphicDefaultSerializer].
+ *
+ * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
*
* @see PolymorphicModuleBuilder.defaultDeserializer
*/
- @ExperimentalSerializationApi
public override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
registerDefaultPolymorphicDeserializer(baseClass, defaultDeserializerProvider, false)
}
@@ -163,7 +173,7 @@
@JvmName("registerDefaultPolymorphicDeserializer") // Don't mangle method name for prettier stack traces
internal fun <Base : Any> registerDefaultPolymorphicDeserializer(
baseClass: KClass<Base>,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?,
allowOverwrite: Boolean
) {
val previous = polyBase2DefaultDeserializerProvider[baseClass]
diff --git a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
index c4af77f..c33d45a 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
@@ -47,13 +47,13 @@
/**
* Accept a default serializer provider, associated with the [baseClass] for polymorphic serialization.
+ * [defaultSerializerProvider] is invoked when no polymorphic serializers for `value` in the scope of [baseClass] were found.
*
- * This will not affect deserialization.
+ * Default serializers provider affects only serialization process. Deserializers are accepted in the
+ * [SerializersModuleCollector.polymorphicDefaultDeserializer] method.
*
- * @see SerializersModuleBuilder.polymorphicDefaultSerializer
- * @see PolymorphicModuleBuilder.defaultSerializer
+ * [defaultSerializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
*/
- @ExperimentalSerializationApi
public fun <Base : Any> polymorphicDefaultSerializer(
baseClass: KClass<Base>,
defaultSerializerProvider: (value: Base) -> SerializationStrategy<Base>?
@@ -61,30 +61,43 @@
/**
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
+ * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
+ * in the scope of [baseClass] were found. `className` could be `null` for formats that support nullable class discriminators
+ * (currently only `Json` with `useArrayPolymorphism` set to `false`).
*
- * This will not affect serialization.
+ * Default deserializers provider affects only deserialization process. Serializers are accepted in the
+ * [SerializersModuleCollector.polymorphicDefaultSerializer] method.
*
- * @see SerializersModuleBuilder.polymorphicDefaultDeserializer
- * @see PolymorphicModuleBuilder.defaultDeserializer
+ * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
*/
- @ExperimentalSerializationApi
public fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
)
/**
* Accept a default deserializer provider, associated with the [baseClass] for polymorphic deserialization.
*
- * This will not affect serialization.
+ * This function affect only deserialization process. To avoid confusion, it was deprecated and replaced with [polymorphicDefaultDeserializer].
+ * To affect serialization process, use [SerializersModuleCollector.polymorphicDefaultSerializer].
*
- * @see SerializersModuleBuilder.polymorphicDefaultDeserializer
- * @see PolymorphicModuleBuilder.defaultDeserializer
+ * [defaultDeserializerProvider] is invoked when no polymorphic serializers associated with the `className`
+ * in the scope of [baseClass] were found. `className` could be `null` for formats that support nullable class discriminators
+ * (currently only `Json` with `useArrayPolymorphism` set to `false`).
+ *
+ * [defaultDeserializerProvider] can be stateful and lookup a serializer for the missing type dynamically.
+ *
+ * @see SerializersModuleCollector.polymorphicDefaultDeserializer
+ * @see SerializersModuleCollector.polymorphicDefaultSerializer
*/
- // TODO: deprecate in 1.4
+ @Deprecated(
+ "Deprecated in favor of function with more precise name: polymorphicDefaultDeserializer",
+ ReplaceWith("polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)"),
+ DeprecationLevel.WARNING // Since 1.5.0. Raise to ERROR in 1.6.0, hide in 1.7.0
+ )
public fun <Base : Any> polymorphicDefault(
baseClass: KClass<Base>,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
polymorphicDefaultDeserializer(baseClass, defaultDeserializerProvider)
}
diff --git a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt
index fab9702..caa2768 100644
--- a/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt
@@ -4,12 +4,15 @@
package kotlinx.serialization
+import kotlinx.serialization.builtins.NothingSerializer
+import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
-import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
import kotlin.test.*
+import kotlin.time.Duration
/*
* Test ensures that type that aggregate all basic (primitive/collection/maps/arrays)
@@ -20,7 +23,7 @@
// KeyValue Input/Output
class KeyValueOutput(private val sb: StringBuilder) : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
sb.append('{')
@@ -56,7 +59,7 @@
}
class KeyValueInput(private val inp: Parser) : AbstractDecoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
inp.expectAfterWhiteSpace('{')
@@ -170,4 +173,40 @@
assertEquals(umbrellaInstance, other)
assertNotSame(umbrellaInstance, other)
}
+
+ @Test
+ fun testEncodeDuration() {
+ val sb = StringBuilder()
+ val out = KeyValueOutput(sb)
+
+ val duration = Duration.parseIsoString("P4DT12H30M5S")
+ out.encodeSerializableValue(Duration.serializer(), duration)
+
+ assertEquals("\"${duration.toIsoString()}\"", sb.toString())
+ }
+
+ @Test
+ fun testDecodeDuration() {
+ val durationString = "P4DT12H30M5S"
+ val inp = KeyValueInput(Parser(StringReader("\"$durationString\"")))
+ val other = inp.decodeSerializableValue(Duration.serializer())
+ assertEquals(Duration.parseIsoString(durationString), other)
+ }
+
+ @Test
+ fun testNothingSerialization() {
+ // impossible to deserialize Nothing
+ assertFailsWith(SerializationException::class, "'kotlin.Nothing' does not have instances") {
+ val inp = KeyValueInput(Parser(StringReader("42")))
+ @Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION")
+ inp.decodeSerializableValue(NothingSerializer())
+ }
+
+ // it is possible to serialize only `null` for `Nothing?`
+ val sb = StringBuilder()
+ val out = KeyValueOutput(sb)
+ out.encodeNullableSerializableValue(NothingSerializer(), null)
+ assertEquals("null", sb.toString())
+ }
+
}
diff --git a/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt b/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt
index d04390a..212169e 100644
--- a/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt
@@ -4,8 +4,10 @@
package kotlinx.serialization
-import kotlinx.serialization.test.noJsLegacy
+import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
import kotlin.test.*
+import kotlin.time.*
class CachedSerializersTest {
@Serializable
@@ -20,18 +22,60 @@
@Serializable
abstract class Abstract
+ @Serializable
+ enum class SerializableEnum {A, B}
+
+ @SerialInfo
+ annotation class MyAnnotation(val x: Int)
+
+ @Serializable
+ enum class SerializableMarkedEnum {
+ @SerialName("first")
+ @MyAnnotation(1)
+ C,
+ @MyAnnotation(2)
+ D
+ }
+
@Test
- fun testObjectSerializersAreSame() = noJsLegacy {
+ fun testObjectSerializersAreSame() {
assertSame(Object.serializer(), Object.serializer())
}
@Test
- fun testSealedSerializersAreSame() = noJsLegacy {
+ fun testSerializableEnumSerializersAreSame() {
+ assertSame(SerializableEnum.serializer(), SerializableEnum.serializer())
+ }
+
+ @Test
+ fun testSerializableMarkedEnumSerializersAreSame() {
+ assertSame(SerializableMarkedEnum.serializer(), SerializableMarkedEnum.serializer())
+ }
+
+ @Test
+ fun testSealedSerializersAreSame() {
assertSame(SealedParent.serializer(), SealedParent.serializer())
}
@Test
- fun testAbstractSerializersAreSame() = noJsLegacy {
+ fun testAbstractSerializersAreSame() {
assertSame(Abstract.serializer(), Abstract.serializer())
}
+
+
+ @OptIn(ExperimentalTime::class)
+ @Test
+ fun testSerializersAreIntrinsified() = jvmOnly {
+ val m = SerializersModule { }
+ val direct = measureTime {
+ Object.serializer()
+ }
+ val directMs = direct.inWholeMicroseconds
+ val indirect = measureTime {
+ m.serializer<Object>()
+ }
+ val indirectMs = indirect.inWholeMicroseconds
+ if (indirectMs > directMs + (directMs / 4)) error("Direct ($directMs) and indirect ($indirectMs) times are too far apart")
+ }
}
+
diff --git a/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt b/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt
index 714c04c..12721dd 100644
--- a/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt
@@ -108,9 +108,8 @@
*/
-
private class CommonStringDecoder(private val elementCount: Int) : AbstractDecoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
private var elementIndex = 0
override fun decodeString(): String {
diff --git a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
index a22be3f..3fee587 100644
--- a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
@@ -2,10 +2,11 @@
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.CompositeDecoder
-import kotlinx.serialization.internal.ElementMarker
+import kotlinx.serialization.internal.*
import kotlin.test.Test
import kotlin.test.assertEquals
+@OptIn(CoreFriendModuleApi::class)
class ElementMarkerTest {
@Test
fun testNothingWasRead() {
diff --git a/core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt b/core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt
new file mode 100644
index 0000000..5a3d452
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+class EnumDescriptorsTest {
+
+ @Serializable
+ enum class SerializableEnum {
+ A,
+ B
+ }
+
+ @SerialInfo
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
+ annotation class SerialAnnotation(val text: String)
+
+ @SerialAnnotation("On Class")
+ @Serializable
+ enum class FullyAnnotatedEnum {
+ @SerialAnnotation("On A")
+ A,
+
+ @SerialAnnotation("On B")
+ B
+ }
+
+ @Serializable
+ enum class EntriesAnnotatedEnum {
+ @SerialAnnotation("On A")
+ A,
+
+ @SerialAnnotation("On B")
+ B
+ }
+
+ @SerialAnnotation("On Class")
+ @Serializable
+ enum class ClassAnnotatedEnum {
+ A,
+ B
+ }
+
+ @Test
+ fun testSerializableEnum() {
+ val d = SerializableEnum.serializer().descriptor
+ assertEquals("kotlinx.serialization.EnumDescriptorsTest.SerializableEnum", d.serialName)
+
+ assertEquals("A", d.getElementName(0))
+ assertEquals("B", d.getElementName(1))
+ }
+
+ @Test
+ fun testFullyAnnotatedEnum() {
+ assertFullyAnnotated(FullyAnnotatedEnum.serializer().descriptor)
+ assertFullyAnnotated(serializer<FullyAnnotatedEnum>().descriptor)
+ }
+
+ @Test
+ fun testEntriesAnnotatedEnum() {
+ assertEntriesAnnotated(EntriesAnnotatedEnum.serializer().descriptor)
+ assertEntriesAnnotated(serializer<EntriesAnnotatedEnum>().descriptor)
+ }
+
+ @Test
+ fun testClassAnnotatedEnum() {
+ assertClassAnnotated(ClassAnnotatedEnum.serializer().descriptor)
+ assertClassAnnotated(serializer<ClassAnnotatedEnum>().descriptor)
+ }
+
+ private fun assertFullyAnnotated(descriptor: SerialDescriptor) {
+ assertEquals(1, descriptor.annotations.size)
+ assertEquals("On Class", (descriptor.annotations.first() as SerialAnnotation).text)
+
+ assertEquals(1, descriptor.getElementAnnotations(0).size)
+ assertEquals("On A", (descriptor.getElementAnnotations(0).first() as SerialAnnotation).text)
+
+ assertEquals(1, descriptor.getElementAnnotations(1).size)
+ assertEquals("On B", (descriptor.getElementAnnotations(1).first() as SerialAnnotation).text)
+ }
+
+ private fun assertEntriesAnnotated(descriptor: SerialDescriptor) {
+ assertEquals(1, descriptor.getElementAnnotations(0).size)
+ assertEquals("On A", (descriptor.getElementAnnotations(0).first() as SerialAnnotation).text)
+
+ assertEquals(1, descriptor.getElementAnnotations(1).size)
+ assertEquals("On B", (descriptor.getElementAnnotations(1).first() as SerialAnnotation).text)
+ }
+
+ private fun assertClassAnnotated(descriptor: SerialDescriptor) {
+ assertEquals(1, descriptor.annotations.size)
+ assertEquals("On Class", (descriptor.annotations.first() as SerialAnnotation).text)
+ }
+
+}
diff --git a/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt b/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt
index a117ad4..cd5b7a0 100644
--- a/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt
@@ -1,7 +1,6 @@
package kotlinx.serialization
import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.test.isJsLegacy
import kotlin.test.*
@@ -33,7 +32,6 @@
class E3: A, B
private fun doTest(descriptor: SerialDescriptor) {
- if (isJsLegacy()) return // Unsupported
val list = descriptor.annotations.filterIsInstance<InheritableDiscriminator>()
assertEquals(1, list.size)
assertEquals("a", list.first().discriminator)
diff --git a/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt
new file mode 100644
index 0000000..071045a
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt
@@ -0,0 +1,56 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.test.*
+import kotlin.reflect.KClass
+import kotlin.test.*
+
+@MetaSerializable
+@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
+annotation class MySerializable
+
+@MetaSerializable
+@Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
+annotation class MySerializableWithInfo(
+ val value: Int,
+ val kclass: KClass<*>
+)
+
+
+class MetaSerializableTest {
+
+ @MySerializable
+ class Project1(val name: String, val language: String)
+
+ @MySerializableWithInfo(123, String::class)
+ class Project2(val name: String, val language: String)
+
+ @MySerializableWithInfo(123, String::class)
+ @Serializable
+ class Project3(val name: String, val language: String)
+
+ @Serializable
+ class Wrapper(
+ @MySerializableWithInfo(234, Int::class) val project: Project3
+ )
+
+ @Test
+ fun testMetaSerializable() {
+ val serializer = serializer<Project1>()
+ assertNotNull(serializer)
+ }
+
+ @Test
+ fun testMetaSerializableWithInfo() {
+ val info = serializer<Project2>().descriptor.annotations.filterIsInstance<MySerializableWithInfo>().first()
+ assertEquals(123, info.value)
+ assertEquals(String::class, info.kclass)
+ }
+
+ @Test
+ fun testMetaSerializableOnProperty() {
+ val info = serializer<Wrapper>().descriptor
+ .getElementAnnotations(0).filterIsInstance<MySerializableWithInfo>().first()
+ assertEquals(234, info.value)
+ assertEquals(Int::class, info.kclass)
+ }
+}
diff --git a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 393e2b5..22de3a4 100644
--- a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -30,7 +30,6 @@
@Serializable
data class PolyDerived(val s: String) : PolyBase(1)
-@SharedImmutable
val BaseAndDerivedModule = SerializersModule {
polymorphic(PolyBase::class, PolyBase.serializer()) {
subclass(PolyDerived.serializer())
diff --git a/core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt b/core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt
new file mode 100644
index 0000000..deafc08
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt
@@ -0,0 +1,29 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.internal.PrimitiveSerialDescriptor
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotSame
+
+class PrimitiveSerialDescriptorTest {
+
+ @Test
+ fun testEqualsImplemented() {
+ val first = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG)
+ val second = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG)
+
+ assertNotSame(first, second)
+ assertEquals(first, second)
+ }
+
+ @Test
+ fun testHashCodeStability() {
+ val first = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG)
+ val second = PrimitiveSerialDescriptor("test_name", PrimitiveKind.LONG)
+
+ assertNotSame(first, second)
+ assertEquals(first.hashCode(), second.hashCode())
+ }
+
+}
diff --git a/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt b/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt
index f2010a2..770ac50 100644
--- a/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt
@@ -5,7 +5,6 @@
package kotlinx.serialization
import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.test.isJsLegacy
import kotlin.test.*
class SerialDescriptorAnnotationsTest {
@@ -103,7 +102,6 @@
class Holder(val r: Result, val a: AbstractResult, val o: ObjectResult, @Contextual val names: WithNames)
private fun doTest(position: Int, expected: String) {
- if (isJsLegacy()) return // Unsupported
val desc = Holder.serializer().descriptor.getElementDescriptor(position)
assertEquals(expected, desc.annotations.getCustom())
}
diff --git a/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt b/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt
index 78b015b..1ff2a7b 100644
--- a/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt
@@ -87,4 +87,11 @@
assertFailsWith<IllegalArgumentException> { PrimitiveSerialDescriptor(" ", PrimitiveKind.STRING) }
assertFailsWith<IllegalArgumentException> { PrimitiveSerialDescriptor("\t", PrimitiveKind.STRING) }
}
+
+ @Test
+ fun testNullableBuild() {
+ val descriptor = buildClassSerialDescriptor("my.Simple") {}.nullable
+ assertTrue(descriptor.isNullable)
+ assertEquals("my.Simple?", descriptor.serialName)
+ }
}
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt
index 4fb61b0..5f5a6f7 100644
--- a/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt
@@ -6,6 +6,7 @@
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
+import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.test.*
import kotlin.test.*
@@ -15,8 +16,7 @@
@Serializable(with = EnumExternalObjectSerializer::class)
enum class EnumExternalObject
- @Serializer(forClass = EnumExternalObject::class)
- object EnumExternalObjectSerializer {
+ object EnumExternalObjectSerializer: KSerializer<EnumExternalObject> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", SerialKind.ENUM)
override fun serialize(encoder: Encoder, value: EnumExternalObject) {
@@ -28,11 +28,10 @@
}
}
- @Serializable(with = EnumExternalClassSerializer::class)
+ @Serializable(with = EnumCustomClassSerializer::class)
enum class EnumExternalClass
- @Serializer(forClass = EnumExternalClass::class)
- class EnumExternalClassSerializer {
+ class EnumCustomClassSerializer: KSerializer<EnumExternalClass> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", SerialKind.ENUM)
override fun serialize(encoder: Encoder, value: EnumExternalClass) {
@@ -44,15 +43,36 @@
}
}
- @Polymorphic
- enum class EnumPolymorphic
-
@Serializable
enum class PlainEnum
+ @Serializable
+ enum class SerializableEnum { C, D }
+
+ @Serializable
+ enum class SerializableMarkedEnum { C, @SerialName("NotD") D }
+
@Test
fun testPlainEnum() {
- assertEquals(PlainEnum.serializer(), serializer<PlainEnum>())
+ assertSame(PlainEnum.serializer(), serializer<PlainEnum>())
+
+ if (!isJs()) {
+ assertIs<EnumSerializer<PlainEnum>>(serializer<PlainEnum>())
+ }
+ }
+
+ @Test
+ fun testSerializableEnumSerializer() {
+ assertIs<EnumSerializer<SerializableEnum>>(SerializableEnum.serializer())
+
+ assertSame(SerializableEnum.serializer(), serializer<SerializableEnum>())
+ }
+
+ @Test
+ fun testSerializableMarkedEnumSerializer() {
+ assertIs<EnumSerializer<SerializableMarkedEnum>>(SerializableMarkedEnum.serializer())
+
+ assertSame(SerializableMarkedEnum.serializer(), serializer<SerializableMarkedEnum>())
}
@Test
@@ -63,26 +83,7 @@
@Test
fun testEnumExternalClass() {
- assertIs<EnumExternalClassSerializer>(EnumExternalClass.serializer())
-
- if (isJvm()) {
- assertIs<EnumExternalClassSerializer>(serializer<EnumExternalClass>())
- } else if (isJsIr() || isNative()) {
- // FIXME serializer<EnumWithClassSerializer> is broken for K/JS and K/Native. Remove `assertFails` after fix
- assertFails { serializer<EnumExternalClass>() }
- }
- }
-
- @Test
- fun testEnumPolymorphic() {
- if (isJvm()) {
- assertEquals(
- PolymorphicSerializer(EnumPolymorphic::class).descriptor,
- serializer<EnumPolymorphic>().descriptor
- )
- } else {
- // FIXME serializer<PolymorphicEnum> is broken for K/JS and K/Native. Remove `assertFails` after fix
- assertFails { serializer<EnumPolymorphic>() }
- }
+ assertIs<EnumCustomClassSerializer>(EnumExternalClass.serializer())
+ assertIs<EnumCustomClassSerializer>(serializer<EnumExternalClass>())
}
}
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt
new file mode 100644
index 0000000..fc77057
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt
@@ -0,0 +1,51 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class SerializersLookupInterfaceTest {
+
+ interface I
+
+ @Polymorphic
+ interface I2
+
+ @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+ @Serializable(PolymorphicSerializer::class)
+ interface I3
+
+ @Serializable
+ @SerialName("S")
+ sealed interface S
+
+ // TODO: not working because (see #1207, plugin does not produce companion object for interfaces)
+ // We even have #1853 with tests for that
+ // @Serializable(ExternalSerializer::class)
+ // interface External
+
+
+ @Test
+ fun testSealedInterfaceLookup() {
+ val serializer = serializer<S>()
+ assertTrue(serializer is SealedClassSerializer)
+ assertEquals("S", serializer.descriptor.serialName)
+ }
+
+ @Test
+ fun testInterfaceLookup() {
+ // Native does not have KClass.isInterface
+ if (isNative() || isWasm()) return
+
+ val serializer1 = serializer<I>()
+ assertTrue(serializer1 is PolymorphicSerializer)
+ assertEquals("kotlinx.serialization.Polymorphic<I>", serializer1.descriptor.serialName)
+
+ val serializer2 = serializer<I2>()
+ assertTrue(serializer2 is PolymorphicSerializer)
+ assertEquals("kotlinx.serialization.Polymorphic<I2>", serializer2.descriptor.serialName)
+
+ val serializer3 = serializer<I3>()
+ assertTrue(serializer3 is PolymorphicSerializer)
+ assertEquals("kotlinx.serialization.Polymorphic<I3>", serializer3.descriptor.serialName)
+ }
+}
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt
new file mode 100644
index 0000000..65324c4
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("RUNTIME_ANNOTATION_NOT_SUPPORTED")
+
+package kotlinx.serialization
+
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.internal.*
+import kotlinx.serialization.test.*
+import kotlin.reflect.*
+import kotlin.test.*
+
+class SerializersLookupNamedCompanionTest {
+ @Serializable
+ class Plain(val i: Int) {
+ companion object Named
+ }
+
+ @Serializable
+ class Parametrized<T>(val value: T) {
+ companion object Named
+ }
+
+
+ @Serializer(forClass = PlainWithCustom::class)
+ object PlainSerializer
+
+ @Serializable(PlainSerializer::class)
+ class PlainWithCustom(val i: Int) {
+ companion object Named
+ }
+
+ class ParametrizedSerializer<T : Any>(val serializer: KSerializer<T>) : KSerializer<ParametrizedWithCustom<T>> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("parametrized (${serializer.descriptor})", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): ParametrizedWithCustom<T> = TODO("Not yet implemented")
+ override fun serialize(encoder: Encoder, value: ParametrizedWithCustom<T>) = TODO("Not yet implemented")
+ }
+
+ @Serializable(ParametrizedSerializer::class)
+ class ParametrizedWithCustom<T>(val i: T) {
+ companion object Named
+ }
+
+ @Serializable
+ sealed interface SealedInterface {
+ companion object Named
+ }
+
+ @Serializable
+ sealed interface SealedInterfaceWithExplicitAnnotation {
+ @NamedCompanion
+ companion object Named
+ }
+
+
+ @Test
+ fun test() {
+ assertSame<KSerializer<*>>(Plain.serializer(), serializer(typeOf<Plain>()))
+
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
+ assertSame<KSerializer<*>>(PlainSerializer, serializer(typeOf<PlainWithCustom>()))
+ }
+
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
+ assertEquals(
+ Parametrized.serializer(Int.serializer()).descriptor.toString(),
+ serializer(typeOf<Parametrized<Int>>()).descriptor.toString()
+ )
+ }
+
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
+ assertEquals(
+ ParametrizedWithCustom.serializer(Int.serializer()).descriptor.toString(),
+ serializer(typeOf<ParametrizedWithCustom<Int>>()).descriptor.toString()
+ )
+ }
+
+ shouldFail<SerializationException>(beforeKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
+ assertEquals(
+ SealedInterface.serializer().descriptor.toString(),
+ serializer(typeOf<SealedInterface>()).descriptor.toString()
+ )
+ }
+
+ // should fail because annotation @NamedCompanion will be placed again by the compilation plugin
+ // and they both will be placed into @Container annotation - thus they will be invisible to the runtime
+ shouldFail<SerializationException>(sinceKotlin = "1.9.20", onJs = false, onNative = false, onWasm = false) {
+ serializer(typeOf<SealedInterfaceWithExplicitAnnotation>())
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt
index 49efb91..38d2fbf 100644
--- a/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt
@@ -11,11 +11,10 @@
@Suppress("RemoveExplicitTypeArguments") // This is exactly what's being tested
class SerializersLookupObjectTest {
- @Serializable(with = ObjectExternalObjectSerializer::class)
+ @Serializable(with = ObjectCustomObjectSerializer::class)
object ObjectExternalObject
- @Serializer(forClass = ObjectExternalObject::class)
- object ObjectExternalObjectSerializer {
+ object ObjectCustomObjectSerializer: KSerializer<ObjectExternalObject> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", StructureKind.OBJECT)
override fun serialize(encoder: Encoder, value: ObjectExternalObject) {
@@ -27,11 +26,10 @@
}
}
- @Serializable(with = ObjectExternalClassSerializer::class)
+ @Serializable(with = ObjectCustomClassSerializer::class)
object ObjectExternalClass
- @Serializer(forClass = ObjectExternalClass::class)
- class ObjectExternalClassSerializer {
+ class ObjectCustomClassSerializer: KSerializer<ObjectExternalClass> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("tmp", StructureKind.OBJECT)
override fun serialize(encoder: Encoder, value: ObjectExternalClass) {
@@ -43,48 +41,24 @@
}
}
- @Polymorphic
- object ObjectPolymorphic
-
@Serializable
object PlainObject
@Test
fun testPlainObject() {
- if (!isJsLegacy()) {
- assertSame(PlainObject.serializer(), serializer<PlainObject>())
- }
+ assertSame(PlainObject.serializer(), serializer<PlainObject>())
}
@Test
fun testObjectExternalObject() {
- assertSame(ObjectExternalObjectSerializer, ObjectExternalObject.serializer())
- if (!isJsLegacy()) {
- assertSame(ObjectExternalObjectSerializer, serializer<ObjectExternalObject>())
- }
+ assertSame(ObjectCustomObjectSerializer, ObjectExternalObject.serializer())
+ assertSame(ObjectCustomObjectSerializer, serializer<ObjectExternalObject>())
}
@Test
fun testObjectExternalClass() {
- assertIs<ObjectExternalClassSerializer>(ObjectExternalClass.serializer())
-
- if (!isJsLegacy()) {
- assertIs<ObjectExternalClassSerializer>(serializer<ObjectExternalClass>())
- }
- }
-
- @Test
- fun testEnumPolymorphic() {
- if (isJvm()) {
- assertEquals(
- PolymorphicSerializer(ObjectPolymorphic::class).descriptor,
- serializer<ObjectPolymorphic>().descriptor
- )
- } else {
- // FIXME serializer<PolymorphicObject> is broken for K/JS and K/Native. Remove `assertFails` after fix
- assertFails { serializer<ObjectPolymorphic>() }
- }
-
+ assertIs<ObjectCustomClassSerializer>(ObjectExternalClass.serializer())
+ assertIs<ObjectCustomClassSerializer>(serializer<ObjectExternalClass>())
}
}
diff --git a/core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt b/core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt
new file mode 100644
index 0000000..9e255f2
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.internal.*
+import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.*
+import kotlin.reflect.*
+import kotlin.test.*
+
+class SerializersModuleTest {
+ @Serializable
+ object Object
+
+ @Serializable
+ sealed class SealedParent {
+ @Serializable
+ data class Child(val i: Int) : SealedParent()
+ }
+
+ @Serializable
+ abstract class Abstract
+
+ @Serializable
+ enum class SerializableEnum { A, B }
+
+ @Serializable(CustomSerializer::class)
+ class WithCustomSerializer(val i: Int)
+
+ @Serializer(forClass = WithCustomSerializer::class)
+ object CustomSerializer
+
+ @Serializable
+ class Parametrized<T : Any>(val a: T)
+
+ @Serializable
+ class ParametrizedOfNullable<T>(val a: T)
+
+ class ContextualType(val i: Int)
+
+ @Serializer(forClass = ContextualType::class)
+ object ContextualSerializer
+
+ @Serializable
+ class ContextualHolder(@Contextual val contextual: ContextualType)
+
+ @Test
+ fun testCompiled() {
+ assertSame<KSerializer<*>>(Object.serializer(), serializer(Object::class, emptyList(), false))
+ assertSame<KSerializer<*>>(SealedParent.serializer(), serializer(SealedParent::class, emptyList(), false))
+ assertSame<KSerializer<*>>(
+ SealedParent.Child.serializer(),
+ serializer(SealedParent.Child::class, emptyList(), false)
+ )
+
+ assertSame<KSerializer<*>>(Abstract.serializer(), serializer(Abstract::class, emptyList(), false))
+ assertSame<KSerializer<*>>(SerializableEnum.serializer(), serializer(SerializableEnum::class, emptyList(), false))
+ }
+
+ @Test
+ fun testBuiltIn() {
+ assertSame<KSerializer<*>>(Int.serializer(), serializer(Int::class, emptyList(), false))
+ }
+
+ @Test
+ fun testCustom() {
+ val m = SerializersModule { }
+ assertSame<KSerializer<*>>(CustomSerializer, m.serializer(WithCustomSerializer::class, emptyList(), false))
+ }
+
+ @Test
+ fun testParametrized() {
+ val serializer = serializer(Parametrized::class, listOf(Int.serializer()), false)
+ assertEquals<KClass<*>>(Parametrized.serializer(Int.serializer())::class, serializer::class)
+ assertEquals(PrimitiveKind.INT, serializer.descriptor.getElementDescriptor(0).kind)
+
+ val mapSerializer = serializer(Map::class, listOf(String.serializer(), Int.serializer()), false)
+ assertIs<MapLikeSerializer<*, *, *, *>>(mapSerializer)
+ assertEquals(PrimitiveKind.STRING, mapSerializer.descriptor.getElementDescriptor(0).kind)
+ assertEquals(PrimitiveKind.INT, mapSerializer.descriptor.getElementDescriptor(1).kind)
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun testNothingAndParameterizedOfNothing() {
+ assertEquals(NothingSerializer, Nothing::class.serializer())
+ //assertEquals(NothingSerializer, serializer<Nothing>()) // prohibited by compiler
+ assertEquals(NothingSerializer, serializer(Nothing::class, emptyList(), false) as KSerializer<Nothing>)
+ //assertEquals(NullableSerializer(NothingSerializer), serializer<Nothing?>()) // prohibited by compiler
+ assertEquals(
+ NullableSerializer(NothingSerializer),
+ serializer(Nothing::class, emptyList(), true) as KSerializer<Nothing?>
+ )
+
+ val parameterizedNothingSerializer = serializer<Parametrized<Nothing>>()
+ val nothingDescriptor = parameterizedNothingSerializer.descriptor.getElementDescriptor(0)
+ assertEquals(NothingSerialDescriptor, nothingDescriptor)
+
+ val parameterizedNullableNothingSerializer = serializer<ParametrizedOfNullable<Nothing?>>()
+ val nullableNothingDescriptor = parameterizedNullableNothingSerializer.descriptor.getElementDescriptor(0)
+ assertEquals(SerialDescriptorForNullable(NothingSerialDescriptor), nullableNothingDescriptor)
+ }
+
+ @Test
+ fun testUnsupportedArray() {
+ assertFails {
+ serializer(Array::class, listOf(Int.serializer()), false)
+ }
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun testContextual() {
+ val m = SerializersModule {
+ contextual<ContextualType>(ContextualSerializer)
+ contextual(ContextualGenericsTest.ThirdPartyBox::class) { args -> ContextualGenericsTest.ThirdPartyBoxSerializer(args[0]) }
+ }
+
+ val contextualSerializer = m.serializer(ContextualType::class, emptyList(), false)
+ assertSame<KSerializer<*>>(ContextualSerializer, contextualSerializer)
+
+ val boxSerializer = m.serializer(ContextualGenericsTest.ThirdPartyBox::class, listOf(Int.serializer()), false)
+ assertIs<ContextualGenericsTest.ThirdPartyBoxSerializer<Int>>(boxSerializer)
+ assertEquals(PrimitiveKind.INT, boxSerializer.descriptor.getElementDescriptor(0).kind)
+
+ val holderSerializer = m.serializer(ContextualHolder::class, emptyList(), false)
+ assertSame<KSerializer<*>>(ContextualHolder.serializer(), holderSerializer)
+ }
+
+}
+
diff --git a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
index 61e42f6..ce9de63 100644
--- a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
+++ b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
@@ -67,7 +67,6 @@
arrIntData.contentEquals(other.arrIntData)
}
-@SharedImmutable
val umbrellaInstance = TypesUmbrella(
Unit, true, 10, 20, 30, 40, 50.1f, 60.1, 'A', "Str0", Attitude.POSITIVE, IntData(70),
null, null, 11, 21, 31, 41, 51.1f, 61.1, 'B', "Str1", Attitude.NEUTRAL, null,
@@ -87,4 +86,4 @@
arrayOf(null, -1, -2),
arrayOf(IntData(1), IntData(2))
)
-)
+)
\ No newline at end of file
diff --git a/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt b/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt
index dece170..c3d6ac0 100644
--- a/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt
@@ -7,6 +7,8 @@
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.internal.*
import kotlinx.serialization.test.EnumSerializer
import kotlin.test.*
@@ -18,15 +20,17 @@
@Serializable
data class Box<T>(val boxed: T)
- @Serializable
+ @Serializable(Data1.Companion::class)
data class Data1(val l: List<Int> = emptyList(), val s: String) {
- @Serializer(forClass = Data1::class)
- companion object {
- // TODO removal of explicit type crashes the compiler
- override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Data1") {
+ companion object: KSerializer<Data1> {
+ override val descriptor = buildClassSerialDescriptor("Data1") {
element("l", listSerialDescriptor<Int>(), isOptional = true)
element("s", serialDescriptor<String>())
}
+
+ override fun serialize(encoder: Encoder, value: Data1) = error("Should not be called")
+
+ override fun deserialize(decoder: Decoder): Data1 = error("Should not be called")
}
}
diff --git a/core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt b/core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt
new file mode 100644
index 0000000..433f9ba
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.test.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+
+class SealedInterfacesSerializationTest {
+ interface A
+
+ sealed interface B
+
+ @Serializable
+ sealed interface C
+
+ @Serializable(DummySerializer::class)
+ sealed interface D
+
+ @Serializable(DummySerializer::class)
+ interface E
+
+ @Serializable
+ @Polymorphic
+ sealed interface F
+
+ @Serializable
+ class ImplA : A, B, C, D, E, F
+
+ @Serializable
+ class ImplB : A, B, C, D, E, F
+
+ @Serializable
+ class Holder(
+ val a: A,
+ val b: B,
+ val c: C,
+ val d: D,
+ val e: E,
+ @Polymorphic val polyC: C,
+ val f: F
+ )
+
+ class DummySerializer : KSerializer<Any> {
+ override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Dummy")
+
+ override fun serialize(encoder: Encoder, value: Any) {
+ error("serialize")
+ }
+
+ override fun deserialize(decoder: Decoder): Any {
+ error("deserialize")
+ }
+ }
+
+ private fun SerialDescriptor.haveSealedSubclasses() {
+ assertEquals(PolymorphicKind.SEALED, kind)
+ val subclasses = getElementDescriptor(1).elementDescriptors.map { it.serialName.substringAfterLast('.') }
+ assertEquals(listOf("ImplA", "ImplB"), subclasses)
+ }
+
+ private fun SerialDescriptor.isDummy() = serialName == "Dummy"
+
+ private fun SerialDescriptor.isPolymorphic() = kind == PolymorphicKind.OPEN
+
+ operator fun SerialDescriptor.get(i: Int) = getElementDescriptor(i)
+
+ @Test
+ fun testInHolder() {
+ val desc = Holder.serializer().descriptor
+ desc[0].isPolymorphic()
+ desc[1].isPolymorphic()
+ desc[2].haveSealedSubclasses()
+ desc[3].isDummy()
+ desc[4].isDummy()
+ desc[5].isPolymorphic()
+ desc[6].isPolymorphic()
+ }
+
+ @Test
+ fun testGenerated() {
+ C.serializer().descriptor.haveSealedSubclasses()
+ }
+
+ @Test
+ fun testResolved() {
+ serializer<C>().descriptor.haveSealedSubclasses()
+ }
+
+
+}
diff --git a/core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt b/core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt
new file mode 100644
index 0000000..696883c
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.modules.*
+
+/**
+ * The purpose of this decoder is to check whether its methods were called currectly,
+ * rather than implement any concrete format.
+ */
+class DummySequentialDecoder(
+ override val serializersModule: SerializersModule = EmptySerializersModule()
+) : Decoder, CompositeDecoder {
+ private fun notImplemented(): Nothing = throw Error("Implement this method if needed")
+
+ override fun decodeSequentially(): Boolean = true
+ override fun decodeElementIndex(descriptor: SerialDescriptor): Int = throw Error("This method shouldn't be called in sequential mode")
+
+ var beginStructureCalled = 0
+ var endStructureCalled = 0
+
+ override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
+ ++beginStructureCalled
+ return this
+ }
+ override fun endStructure(descriptor: SerialDescriptor): Unit {
+ ++endStructureCalled
+ return Unit
+ }
+
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder = notImplemented()
+
+ override fun decodeBoolean(): Boolean = notImplemented()
+ override fun decodeByte(): Byte = notImplemented()
+ override fun decodeShort(): Short = notImplemented()
+ override fun decodeInt(): Int = notImplemented()
+ override fun decodeLong(): Long = notImplemented()
+ override fun decodeFloat(): Float = notImplemented()
+ override fun decodeDouble(): Double = notImplemented()
+ override fun decodeChar(): Char = notImplemented()
+ override fun decodeString(): String = notImplemented()
+ override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = notImplemented()
+
+ override fun decodeNotNullMark(): Boolean = notImplemented()
+ override fun decodeNull(): Nothing? = notImplemented()
+
+ override fun decodeBooleanElement(descriptor: SerialDescriptor, index: Int): Boolean = notImplemented()
+ override fun decodeByteElement(descriptor: SerialDescriptor, index: Int): Byte = notImplemented()
+ override fun decodeShortElement(descriptor: SerialDescriptor, index: Int): Short = notImplemented()
+ override fun decodeIntElement(descriptor: SerialDescriptor, index: Int): Int = notImplemented()
+ override fun decodeLongElement(descriptor: SerialDescriptor, index: Int): Long = notImplemented()
+ override fun decodeFloatElement(descriptor: SerialDescriptor, index: Int): Float = notImplemented()
+ override fun decodeDoubleElement(descriptor: SerialDescriptor, index: Int): Double = notImplemented()
+ override fun decodeCharElement(descriptor: SerialDescriptor, index: Int): Char = notImplemented()
+ override fun decodeStringElement(descriptor: SerialDescriptor, index: Int): String = notImplemented()
+
+ override fun decodeInlineElement(descriptor: SerialDescriptor, index: Int): Decoder = notImplemented()
+ override fun <T : Any?> decodeSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T>, previousValue: T?): T = decodeSerializableValue(deserializer)
+ override fun <T : Any> decodeNullableSerializableElement(descriptor: SerialDescriptor, index: Int, deserializer: DeserializationStrategy<T?>, previousValue: T?): T? = notImplemented()
+}
diff --git a/core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt b/core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt
new file mode 100644
index 0000000..2234b8f
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlin.test.*
+import kotlinx.serialization.*
+
+class ObjectSerializerTest {
+ @Test
+ fun testSequentialDecoding() {
+ SimpleObject.serializer().deserialize(DummySequentialDecoder())
+ }
+
+ @Serializable
+ object SimpleObject
+}
diff --git a/core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt b/core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt
new file mode 100644
index 0000000..ea0804c
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlin.test.*
+import kotlinx.serialization.builtins.*
+
+class TuplesTest {
+ @Test
+ fun testSequentialDecodingKeyValue() {
+ val decoder = DummySequentialDecoder()
+ val serializer = MapEntrySerializer(Unit.serializer(), Unit.serializer())
+ serializer.deserialize(decoder)
+ assertEquals(decoder.beginStructureCalled, decoder.endStructureCalled)
+ }
+}
diff --git a/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt b/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt
new file mode 100644
index 0000000..7bd35c1
--- /dev/null
+++ b/core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+import kotlin.test.*
+
+private val currentKotlinVersion = KotlinVersion.CURRENT
+
+private fun String.toKotlinVersion(): KotlinVersion {
+ val parts = split(".")
+ val intParts = parts.mapNotNull { it.toIntOrNull() }
+ if (parts.size != 3 || intParts.size != 3) error("Illegal kotlin version, expected format is 1.2.3")
+
+ return KotlinVersion(intParts[0], intParts[1], intParts[2])
+}
+
+internal fun runSince(kotlinVersion: String, test: () -> Unit) {
+ if (currentKotlinVersion >= kotlinVersion.toKotlinVersion()) {
+ test()
+ }
+}
+
+
+internal inline fun <reified T : Throwable> shouldFail(
+ sinceKotlin: String? = null,
+ beforeKotlin: String? = null,
+ onJvm: Boolean = true,
+ onJs: Boolean = true,
+ onNative: Boolean = true,
+ onWasm: Boolean = true,
+ test: () -> Unit
+) {
+ val args = mapOf(
+ "since" to sinceKotlin,
+ "before" to beforeKotlin,
+ "onJvm" to onJvm,
+ "onJs" to onJs,
+ "onNative" to onNative,
+ "onWasm" to onWasm
+ )
+
+ val sinceVersion = sinceKotlin?.toKotlinVersion()
+ val beforeVersion = beforeKotlin?.toKotlinVersion()
+
+ val version = (sinceVersion != null && currentKotlinVersion >= sinceVersion)
+ || (beforeVersion != null && currentKotlinVersion < beforeVersion)
+
+ val platform = (isJvm() && onJvm) || (isJs() && onJs) || (isNative() && onNative) || (isWasm() && onWasm)
+
+ var error: Throwable? = null
+ try {
+ test()
+ } catch (e: Throwable) {
+ error = e
+ }
+
+ if (version && platform) {
+ if (error == null) {
+ throw AssertionError("Exception with type '${T::class.simpleName}' expected for $args")
+ }
+ if (error !is T) throw AssertionError(
+ "Illegal exception type, expected '${T::class.simpleName}' actual '${error::class.simpleName}' for $args",
+ error
+ )
+ } else {
+ if (error != null) throw AssertionError(
+ "Unexpected error for $args",
+ error
+ )
+ }
+}
+
+internal class CompilerVersionTest {
+ @Test
+ fun testSince() {
+ var executed = false
+
+ runSince("1.0.0") {
+ executed = true
+ }
+ assertTrue(executed)
+
+ executed = false
+ runSince("255.255.255") {
+ executed = true
+ }
+ assertFalse(executed)
+ }
+
+ @Test
+ fun testFailBefore() {
+ // ok if there is no exception if current version greater is before of the specified
+ shouldFail<IllegalArgumentException>(beforeKotlin = "0.0.0") {
+ // no-op
+ }
+
+ // error if there is no exception and if current version is before of the specified
+ assertFails {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255") {
+ // no-op
+ }
+ }
+
+ // ok if thrown expected exception if current version is before of the specified
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255") {
+ throw IllegalArgumentException()
+ }
+
+ // ok if thrown unexpected exception if current version is before of the specified
+ assertFails {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255") {
+ throw Exception()
+ }
+ }
+
+ }
+
+ @Test
+ fun testFailSince() {
+ // ok if there is no exception if current version less then specified
+ shouldFail<IllegalArgumentException>(sinceKotlin = "255.255.255") {
+ // no-op
+ }
+
+ // error if there is no exception and if current version is greater or equals specified
+ assertFails {
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0") {
+ // no-op
+ }
+ }
+
+ // ok if thrown expected exception if current version is greater or equals specified
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0") {
+ throw IllegalArgumentException()
+ }
+
+ // ok if thrown unexpected exception if current version is greater or equals specified
+ assertFails {
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0") {
+ throw Exception()
+ }
+ }
+ }
+
+ @Test
+ fun testExcludePlatform() {
+ if (isJvm()) {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onJvm = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onJvm = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onJvm = false) {
+ // no-op
+ }
+ } else if (isJs()) {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onJs = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onJs = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onJs = false) {
+ // no-op
+ }
+ } else if (isWasm()) {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onWasm = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onWasm = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onWasm = false) {
+ // no-op
+ }
+ } else if (isNative()) {
+ shouldFail<IllegalArgumentException>(beforeKotlin = "255.255.255", onNative = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", onNative = false) {
+ // no-op
+ }
+ shouldFail<IllegalArgumentException>(sinceKotlin = "0.0.0", beforeKotlin = "255.255.255", onNative = false) {
+ // no-op
+ }
+ }
+ }
+
+}
diff --git a/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
index c47252d..594ec0b 100644
--- a/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
+++ b/core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
@@ -5,13 +5,12 @@
package kotlinx.serialization.test
enum class Platform {
- JVM, JS_LEGACY, JS_IR, NATIVE
+ JVM, JS, NATIVE, WASM
}
public expect val currentPlatform: Platform
-public fun isJs(): Boolean = currentPlatform == Platform.JS_LEGACY || currentPlatform == Platform.JS_IR
-public fun isJsLegacy(): Boolean = currentPlatform == Platform.JS_LEGACY
-public fun isJsIr(): Boolean = currentPlatform == Platform.JS_IR
+public fun isJs(): Boolean = currentPlatform == Platform.JS
public fun isJvm(): Boolean = currentPlatform == Platform.JVM
public fun isNative(): Boolean = currentPlatform == Platform.NATIVE
+public fun isWasm(): Boolean = currentPlatform == Platform.WASM
diff --git a/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt b/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
index 660d1ef..8697450 100644
--- a/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
+++ b/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
@@ -34,10 +34,6 @@
if (!isJs()) test()
}
-inline fun noJsLegacy(test: () -> Unit) {
- if (!isJsLegacy()) test()
-}
-
inline fun jvmOnly(test: () -> Unit) {
if (isJvm()) test()
}
diff --git a/core/jsMain/src/kotlinx/serialization/Serializers.kt b/core/jsMain/src/kotlinx/serialization/SerializersJs.kt
similarity index 100%
rename from core/jsMain/src/kotlinx/serialization/Serializers.kt
rename to core/jsMain/src/kotlinx/serialization/SerializersJs.kt
diff --git a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
index 25c4814..6bd6339 100644
--- a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
@@ -16,19 +16,35 @@
if (index !in indices) throw IndexOutOfBoundsException("Index $index out of bounds $indices")
return get(index)
}
-@Suppress("UNCHECKED_CAST")
+
internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
- this.constructSerializerForGivenTypeArgs() ?: this.js.asDynamic().Companion?.serializer() as? KSerializer<T>
+ this.constructSerializerForGivenTypeArgs() ?: (
+ if (this === Nothing::class) NothingSerializer // Workaround for KT-51333
+ else this.js.asDynamic().Companion?.serializer()
+ ) as? KSerializer<T>
+
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return object: SerializerCache<T> {
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return factory(key)
+ }
+ }
+}
+
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return object: ParametrizedSerializerCache<T> {
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return kotlin.runCatching { factory(key, types) }
+ }
+ }
+}
internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray()
-internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this)
-
internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing {
throw SerializationException(
- "Serializer for class '${simpleName}' is not found.\n" +
- "Mark the class as @Serializable or provide the serializer explicitly.\n" +
- "On Kotlin/JS explicitly declared serializer should be used for interfaces and enums without @Serializable annotation"
+ notRegisteredMessage() +
+ "To get enum serializer on Kotlin/JS, it should be annotated with @Serializable annotation."
)
}
diff --git a/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index b87276e..23627d1 100644
--- a/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -4,9 +4,4 @@
package kotlinx.serialization.test
-public actual val currentPlatform: Platform = if (isLegacyBackend()) Platform.JS_LEGACY else Platform.JS_IR
-
-// from https://github.com/JetBrains/kotlin/blob/569187a7516e2e5ab217158a3170d4beb0c5cb5a/js/js.translator/testData/_commonFiles/testUtils.kt#L3
-private fun isLegacyBackend(): Boolean =
- // Using eval to prevent DCE from thinking that following code depends on Kotlin module.
- eval("(typeof Kotlin != \"undefined\" && typeof Kotlin.kotlin != \"undefined\")").unsafeCast<Boolean>()
+public actual val currentPlatform: Platform = Platform.JS
diff --git a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
index b110f12..b2d8da7 100644
--- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
+++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
@@ -17,65 +17,90 @@
import kotlin.reflect.*
/**
- * Reflectively constructs a serializer for the given reflective Java [type].
- * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit,
- * that operate with reflective Java [Type] and cannot use [typeOf].
+ * Reflectively retrieves a serializer for the given [type].
*
- * For application-level serialization, it is recommended to use `serializer<T>()` instead as it is aware of
+ * This overload is intended to be used as an interoperability layer for JVM-centric libraries,
+ * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf].
+ * For application-level serialization, it is recommended to use `serializer<T>()` or `serializer(KType)` instead as it is aware of
* Kotlin-specific type information, such as nullability, sealed classes and object singletons.
*
+ * Note that because [Type] does not contain any information about nullability, all created serializers
+ * work only with non-nullable data.
+ *
+ * Not all [Type] implementations are supported.
+ * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType].
+ *
* @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided.
*/
-@ExperimentalSerializationApi
-public fun serializer(type: Type): KSerializer<Any> = EmptySerializersModule.serializer(type)
+public fun serializer(type: Type): KSerializer<Any> = EmptySerializersModule().serializer(type)
/**
- * Reflectively constructs a serializer for the given reflective Java [type].
- * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit,
- * that operate with reflective Java [Type] and cannot use [typeOf].
+ * Reflectively retrieves a serializer for the given [type].
*
- * For application-level serialization, it is recommended to use `serializer<T>()` instead as it is aware of
+ * This overload is intended to be used as an interoperability layer for JVM-centric libraries,
+ * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf].
+ * For application-level serialization, it is recommended to use `serializer<T>()` or `serializer(KType)` instead as it is aware of
* Kotlin-specific type information, such as nullability, sealed classes and object singletons.
*
- * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * Note that because [Type] does not contain any information about nullability, all created serializers
+ * work only with non-nullable data.
+ *
+ * Not all [Type] implementations are supported.
+ * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType].
+ *
+ * @return [KSerializer] for given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable).
+ * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided.
*/
-@ExperimentalSerializationApi
-public fun serializerOrNull(type: Type): KSerializer<Any>? = EmptySerializersModule.serializerOrNull(type)
+public fun serializerOrNull(type: Type): KSerializer<Any>? = EmptySerializersModule().serializerOrNull(type)
/**
- * Retrieves serializer for the given reflective Java [type] using
- * reflective construction and [contextual][SerializersModule.getContextual] lookup for non-serializable types.
+ * Retrieves a serializer for the given [type] using
+ * reflective construction and [contextual][SerializersModule.getContextual] lookup as a fallback for non-serializable types.
*
- * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit,
- * that operate with reflective Java [Type] and cannot use [typeOf].
- *
- * For application-level serialization, it is recommended to use `serializer<T>()` instead as it is aware of
+ * This overload is intended to be used as an interoperability layer for JVM-centric libraries,
+ * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf].
+ * For application-level serialization, it is recommended to use `serializer<T>()` or `serializer(KType)` instead as it is aware of
* Kotlin-specific type information, such as nullability, sealed classes and object singletons.
*
+ * Note that because [Type] does not contain any information about nullability, all created serializers
+ * work only with non-nullable data.
+ *
+ * Not all [Type] implementations are supported.
+ * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType].
+ *
* @throws SerializationException if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided.
*/
-@ExperimentalSerializationApi
public fun SerializersModule.serializer(type: Type): KSerializer<Any> =
- serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true) ?: type.prettyClass().serializerNotRegistered()
+ serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = true)
+ ?: type.prettyClass().serializerNotRegistered()
/**
- * Retrieves serializer for the given reflective Java [type] using
- * reflective construction and [contextual][SerializersModule.getContextual] lookup for non-serializable types.
+ * Retrieves a serializer for the given [type] using
+ * reflective construction and [contextual][SerializersModule.getContextual] lookup as a fallback for non-serializable types.
*
- * [serializer] is intended to be used as an interoperability layer for libraries like GSON and Retrofit,
- * that operate with reflective Java [Type] and cannot use [typeOf].
- *
- * For application-level serialization, it is recommended to use `serializer<T>()` instead as it is aware of
+ * This overload is intended to be used as an interoperability layer for JVM-centric libraries,
+ * that operate with Java's type tokens and cannot use Kotlin's [KType] or [typeOf].
+ * For application-level serialization, it is recommended to use `serializer<T>()` or `serializer(KType)` instead as it is aware of
* Kotlin-specific type information, such as nullability, sealed classes and object singletons.
*
- * Returns `null` if serializer cannot be created (provided [type] or its type argument is not serializable).
+ * Note that because [Type] does not contain any information about nullability, all created serializers
+ * work only with non-nullable data.
+ *
+ * Not all [Type] implementations are supported.
+ * [type] must be an instance of [Class], [GenericArrayType], [ParameterizedType] or [WildcardType].
+ *
+ * @return [KSerializer] for given [type] or `null` if serializer cannot be created (given [type] or its type argument is not serializable).
+ * @throws IllegalArgumentException if an unsupported subclass of [Type] is provided.
*/
-@ExperimentalSerializationApi
public fun SerializersModule.serializerOrNull(type: Type): KSerializer<Any>? =
serializerByJavaTypeImpl(type, failOnMissingTypeArgSerializer = false)
-@OptIn(ExperimentalSerializationApi::class)
-private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissingTypeArgSerializer: Boolean = true): KSerializer<Any>? =
+private fun SerializersModule.serializerByJavaTypeImpl(
+ type: Type,
+ failOnMissingTypeArgSerializer: Boolean = true
+): KSerializer<Any>? =
when (type) {
is GenericArrayType -> {
genericArraySerializer(type, failOnMissingTypeArgSerializer)
@@ -85,7 +110,9 @@
val rootClass = (type.rawType as Class<*>)
val args = (type.actualTypeArguments)
val argsSerializers =
- if (failOnMissingTypeArgSerializer) args.map { serializer(it) } else args.map { serializerOrNull(it) ?: return null }
+ if (failOnMissingTypeArgSerializer) args.map { serializer(it) } else args.map {
+ serializerOrNull(it) ?: return null
+ }
when {
Set::class.java.isAssignableFrom(rootClass) -> SetSerializer(argsSerializers[0]) as KSerializer<Any>
List::class.java.isAssignableFrom(rootClass) || Collection::class.java.isAssignableFrom(rootClass) -> ListSerializer(
@@ -110,19 +137,20 @@
) as KSerializer<Any>
else -> {
- // probably we should deprecate this method because it can't differ nullable vs non-nullable types
- // since it uses Java TypeToken, not Kotlin one
val varargs = argsSerializers.map { it as KSerializer<Any?> }
reflectiveOrContextual(rootClass as Class<Any>, varargs)
}
}
}
is WildcardType -> serializerByJavaTypeImpl(type.upperBounds.first())
- else -> throw IllegalArgumentException("typeToken should be an instance of Class<?>, GenericArray, ParametrizedType or WildcardType, but actual type is $type ${type::class}")
+ else -> throw IllegalArgumentException("type should be an instance of Class<?>, GenericArrayType, ParametrizedType or WildcardType, but actual argument $type has type ${type::class}")
}
@OptIn(ExperimentalSerializationApi::class)
-private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeArgSerializer: Boolean): KSerializer<Any>? {
+private fun SerializersModule.typeSerializer(
+ type: Class<*>,
+ failOnMissingTypeArgSerializer: Boolean
+): KSerializer<Any>? {
return if (type.isArray && !type.componentType.isPrimitive) {
val eType: Class<*> = type.componentType
val s = if (failOnMissingTypeArgSerializer) serializer(eType) else (serializerOrNull(eType) ?: return null)
@@ -134,7 +162,10 @@
}
@OptIn(ExperimentalSerializationApi::class)
-private fun <T : Any> SerializersModule.reflectiveOrContextual(jClass: Class<T>, typeArgumentsSerializers: List<KSerializer<Any?>>): KSerializer<T>? {
+private fun <T : Any> SerializersModule.reflectiveOrContextual(
+ jClass: Class<T>,
+ typeArgumentsSerializers: List<KSerializer<Any?>>
+): KSerializer<T>? {
jClass.constructSerializerForGivenTypeArgs(*typeArgumentsSerializers.toTypedArray())?.let { return it }
val kClass = jClass.kotlin
return kClass.builtinSerializerOrNull() ?: getContextual(kClass, typeArgumentsSerializers)
@@ -165,5 +196,5 @@
is ParameterizedType -> it.rawType.prettyClass()
is WildcardType -> it.upperBounds.first().prettyClass()
is GenericArrayType -> it.genericComponentType.prettyClass()
- else -> throw IllegalArgumentException("typeToken should be an instance of Class<?>, GenericArray, ParametrizedType or WildcardType, but actual type is $it ${it::class}")
+ else -> throw IllegalArgumentException("type should be an instance of Class<?>, GenericArrayType, ParametrizedType or WildcardType, but actual argument $it has type ${it::class}")
}
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt
new file mode 100644
index 0000000..191b30c
--- /dev/null
+++ b/core/jvmMain/src/kotlinx/serialization/internal/Caching.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.KSerializer
+import java.lang.ref.SoftReference
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.reflect.KClass
+import kotlin.reflect.KClassifier
+import kotlin.reflect.KType
+import kotlin.reflect.KTypeProjection
+
+/*
+ * By default, we use ClassValue-based caches to avoid classloader leaks,
+ * but ClassValue is not available on Android, thus we attempt to check it dynamically
+ * and fallback to ConcurrentHashMap-based cache.
+ */
+private val useClassValue = try {
+ Class.forName("java.lang.ClassValue")
+ true
+} catch (_: Throwable) {
+ false
+}
+
+/**
+ * Creates a **strongly referenced** cache of values associated with [Class].
+ * Serializers are computed using provided [factory] function.
+ *
+ * `null` values are not supported, though there aren't any technical limitations.
+ */
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return if (useClassValue) ClassValueCache(factory) else ConcurrentHashMapCache(factory)
+}
+
+/**
+ * Creates a **strongly referenced** cache of values associated with [Class].
+ * Serializers are computed using provided [factory] function.
+ *
+ * `null` values are not supported, though there aren't any technical limitations.
+ */
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return if (useClassValue) ClassValueParametrizedCache(factory) else ConcurrentHashMapParametrizedCache(factory)
+}
+
+private class ClassValueCache<T>(val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
+ private val classValue = ClassValueReferences<CacheEntry<T>>()
+
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return classValue
+ .getOrSet(key.java) { CacheEntry(compute(key)) }
+ .serializer
+ }
+}
+
+/**
+ * A class that combines the capabilities of ClassValue and SoftReference.
+ * Softly binds the calculated value to the specified class.
+ *
+ * [SoftReference] used to prevent class loaders from leaking,
+ * since the value can transitively refer to an instance of type [Class], this may prevent the loader from
+ * being collected during garbage collection.
+ *
+ * In the first calculation the value is cached, every time [getOrSet] is called, a pre-calculated value is returned.
+ *
+ * However, the value can be collected during garbage collection (thanks to [SoftReference])
+ * - in this case, when trying to call the [getOrSet] function, the value will be calculated again and placed in the cache.
+ *
+ * An important requirement for a function generating a value is that it must be stable, so that each time it is called for the same class, the function returns similar values.
+ * In the case of serializers, these should be instances of the same class filled with equivalent values.
+ */
+@SuppressAnimalSniffer
+private class ClassValueReferences<T> : ClassValue<MutableSoftReference<T>>() {
+ override fun computeValue(type: Class<*>): MutableSoftReference<T> {
+ return MutableSoftReference()
+ }
+
+ inline fun getOrSet(key: Class<*>, crossinline factory: () -> T): T {
+ val ref: MutableSoftReference<T> = get(key)
+
+ ref.reference.get()?.let { return it }
+
+ // go to the slow path and create serializer with blocking, also wrap factory block
+ return ref.getOrSetWithLock { factory() }
+ }
+
+}
+
+/**
+ * Wrapper over `SoftReference`, used to store a mutable value.
+ */
+private class MutableSoftReference<T> {
+ // volatile because of situations like https://stackoverflow.com/a/7855774
+ @JvmField
+ @Volatile
+ var reference: SoftReference<T> = SoftReference(null)
+
+ /*
+ It is important that the monitor for synchronized is the `MutableSoftReference` of a specific class
+ This way access to reference is blocked only for one serializable class, and not for all
+ */
+ @Synchronized
+ fun getOrSetWithLock(factory: () -> T): T {
+ // exit function if another thread has already filled in the `reference` with non-null value
+ reference.get()?.let { return it }
+
+ val value = factory()
+ reference = SoftReference(value)
+ return value
+ }
+}
+
+private class ClassValueParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) :
+ ParametrizedSerializerCache<T> {
+ private val classValue = ClassValueReferences<ParametrizedCacheEntry<T>>()
+
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return classValue.getOrSet(key.java) { ParametrizedCacheEntry() }
+ .computeIfAbsent(types) { compute(key, types) }
+ }
+}
+
+/**
+ * We no longer support Java 6, so the only place we use this cache is Android, where there
+ * are no classloader leaks issue, thus we can safely use strong references and do not bother
+ * with WeakReference wrapping.
+ */
+private class ConcurrentHashMapCache<T>(private val compute: (KClass<*>) -> KSerializer<T>?) : SerializerCache<T> {
+ private val cache = ConcurrentHashMap<Class<*>, CacheEntry<T>>()
+
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return cache.getOrPut(key.java) {
+ CacheEntry(compute(key))
+ }.serializer
+ }
+}
+
+
+private class ConcurrentHashMapParametrizedCache<T>(private val compute: (KClass<Any>, List<KType>) -> KSerializer<T>?) :
+ ParametrizedSerializerCache<T> {
+ private val cache = ConcurrentHashMap<Class<*>, ParametrizedCacheEntry<T>>()
+
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return cache.getOrPut(key.java) { ParametrizedCacheEntry() }
+ .computeIfAbsent(types) { compute(key, types) }
+ }
+}
+
+/**
+ * Wrapper for cacheable serializer of some type.
+ * Used to store cached serializer or indicates that the serializer is not cacheable.
+ *
+ * If serializer for type is not cacheable then value of [serializer] is `null`.
+ */
+private class CacheEntry<T>(@JvmField val serializer: KSerializer<T>?)
+
+/**
+ * Workaround of https://youtrack.jetbrains.com/issue/KT-54611 and https://github.com/Kotlin/kotlinx.serialization/issues/2065
+ */
+private class KTypeWrapper(private val origin: KType) : KType {
+ override val annotations: List<Annotation>
+ get() = origin.annotations
+ override val arguments: List<KTypeProjection>
+ get() = origin.arguments
+ override val classifier: KClassifier?
+ get() = origin.classifier
+ override val isMarkedNullable: Boolean
+ get() = origin.isMarkedNullable
+
+ override fun equals(other: Any?): Boolean {
+ if (other == null) return false
+ if (origin != (other as? KTypeWrapper)?.origin) return false
+
+ val kClassifier = classifier
+ if (kClassifier is KClass<*>) {
+ val otherClassifier = (other as? KType)?.classifier
+ if (otherClassifier == null || otherClassifier !is KClass<*>) {
+ return false
+ }
+ return kClassifier.java == otherClassifier.java
+ } else {
+ return false
+ }
+ }
+
+ override fun hashCode(): Int {
+ return origin.hashCode()
+ }
+
+ override fun toString(): String {
+ return "KTypeWrapper: $origin"
+ }
+}
+
+private class ParametrizedCacheEntry<T> {
+ private val serializers: ConcurrentHashMap<List<KTypeWrapper>, Result<KSerializer<T>?>> = ConcurrentHashMap()
+ inline fun computeIfAbsent(types: List<KType>, producer: () -> KSerializer<T>?): Result<KSerializer<T>?> {
+ val wrappedTypes = types.map { KTypeWrapper(it) }
+ return serializers.getOrPut(wrappedTypes) {
+ kotlin.runCatching { producer() }
+ }
+ }
+}
+
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
index 9dbb5a0..72ec9ea 100644
--- a/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/jvmMain/src/kotlinx/serialization/internal/Platform.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.internal
@@ -18,7 +18,6 @@
return get(index)
}
-@Suppress("UNCHECKED_CAST")
internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
this.constructSerializerForGivenTypeArgs()
@@ -29,40 +28,58 @@
internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing = serializerNotRegistered()
internal fun Class<*>.serializerNotRegistered(): Nothing {
- throw SerializationException(
- "Serializer for class '${simpleName}' is not found.\n" +
- "Mark the class as @Serializable or provide the serializer explicitly."
- )
+ throw SerializationException(this.kotlin.notRegisteredMessage())
}
internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
return java.constructSerializerForGivenTypeArgs(*args)
}
-@Suppress("UNCHECKED_CAST")
internal fun <T: Any> Class<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? {
if (isEnum && isNotAnnotated()) {
return createEnumSerializer()
}
- if (isInterface) {
- return interfaceSerializer()
- }
+ // Fall-through if the serializer is not found -- lookup on companions (for sealed interfaces) or fallback to polymorphic if applicable
+ if (isInterface) interfaceSerializer()?.let { return it }
// Search for serializer defined on companion object.
- val serializer = invokeSerializerOnCompanion<T>(this, *args)
+ val serializer = invokeSerializerOnDefaultCompanion<T>(this, *args)
if (serializer != null) return serializer
// Check whether it's serializable object
findObjectSerializer()?.let { return it }
// Search for default serializer if no serializer is defined in companion object.
// It is required for named companions
- val fromNamedCompanion = try {
+ val fromNamedCompanion = findInNamedCompanion(*args)
+ if (fromNamedCompanion != null) return fromNamedCompanion
+ // Check for polymorphic
+ return if (isPolymorphicSerializer()) {
+ PolymorphicSerializer(this.kotlin)
+ } else {
+ null
+ }
+}
+
+@Suppress("UNCHECKED_CAST")
+private fun <T: Any> Class<T>.findInNamedCompanion(vararg args: KSerializer<Any?>): KSerializer<T>? {
+ val namedCompanion = findNamedCompanionByAnnotation()
+ if (namedCompanion != null) {
+ invokeSerializerOnCompanion<T>(namedCompanion, *args)?.let { return it }
+ }
+
+ // fallback strategy for old compiler - try to locate plugin-generated singleton (without type parameters) serializer
+ return try {
declaredClasses.singleOrNull { it.simpleName == ("\$serializer") }
?.getField("INSTANCE")?.get(null) as? KSerializer<T>
} catch (e: NoSuchFieldException) {
null
}
- if (fromNamedCompanion != null) return fromNamedCompanion
- // Check for polymorphic
- return polymorphicSerializer()
+}
+
+private fun <T: Any> Class<T>.findNamedCompanionByAnnotation(): Any? {
+ val companionClass = declaredClasses.firstOrNull { clazz ->
+ clazz.getAnnotation(NamedCompanion::class.java) != null
+ } ?: return null
+
+ return companionOrNull(companionClass.simpleName)
}
private fun <T: Any> Class<T>.isNotAnnotated(): Boolean {
@@ -73,19 +90,19 @@
getAnnotation(Polymorphic::class.java) == null
}
-private fun <T: Any> Class<T>.polymorphicSerializer(): KSerializer<T>? {
+private fun <T: Any> Class<T>.isPolymorphicSerializer(): Boolean {
/*
* Last resort: check for @Polymorphic or Serializable(with = PolymorphicSerializer::class)
* annotations.
*/
if (getAnnotation(Polymorphic::class.java) != null) {
- return PolymorphicSerializer(this.kotlin)
+ return true
}
val serializable = getAnnotation(Serializable::class.java)
if (serializable != null && serializable.with == PolymorphicSerializer::class) {
- return PolymorphicSerializer(this.kotlin)
+ return true
}
- return null
+ return false
}
private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? {
@@ -101,9 +118,13 @@
return null
}
+private fun <T : Any> invokeSerializerOnDefaultCompanion(jClass: Class<*>, vararg args: KSerializer<Any?>): KSerializer<T>? {
+ val companion = jClass.companionOrNull("Companion") ?: return null
+ return invokeSerializerOnCompanion(companion, *args)
+}
+
@Suppress("UNCHECKED_CAST")
-private fun <T : Any> invokeSerializerOnCompanion(jClass: Class<*>, vararg args: KSerializer<Any?>): KSerializer<T>? {
- val companion = jClass.companionOrNull() ?: return null
+private fun <T : Any> invokeSerializerOnCompanion(companion: Any, vararg args: KSerializer<Any?>): KSerializer<T>? {
return try {
val types = if (args.isEmpty()) emptyArray() else Array(args.size) { KSerializer::class.java }
companion.javaClass.getDeclaredMethod("serializer", *types)
@@ -116,9 +137,9 @@
}
}
-private fun Class<*>.companionOrNull() =
+private fun Class<*>.companionOrNull(companionName: String) =
try {
- val companion = getDeclaredField("Companion")
+ val companion = getDeclaredField(companionName)
companion.isAccessible = true
companion.get(null)
} catch (e: Throwable) {
@@ -126,12 +147,15 @@
}
@Suppress("UNCHECKED_CAST")
-private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T>? {
+private fun <T : Any> Class<T>.createEnumSerializer(): KSerializer<T> {
val constants = enumConstants
- return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as? KSerializer<T>
+ return EnumSerializer(canonicalName, constants as Array<out Enum<*>>) as KSerializer<T>
}
private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? {
+ // Special case to avoid IllegalAccessException on Java11+ (#2449)
+ // There are no serializable objects in the stdlib anyway.
+ if (this.canonicalName?.let { it.startsWith("java.") || it.startsWith("kotlin.") } != false) return null
// Check it is an object without using kotlin-reflect
val field =
declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == this && Modifier.isStatic(it.modifiers) }
@@ -146,18 +170,4 @@
return result as? KSerializer<T>
}
-/**
- * Checks if an [this@isInstanceOf] is an instance of a given [kclass].
- *
- * This check is a replacement for [KClass.isInstance] because
- * on JVM it requires kotlin-reflect.jar in classpath
- * (see https://youtrack.jetbrains.com/issue/KT-14720).
- *
- * On JS and Native, this function delegates to aforementioned
- * [KClass.isInstance] since it is supported there out-of-the box;
- * on JVM, it falls back to java.lang.Class.isInstance, which causes
- * difference when applied to function types with big arity.
- */
-internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.javaObjectType.isInstance(this)
-
internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass.java.isArray
diff --git a/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt b/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt
new file mode 100644
index 0000000..7b3cc31
--- /dev/null
+++ b/core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+/**
+ * Suppresses Animal Sniffer plugin errors for certain classes.
+ * Such classes are not available in Android API, but used only for JVM.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS)
+internal annotation class SuppressAnimalSniffer
diff --git a/core/jvmTest/src/kotlinx/serialization/CachingTest.kt b/core/jvmTest/src/kotlinx/serialization/CachingTest.kt
new file mode 100644
index 0000000..b146c92
--- /dev/null
+++ b/core/jvmTest/src/kotlinx/serialization/CachingTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.internal.*
+import kotlinx.serialization.modules.*
+import org.junit.Test
+import kotlin.reflect.*
+import kotlin.test.*
+
+class CachingTest {
+ @Test
+ fun testCache() {
+ var factoryCalled = 0
+
+ val cache = createCache {
+ factoryCalled += 1
+ it.serializerOrNull()
+ }
+
+ repeat(10) {
+ cache.get(typeOf<String>().kclass())
+ }
+
+ assertEquals(1, factoryCalled)
+ }
+
+ @Test
+ fun testParameterizedCache() {
+ var factoryCalled = 0
+
+ val cache = createParametrizedCache { clazz, types ->
+ factoryCalled += 1
+ val serializers = EmptySerializersModule().serializersForParameters(types, true)!!
+ clazz.parametrizedSerializerOrNull(serializers) { types[0].classifier }
+ }
+
+ repeat(10) {
+ cache.get(typeOf<Map<*, *>>().kclass(), listOf(typeOf<String>(), typeOf<String>()))
+ }
+
+ assertEquals(1, factoryCalled)
+ }
+}
diff --git a/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt b/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt
index 31eda2f..332886d 100644
--- a/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt
+++ b/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt
@@ -43,15 +43,17 @@
class Out : AbstractEncoder() {
var step = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
when (step) {
1 -> {
- checkContainerDesc(descriptor); step++; return this
+ checkContainerDesc(descriptor); step++
+ return this
}
4 -> {
- checkDataDesc(descriptor); step++; return this
+ checkDataDesc(descriptor); step++
+ return this
}
}
fail("@$step: beginStructure($descriptor)")
@@ -61,17 +63,20 @@
when (step) {
2 -> {
checkContainerDesc(descriptor); if (index == 0) {
- step++; return true
+ step++
+ return true
}
}
5 -> {
checkDataDesc(descriptor); if (index == 0) {
- step++; return true
+ step++
+ return true
}
}
7 -> {
checkDataDesc(descriptor); if (index == 1) {
- step++; return true
+ step++
+ return true
}
}
}
@@ -80,29 +85,44 @@
override fun <T : Any?> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
when (step) {
- 0, 3 -> { step++; serializer.serialize(this, value); return }
+ 0, 3 -> {
+ step++; serializer.serialize(this, value)
+ return
+ }
}
fail("@$step: encodeSerializableValue($value)")
}
override fun encodeString(value: String) {
when (step) {
- 6 -> if (value == "s1") { step++; return }
+ 6 -> if (value == "s1") {
+ step++
+ return
+ }
}
fail("@$step: encodeString($value)")
}
override fun encodeInt(value: Int) {
when (step) {
- 8 -> if (value == 42) { step++; return }
+ 8 -> if (value == 42) {
+ step++
+ return
+ }
}
fail("@$step: decodeInt($value)")
}
override fun endStructure(descriptor: SerialDescriptor) {
- when(step) {
- 9 -> { checkDataDesc(descriptor); step++; return }
- 10 -> { checkContainerDesc(descriptor); step++; return }
+ when (step) {
+ 9 -> {
+ checkDataDesc(descriptor); step++
+ return
+ }
+ 10 -> {
+ checkContainerDesc(descriptor); step++
+ return
+ }
}
fail("@$step: endStructure($descriptor)")
}
@@ -115,15 +135,17 @@
class Inp : AbstractDecoder() {
var step = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
when (step) {
1 -> {
- checkContainerDesc(descriptor); step++; return this
+ checkContainerDesc(descriptor); step++
+ return this
}
4 -> {
- checkDataDesc(descriptor); step++; return this
+ checkDataDesc(descriptor); step++
+ return this
}
}
fail("@$step: beginStructure($descriptor)")
@@ -132,19 +154,24 @@
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
when (step) {
2 -> {
- checkContainerDesc(descriptor); step++; return 0
+ checkContainerDesc(descriptor); step++
+ return 0
}
5 -> {
- checkDataDesc(descriptor); step++; return 0
+ checkDataDesc(descriptor); step++
+ return 0
}
7 -> {
- checkDataDesc(descriptor); step++; return 1
+ checkDataDesc(descriptor); step++
+ return 1
}
9 -> {
- checkDataDesc(descriptor); step++; return -1
+ checkDataDesc(descriptor); step++
+ return -1
}
11 -> {
- checkContainerDesc(descriptor); step++; return -1
+ checkContainerDesc(descriptor); step++
+ return -1
}
}
fail("@$step: decodeElementIndex($descriptor)")
@@ -152,29 +179,44 @@
override fun <T : Any?> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
when (step) {
- 0, 3 -> { step++; return deserializer.deserialize(this) }
+ 0, 3 -> {
+ step++
+ return deserializer.deserialize(this)
+ }
}
fail("@$step: decodeSerializableValue()")
}
override fun decodeString(): String {
when (step) {
- 6 -> { step++; return "s1" }
+ 6 -> {
+ step++
+ return "s1"
+ }
}
fail("@$step: decodeString()")
}
override fun decodeInt(): Int {
when (step) {
- 8 -> { step++; return 42 }
+ 8 -> {
+ step++
+ return 42
+ }
}
fail("@$step: decodeInt()")
}
override fun endStructure(descriptor: SerialDescriptor) {
- when(step) {
- 10 -> { checkDataDesc(descriptor); step++; return }
- 12 -> { checkContainerDesc(descriptor); step++; return }
+ when (step) {
+ 10 -> {
+ checkDataDesc(descriptor); step++
+ return
+ }
+ 12 -> {
+ checkContainerDesc(descriptor); step++
+ return
+ }
}
fail("@$step: endStructure($descriptor)")
}
diff --git a/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt b/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt
index 356e9bd..7e92041 100644
--- a/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt
+++ b/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt
@@ -198,7 +198,7 @@
class Out(private val name: String) : AbstractEncoder() {
var step = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(
descriptor: SerialDescriptor
@@ -212,10 +212,12 @@
checkDesc(name, descriptor)
when (step) {
1 -> if (index == 0) {
- step++; return true
+ step++
+ return true
}
3 -> if (index == 1) {
- step++; return true
+ step++
+ return true
}
}
fail("@$step: encodeElement($descriptor, $index)")
@@ -224,7 +226,8 @@
override fun encodeString(value: String) {
when (step) {
2 -> if (value == "s1") {
- step++; return
+ step++
+ return
}
}
fail("@$step: encodeString($value)")
@@ -232,7 +235,10 @@
override fun encodeInt(value: Int) {
when (step) {
- 4 -> if (value == 42) { step++; return }
+ 4 -> if (value == 42) {
+ step++
+ return
+ }
}
fail("@$step: decodeInt($value)")
}
@@ -250,7 +256,7 @@
class Inp(private val name: String) : AbstractDecoder() {
var step = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
checkDesc(name, descriptor)
@@ -262,13 +268,16 @@
checkDesc(name, descriptor)
when (step) {
1 -> {
- step++; return 0
+ step++
+ return 0
}
3 -> {
- step++; return 1
+ step++
+ return 1
}
5 -> {
- step++; return -1
+ step++
+ return -1
}
}
fail("@$step: decodeElementIndex($descriptor)")
@@ -276,14 +285,20 @@
override fun decodeString(): String {
when (step) {
- 2 -> { step++; return "s1" }
+ 2 -> {
+ step++
+ return "s1"
+ }
}
fail("@$step: decodeString()")
}
override fun decodeInt(): Int {
when (step) {
- 4 -> { step++; return 42 }
+ 4 -> {
+ step++
+ return 42
+ }
}
fail("@$step: decodeInt()")
}
diff --git a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt
index e24c182..2c91769 100644
--- a/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/nativeMain/src/kotlinx/serialization/internal/Platform.kt
@@ -19,9 +19,9 @@
internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing {
throw SerializationException(
- "Serializer for class '${simpleName}' is not found.\n" +
- "Mark the class as @Serializable or provide the serializer explicitly.\n" +
- "On Kotlin/Native explicitly declared serializer should be used for interfaces and enums without @Serializable annotation"
+ notRegisteredMessage() +
+ "To get enum serializer on Kotlin/Native, it should be annotated with @Serializable annotation.\n" +
+ "To get interface serializer on Kotlin/Native, use PolymorphicSerializer() constructor function.\n"
)
}
@@ -37,25 +37,36 @@
else -> null
}
-@Suppress(
- "UNCHECKED_CAST",
- "DEPRECATION_ERROR"
-)
-@OptIn(ExperimentalAssociatedObjects::class)
+@Suppress("DEPRECATION_ERROR")
internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
this.constructSerializerForGivenTypeArgs()
+
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return object: SerializerCache<T> {
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return factory(key)
+ }
+ }
+}
+
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return object: ParametrizedSerializerCache<T> {
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return kotlin.runCatching { factory(key, types) }
+ }
+ }
+}
+
internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> {
val result = arrayOfAnyNulls<E>(size)
var index = 0
for (element in this) result[index++] = element
- @Suppress("UNCHECKED_CAST", "USELESS_CAST")
+ @Suppress("USELESS_CAST")
return result as Array<E>
}
@Suppress("UNCHECKED_CAST")
private fun <T> arrayOfAnyNulls(size: Int): Array<T> = arrayOfNulls<Any>(size) as Array<T>
-internal actual fun Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this)
-
internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass == Array::class
diff --git a/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index 9e51d7f..2691ce0 100644
--- a/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -5,7 +5,5 @@
package kotlinx.serialization.test
import kotlinx.serialization.test.Platform
-import kotlin.native.concurrent.SharedImmutable
-@SharedImmutable
public actual val currentPlatform: Platform = Platform.NATIVE
diff --git a/core/wasmMain/src/kotlinx/serialization/Serializers.kt b/core/wasmMain/src/kotlinx/serialization/Serializers.kt
new file mode 100644
index 0000000..a499392
--- /dev/null
+++ b/core/wasmMain/src/kotlinx/serialization/Serializers.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlin.reflect.*
+
+@OptIn(ExperimentalAssociatedObjects::class)
+@AssociatedObjectKey
+@Retention(AnnotationRetention.BINARY)
+@PublishedApi
+internal annotation class SerializableWith(public val serializer: KClass<out KSerializer<*>>)
\ No newline at end of file
diff --git a/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt
new file mode 100644
index 0000000..310df02
--- /dev/null
+++ b/core/wasmMain/src/kotlinx/serialization/internal/Platform.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.internal
+
+import kotlinx.serialization.*
+import kotlin.reflect.*
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun <T> Array<T>.getChecked(index: Int): T {
+ return get(index)
+}
+
+@Suppress("NOTHING_TO_INLINE")
+internal actual inline fun BooleanArray.getChecked(index: Int): Boolean {
+ return get(index)
+}
+
+internal actual fun KClass<*>.platformSpecificSerializerNotRegistered(): Nothing {
+ throw SerializationException(
+ "${notRegisteredMessage()}\n" +
+ "On Kotlin/Wasm explicitly declared serializer should be used for interfaces and enums without @Serializable annotation"
+ )
+}
+
+@Suppress(
+ "UNCHECKED_CAST",
+ "DEPRECATION_ERROR"
+)
+@OptIn(ExperimentalAssociatedObjects::class)
+internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? =
+ when (val assocObject = findAssociatedObject<SerializableWith>()) {
+ is KSerializer<*> -> assocObject as KSerializer<T>
+ is SerializerFactory -> assocObject.serializer(*args) as KSerializer<T>
+ else -> null
+ }
+
+@Suppress("DEPRECATION_ERROR")
+internal actual fun <T : Any> KClass<T>.compiledSerializerImpl(): KSerializer<T>? =
+ this.constructSerializerForGivenTypeArgs()
+
+
+internal actual fun <T> createCache(factory: (KClass<*>) -> KSerializer<T>?): SerializerCache<T> {
+ return object: SerializerCache<T> {
+ override fun get(key: KClass<Any>): KSerializer<T>? {
+ return factory(key)
+ }
+ }
+}
+
+internal actual fun <T> createParametrizedCache(factory: (KClass<Any>, List<KType>) -> KSerializer<T>?): ParametrizedSerializerCache<T> {
+ return object: ParametrizedSerializerCache<T> {
+ override fun get(key: KClass<Any>, types: List<KType>): Result<KSerializer<T>?> {
+ return kotlin.runCatching { factory(key, types) }
+ }
+ }
+}
+
+internal actual fun <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray()
+
+internal actual fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass == Array::class
\ No newline at end of file
diff --git a/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..fd359b7
--- /dev/null
+++ b/core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file
diff --git a/docs/basic-serialization.md b/docs/basic-serialization.md
index ce959f8..96e7098 100644
--- a/docs/basic-serialization.md
+++ b/docs/basic-serialization.md
@@ -79,7 +79,7 @@
```text
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.
-Mark the class as @Serializable or provide the serializer explicitly.
+Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
```
<!--- TEST LINES_START -->
@@ -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 -->
@@ -687,14 +687,14 @@
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-[kotlinx.serialization.encodeToString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html
-[Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
-[kotlinx.serialization.decodeFromString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html
-[Required]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/index.html
-[Transient]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html
-[EncodeDefault]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/index.html
-[EncodeDefault.Mode]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/index.html
-[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
+[kotlinx.serialization.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/encode-to-string.html
+[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
+[kotlinx.serialization.decodeFromString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/decode-from-string.html
+[Required]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-required/index.html
+[Transient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html
+[EncodeDefault]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/index.html
+[EncodeDefault.Mode]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/index.html
+[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
<!--- MODULE /kotlinx-serialization-json -->
<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
diff --git a/docs/building.md b/docs/building.md
index 533cdcc..e9d00a0 100644
--- a/docs/building.md
+++ b/docs/building.md
@@ -2,15 +2,22 @@
## JDK version
-To build Kotlin Serialization JDK version 9 or higher is required.
+To build Kotlin Serialization JDK version 11 or higher is required. Make sure this is your default JDK (`JAVA_HOME` is set accordingly).
This is needed to compile the `module-info` file included for JPMS support.
+In case you are determined to use different JDK version, or experience problems with JPMS you can turn off compilation of modules
+completely with `disableJPMS` property: add `disableJPMS=true` to gradle.properties or `-PdisableJPMS` to Gradle CLI invocation.
+
## Runtime library
Kotlin Serialization runtime library itself is a [multiplatform](http://kotlinlang.org/docs/reference/multiplatform.html) project.
-To build library from the source and run all tests, use `./gradlew build`. Corresponding platform tasks like `jvmTest`, `jsTest` and so on are also available.
+To build library from the source and run all tests, use `./gradlew build`. Corresponding platform tasks like `jvmTest`, `jsTest`, `nativeTest` and so on are also available.
-To install it into the local Maven repository, run `./gradlew publishToMavenLocal`.
+Project can be opened in in Intellij IDEA without additional prerequisites.
+In case you want to work with Protobuf tests, you may need to run `./gradlew generateTestProto` beforehand.
+
+
+To install runtime library into the local Maven repository, run `./gradlew publishToMavenLocal`.
After that, you can include this library in arbitrary projects like usual gradle dependency:
```gradle
@@ -23,17 +30,19 @@
}
```
-To open project in Intellij IDEA, first run `./gradlew generateTestProto` from console.
-Make sure you've set an option 'Use Gradle wrapper' on import to use a correct version of Gradle.
-You may also need to mark `runtime/build/generated/source/proto/test/java` as 'Generated source root' to build project/run tests in IDE without delegating a build to Gradle.
-This requires Kotlin 1.3.11 and higher.
+Note that by default, only one Native target is built (the one that is the current host, e.g. `macosX64` on Intel Mac machines, `linuxX64` on linux machines, etc).
+To compile and publish all Native artifacts, not only the host one, use Gradle property `native.deploy=true`.
-To use snapshot version of compiler (if you have built it from sources), use flag `-Pbootstrap`. To compile and publish all Native artifacts, not only the host one, use `-Pnative.deploy=true`.
+To use snapshot version of compiler (if you have built and install it from sources), use flag `-Pbootstrap`.
+If you have built both Kotlin and Kotlin/Native compilers, set `KONAN_LOCAL_DIST` environment property to the path with Kotlin/Native distribution
+(usually `kotlin-native/dist` folder inside Kotlin project).
-`master` branch of library should be binary compatible with latest released compiler plugin. In case you want to test some new features from other branches, which are still in development and may not be compatible in terms of bytecode produced by plugin, you'll need to build the plugin by yourself.
+`master` and `dev` branches of library should be binary compatible with latest released compiler plugin. In case you want to test some new features from other branches,
+which are still in development and may not be compatible in terms of bytecode produced by plugin, you'll need to build the plugin by yourself.
## Compiler plugin
Compiler plugin for Gradle/Maven and IntelliJ plugin, starting from Kotlin 1.3, are embedded into the Kotlin compiler.
-Sources and steps to build it are located [here](https://github.com/JetBrains/kotlin/blob/master/plugins/kotlin-serialization/kotlin-serialization-compiler/). In general, you'll just need to run `./gradlew dist install` to get `1.x.255` versions of Kotlin compiler, stdlib and serialization plugins in the Maven local repository.
+Sources and steps to build it are located [here](https://github.com/JetBrains/kotlin/tree/master/plugins/kotlinx-serialization).
+In short, you'll just need to run `./gradlew dist install` to get `1.x.255-SNAPSHOT` versions of Kotlin compiler, stdlib and serialization plugins in the Maven local repository.
diff --git a/docs/builtin-classes.md b/docs/builtin-classes.md
index 0183115..8671dc7 100644
--- a/docs/builtin-classes.md
+++ b/docs/builtin-classes.md
@@ -23,6 +23,8 @@
* [Deserializing collections](#deserializing-collections)
* [Maps](#maps)
* [Unit and singleton objects](#unit-and-singleton-objects)
+ * [Duration](#duration)
+* [Nothing](#nothing)
<!--- END -->
@@ -68,8 +70,6 @@
<!--- TEST -->
-> Experimental unsigned numbers as well as other experimental inline classes are not supported by Kotlin Serialization yet.
-
### Long numbers
@@ -166,6 +166,8 @@
{"name":"kotlinx.serialization","status":"SUPPORTED"}
```
+> Note: On Kotlin/JS and Kotlin/Native, `@Serializable` annotation is needed for enum class if you want to use it as a root object — i.e. use `encodeToString<Status>(Status.SUPPORTED)`.
+
<!--- TEST -->
### Serial names of enum entries
@@ -382,6 +384,57 @@
> Serialization of objects is format specific. Other formats may represent objects differently,
> e.g. using their fully-qualified names.
+
+### Duration
+
+Since Kotlin `1.7.20` the [Duration] class has become serializable.
+
+<!--- INCLUDE
+import kotlin.time.*
+-->
+
+```kotlin
+fun main() {
+ val duration = 1000.toDuration(DurationUnit.SECONDS)
+ println(Json.encodeToString(duration))
+}
+```
+> You can get the full code [here](../guide/example/example-builtin-12.kt).
+
+Duration is serialized as a string in the ISO-8601-2 format.
+```text
+"PT16M40S"
+```
+
+<!--- TEST -->
+
+
+## Nothing
+
+By default, [Nothing] is a serializable class. However, since there are no instances of this class, it is impossible to encode or decode its values - any attempt will cause an exception.
+
+This serializer is used when syntactically some type is needed, but it is not actually used in serialization. For example, when using parameterized polymorphic base classes:
+```kotlin
+@Serializable
+sealed class ParametrizedParent<out R> {
+ @Serializable
+ data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
+}
+
+fun main() {
+ println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
+}
+```
+> You can get the full code [here](../guide/example/example-builtin-13.kt).
+
+When encoding, the serializer for the `Nothing` was not used
+
+```text
+{"value":42}
+```
+
+<!--- TEST -->
+
---
The next chapter covers [Serializers](serializers.md).
@@ -394,17 +447,18 @@
[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/
[Set]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-set/
[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/
+[Duration]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.time/-duration/
+[Nothing]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-nothing.html
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-[Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
-[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
+[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
+[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.builtins -->
-[LongAsStringSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-long-as-string-serializer/index.html
+[LongAsStringSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-long-as-string-serializer/index.html
<!--- END -->
-
diff --git a/docs/formats.md b/docs/formats.md
index 790ce5f..3fcbf9c 100644
--- a/docs/formats.md
+++ b/docs/formats.md
@@ -557,7 +557,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -624,7 +624,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -655,7 +655,7 @@
class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
@@ -732,7 +732,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -752,7 +752,7 @@
class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
@@ -819,7 +819,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -852,7 +852,7 @@
class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
@@ -924,7 +924,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -957,7 +957,7 @@
class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
@@ -1039,7 +1039,7 @@
```kotlin
class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
override fun encodeShort(value: Short) = output.writeShort(value.toInt())
@@ -1076,7 +1076,7 @@
```kotlin
class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = input.readShort()
@@ -1191,7 +1191,7 @@
<!--- INCLUDE
class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
override fun encodeShort(value: Short) = output.writeShort(value.toInt())
@@ -1247,7 +1247,7 @@
class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = input.readShort()
@@ -1360,61 +1360,61 @@
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-[serializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/serializer.html
-[KSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
+[serializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/serializer.html
+[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.builtins -->
-[kotlinx.serialization.builtins.ByteArraySerializer()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-byte-array-serializer.html
+[kotlinx.serialization.builtins.ByteArraySerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-byte-array-serializer.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding -->
-[Encoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
-[Decoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
-[AbstractEncoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/index.html
-[AbstractDecoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/index.html
-[AbstractEncoder.encodeValue]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/encode-value.html
-[AbstractDecoder.decodeValue]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/decode-value.html
-[CompositeDecoder.decodeElementIndex]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html
-[Decoder.beginStructure]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/begin-structure.html
-[CompositeDecoder.decodeSequentially]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html
-[Encoder.beginCollection]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/begin-collection.html
-[CompositeDecoder.decodeCollectionSize]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-collection-size.html
-[Encoder.encodeNull]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-null.html
-[Encoder.encodeNotNullMark]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-not-null-mark.html
-[Decoder.decodeNotNullMark]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-not-null-mark.html
-[Encoder.encodeSerializableValue]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html
-[Decoder.decodeSerializableValue]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html
+[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
+[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
+[AbstractEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/index.html
+[AbstractDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/index.html
+[AbstractEncoder.encodeValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-encoder/encode-value.html
+[AbstractDecoder.decodeValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-abstract-decoder/decode-value.html
+[CompositeDecoder.decodeElementIndex]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html
+[Decoder.beginStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/begin-structure.html
+[CompositeDecoder.decodeSequentially]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html
+[Encoder.beginCollection]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/begin-collection.html
+[CompositeDecoder.decodeCollectionSize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-collection-size.html
+[Encoder.encodeNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-null.html
+[Encoder.encodeNotNullMark]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-not-null-mark.html
+[Decoder.decodeNotNullMark]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-not-null-mark.html
+[Encoder.encodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html
+[Decoder.decodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html
<!--- MODULE /kotlinx-serialization-properties -->
<!--- INDEX kotlinx-serialization-properties/kotlinx.serialization.properties -->
-[kotlinx.serialization.properties.Properties]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-properties/kotlinx.serialization.properties/-properties/index.html
+[kotlinx.serialization.properties.Properties]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-properties/kotlinx.serialization.properties/-properties/index.html
<!--- MODULE /kotlinx-serialization-protobuf -->
<!--- INDEX kotlinx-serialization-protobuf/kotlinx.serialization.protobuf -->
-[ProtoBuf]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/index.html
-[ProtoBuf.encodeToByteArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/encode-to-byte-array.html
-[ProtoBuf.decodeFromByteArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/decode-from-byte-array.html
-[ProtoNumber]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-number/index.html
-[ProtoType]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-type/index.html
-[ProtoIntegerType]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/index.html
-[ProtoIntegerType.DEFAULT]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-d-e-f-a-u-l-t/index.html
-[ProtoIntegerType.SIGNED]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-s-i-g-n-e-d/index.html
-[ProtoIntegerType.FIXED]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/index.html
+[ProtoBuf]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/index.html
+[ProtoBuf.encodeToByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/encode-to-byte-array.html
+[ProtoBuf.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-buf/decode-from-byte-array.html
+[ProtoNumber]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-number/index.html
+[ProtoType]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-type/index.html
+[ProtoIntegerType]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/index.html
+[ProtoIntegerType.DEFAULT]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-d-e-f-a-u-l-t/index.html
+[ProtoIntegerType.SIGNED]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-s-i-g-n-e-d/index.html
+[ProtoIntegerType.FIXED]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf/-proto-integer-type/-f-i-x-e-d/index.html
<!--- INDEX kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema -->
-[ProtoBufSchemaGenerator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema/-proto-buf-schema-generator/index.html
+[ProtoBufSchemaGenerator]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-protobuf/kotlinx.serialization.protobuf.schema/-proto-buf-schema-generator/index.html
<!--- MODULE /kotlinx-serialization-cbor -->
<!--- INDEX kotlinx-serialization-cbor/kotlinx.serialization.cbor -->
-[Cbor]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/index.html
-[Cbor.encodeToByteArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/encode-to-byte-array.html
-[Cbor.decodeFromByteArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/decode-from-byte-array.html
-[CborBuilder.ignoreUnknownKeys]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html
-[ByteString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/index.html
+[Cbor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/index.html
+[Cbor.encodeToByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/encode-to-byte-array.html
+[Cbor.decodeFromByteArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor/decode-from-byte-array.html
+[CborBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-cbor-builder/ignore-unknown-keys.html
+[ByteString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-cbor/kotlinx.serialization.cbor/-byte-string/index.html
<!--- END -->
diff --git a/docs/inline-classes.md b/docs/inline-classes.md
index a28f51c..fda8142 100644
--- a/docs/inline-classes.md
+++ b/docs/inline-classes.md
@@ -1,205 +1 @@
-# Serialization and inline classes (experimental, IR-specific)
-
-This appendix describes how inline classes are handled by kotlinx.serialization.
-
-> Features described in this document are currently [experimental](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/compatibility.md#experimental-api)
-> and are available only with IR compilers. Native targets use IR compiler by default;
-> see documentation for [JS](https://kotlinlang.org/docs/reference/js-ir-compiler.html) and [JVM](https://kotlinlang.org/docs/reference/whatsnew14.html#new-jvm-ir-backend) to learn how to enable IR compilers.
-> Inline classes themselves are an [Alpha](https://kotlinlang.org/docs/reference/inline-classes.html#alpha-status-of-inline-classes) Kotlin feature.
-
-**Table of contents**
-
-<!--- TOC -->
-
-* [Serializable inline classes](#serializable-inline-classes)
-* [Unsigned types support (JSON only)](#unsigned-types-support-json-only)
-* [Using inline classes in your custom serializers](#using-inline-classes-in-your-custom-serializers)
-
-<!--- END -->
-
-## Serializable inline classes
-
-We can mark inline class as serializable:
-
-```kotlin
-@Serializable
-inline class Color(val rgb: Int)
-```
-
-Inline class in Kotlin is stored as its underlying type when possible (i.e. no boxing is required).
-Serialization framework makes does not impose any additional restriction and uses the underlying type where possible as well.
-
-```kotlin
-@Serializable
-data class NamedColor(val color: Color, val name: String)
-
-fun main() {
- println(Json.encodeToString(NamedColor(Color(0), "black")))
-}
-```
-
-In this example, `NamedColor` is serialized as two primitives: `color: Int` and `name: String` without an allocation
-of `Color` class. When we run the example, encoding data with JSON format, we get the following
-output:
-
-```text
-{"color": 0, "name": "black"}
-```
-
-As we see, `Color` class is not included during the encoding, only its underlying data. This invariant holds even if the actual inline class
-is [allocated](https://kotlinlang.org/docs/reference/inline-classes.html#representation) — for example, when inline
-class is used as a generic type argument:
-
-```kotlin
-@Serializable
-class Palette(val colors: List<Color>)
-
-fun main() {
- println(Json.encodeToString(Palette(listOf(Color(0), Color(255), Color(128)))))
-}
-```
-
-The snippet produces the following output:
-
-```text
-{"colors":[0, 255, 128]}
-```
-
-## Unsigned types support (JSON only)
-
-Kotlin standard library provides ready-to-use unsigned arithmetics, leveraging inline classes
-to represent unsigned types: `UByte`, `UShort`, `UInt` and `ULong`.
-[Json] format has built-in support for them: these types are serialized as theirs string
-representations in unsigned form.
-These types are handled as regular serializable types by the compiler plugin and can be freely used in serializable classes:
-
-```kotlin
-@Serializable
-class Counter(val counted: UByte, val description: String)
-
-fun main() {
- val counted = 239.toUByte()
- println(Json.encodeToString(Counter(counted, "tries")))
-}
-```
-
-The output is following:
-
-```text
-{"counted":239,"description":"tries"}
-```
-
-> Unsigned types are currently unsupported in Protobuf and CBOR, but we plan to add them later.
-
-## Using inline classes in your custom serializers
-
-Let's return to our `NamedColor` example and try to write a custom serializer for it. Normally, as shown
-in [Hand-written composite serializer](serializers.md#hand-written-composite-serializer), we would write the following code
-in `serialize` method:
-
-```kotlin
-override fun serialize(encoder: Encoder, value: NamedColor) {
- encoder.beginStructure(descriptor) {
- encodeSerializableElement(descriptor, 0, Color.serializer(), value.color)
- encodeStringElement(descriptor, 1, value.name)
- }
-}
-```
-
-However, since `Color` is used as a type argument in [encodeSerializableElement][CompositeEncoder.encodeSerializableElement] function, `value.color` will be boxed
-to `Color` wrapper before passing it to the function, preventing the inline class optimization. To avoid this, we can use
-special [encodeInlineElement][CompositeEncoder.encodeInlineElement] function instead. It uses [serial descriptor][SerialDescriptor] of `Color` ([retrieved][SerialDescriptor.getElementDescriptor] from serial descriptor of `NamedColor`) instead of [KSerializer],
-does not have type parameters and does not accept any values. Instead, it returns [Encoder]. Using it, we can encode
-unboxed value:
-
-```kotlin
-override fun serialize(encoder: Encoder, value: NamedColor) {
- encoder.beginStructure(descriptor) {
- encodeInlineElement(descriptor, 0).encodeInt(value.color)
- encodeStringElement(descriptor, 1, value.name)
- }
-}
-```
-
-The same principle goes also with [CompositeDecoder]: it has [decodeInlineElement][CompositeDecoder.decodeInlineElement] function that returns [Decoder].
-
-If your class should be represented as a primitive (as shown in [Primitive serializer](serializers.md#primitive-serializer) section),
-and you cannot use [beginStructure][Encoder.beginStructure] function, there is a complementary function in [Encoder] called [encodeInline][Encoder.encodeInline].
-We will use it to show an example how one can represent a class as an unsigned integer.
-
-Let's start with a UID class:
-
-```kotlin
-@Serializable(UIDSerializer::class)
-class UID(val uid: Int)
-```
-
-`uid` type is `Int`, but suppose we want it to be an unsigned integer in JSON. We can start writing the
-following custom serializer:
-
-```kotlin
-object UIDSerializer: KSerializer<UID> {
- override val descriptor = UInt.serializer().descriptor
-}
-```
-
-Note that we are using here descriptor from `UInt.serializer()` — it means that the class' representation looks like a
-UInt's one.
-
-Then the `serialize` method:
-
-```kotlin
-override fun serialize(encoder: Encoder, value: UID) {
- encoder.encodeInline(descriptor).encodeInt(value.uid)
-}
-```
-
-That's where the magic happens — despite we called a regular [encodeInt][Encoder.encodeInt] with a `uid: Int` argument, the output will contain
-an unsigned int because of the special encoder from `encodeInline` function. Since JSON format supports unsigned integers, it
-recognizes theirs descriptors when they're passed into `encodeInline` and handles consecutive calls as for unsigned integers.
-
-The `deserialize` method looks symmetrically:
-
-```kotlin
-override fun deserialize(decoder: Decoder): UID {
- return UID(decoder.decodeInline(descriptor).decodeInt())
-}
-```
-
-> Disclaimer: You can also write such a serializer for inline class itself (imagine UID being the inline class — there's no need to change anything in the serializer).
-> However, do not use anything in custom serializers for inline classes besides `encodeInline`. As we discussed, calls to inline class serializer may be
-> optimized and replaced with a `encodeInlineElement` calls.
-> `encodeInline` and `encodeInlineElement` calls with the same descriptor are considered equivalent and can be replaced with each other — formats should return the same `Encoder`.
-> If you embed custom logic in custom inline class serializer, you may get different results depending on whether this serializer was called at all
-> (and this, in turn, depends on whether inline class was boxed or not).
-
----
-
-<!--- MODULE /kotlinx-serialization-core -->
-<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-
-[KSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
-
-<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding -->
-
-[CompositeEncoder.encodeSerializableElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-serializable-element.html
-[CompositeEncoder.encodeInlineElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-inline-element.html
-[Encoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
-[CompositeDecoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html
-[CompositeDecoder.decodeInlineElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-inline-element.html
-[Decoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
-[Encoder.beginStructure]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/begin-structure.html
-[Encoder.encodeInline]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-inline.html
-[Encoder.encodeInt]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-int.html
-
-<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.descriptors -->
-
-[SerialDescriptor]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html
-[SerialDescriptor.getElementDescriptor]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/get-element-descriptor.html
-
-<!--- MODULE /kotlinx-serialization-json -->
-<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
-
-[Json]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
-
-<!--- END -->
+The documentation has been moved to the [value-classes.md](value-classes.md) page.
diff --git a/docs/json.md b/docs/json.md
index 606d4ac..c637eb1 100644
--- a/docs/json.md
+++ b/docs/json.md
@@ -20,11 +20,17 @@
* [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)
* [Parsing to Json element](#parsing-to-json-element)
* [Types of Json elements](#types-of-json-elements)
* [Json element builders](#json-element-builders)
* [Decoding Json elements](#decoding-json-elements)
+ * [Encoding literal Json content (experimental)](#encoding-literal-json-content-experimental)
+ * [Serializing large decimal numbers](#serializing-large-decimal-numbers)
+ * [Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden](#using-jsonunquotedliteral-to-create-a-literal-unquoted-value-of-null-is-forbidden)
* [Json transformations](#json-transformations)
* [Array wrapping](#array-wrapping)
* [Array unwrapping](#array-unwrapping)
@@ -89,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
@@ -236,7 +242,7 @@
### Encoding defaults
Default values of properties are not encoded by default because they will be assigned to missing fields during decoding anyway.
-See the [Defaults are not encoded](basic-serialization.md#defaults-are-not-encoded) section for details and an example.
+See the [Defaults are not encoded](basic-serialization.md#defaults-are-not-encoded-by-default) section for details and an example.
This is especially useful for nullable properties with null defaults and avoids writing the corresponding null values.
The default behavior can be changed by setting the [encodeDefaults][JsonBuilder.encodeDefaults] property to `true`:
@@ -465,6 +471,125 @@
<!--- 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
+using either uppercase underscore-separated names or upper camel case names.
+[Json] uses exact Kotlin enum values names for decoding by default.
+However, sometimes third-party JSONs have such values named in lowercase or some mixed case.
+In this case, it is possible to decode enum values in a case-insensitive manner using [JsonBuilder.decodeEnumsCaseInsensitive] property:
+
+```kotlin
+val format = Json { decodeEnumsCaseInsensitive = true }
+
+enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B }
+
+@Serializable
+data class CasesList(val cases: List<Cases>)
+
+fun main() {
+ println(format.decodeFromString<CasesList>("""{"cases":["value_A", "alternative"]}"""))
+}
+```
+
+> 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:
+
+```text
+CasesList(cases=[VALUE_A, VALUE_B])
+```
+
+This property does not affect encoding in any way.
+
+<!--- TEST -->
+
+### Global naming strategy
+
+If properties' names in Json input are different from Kotlin ones, it is recommended to specify the name
+for each property explicitly using [`@SerialName` annotation](basic-serialization.md#serial-field-names).
+However, there are certain situations where transformation should be applied to every serial name — such as migration
+from other frameworks or legacy codebase. For that cases, it is possible to specify a [namingStrategy][JsonBuilder.namingStrategy]
+for a [Json] instance. `kotlinx.serialization` provides one strategy implementation out of the box, the [JsonNamingStrategy.SnakeCase](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-naming-strategy/-builtins/-snake-case.html):
+
+```kotlin
+@Serializable
+data class Project(val projectName: String, val projectOwner: String)
+
+val format = Json { namingStrategy = JsonNamingStrategy.SnakeCase }
+
+fun main() {
+ val project = format.decodeFromString<Project>("""{"project_name":"kotlinx.coroutines", "project_owner":"Kotlin"}""")
+ println(format.encodeToString(project.copy(projectName = "kotlinx.serialization")))
+}
+```
+
+> 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:
+
+```text
+{"project_name":"kotlinx.serialization","project_owner":"Kotlin"}
+```
+
+There are some caveats one should remember while dealing with a [JsonNamingStrategy]:
+
+* Due to the nature of the `kotlinx.serialization` framework, naming strategy transformation is applied to all properties regardless
+of whether their serial name was taken from the property name or provided by [SerialName] annotation.
+Effectively, it means one cannot avoid transformation by explicitly specifying the serial name. To be able to deserialize
+non-transformed names, [JsonNames] annotation can be used instead.
+
+* Collision of the transformed name with any other (transformed) properties serial names or any alternative names
+specified with [JsonNames] will lead to a deserialization exception.
+
+* Global naming strategies are very implicit: by looking only at the definition of the class,
+it is impossible to determine which names it will have in the serialized form.
+As a consequence, naming strategies are not friendly to actions like Find Usages/Rename in IDE, full-text search by grep, etc.
+For them, the original name and the transformed are two different things;
+changing one without the other may introduce bugs in many unexpected ways and lead to greater maintenance efforts for code with global naming strategies.
+
+Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application.
+
+<!--- TEST -->
## Json elements
@@ -490,7 +615,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-15.kt).
A `JsonElement` prints itself as a valid JSON:
@@ -533,7 +658,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-16.kt).
The above example sums `votes` in all objects in the `forks` array, ignoring the objects that have no `votes`:
@@ -548,7 +673,7 @@
### Json element builders
You can construct instances of specific [JsonElement] subtypes using the respective builder functions
-[buildJsonArray] and [buildJsonObject]. They provide a DSL to define the resulting JSON structure. It is
+[buildJsonArray] and [buildJsonObject]. They provide a DSL to define the resulting JSON structure. It
is similar to Kotlin standard library collection builders, but with a JSON-specific convenience
of more type-specific overloads and inner builder functions. The following example shows
all the key features:
@@ -573,7 +698,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-17.kt).
As a result, you get a proper JSON string:
@@ -602,7 +727,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-18.kt).
The result is exactly what you would expect:
@@ -612,6 +737,153 @@
<!--- TEST -->
+### Encoding literal Json content (experimental)
+
+> This functionality is experimental and requires opting-in to [the experimental Kotlinx Serialization API](compatibility.md#experimental-api).
+
+In some cases it might be necessary to encode an arbitrary unquoted value.
+This can be achieved with [JsonUnquotedLiteral].
+
+#### Serializing large decimal numbers
+
+The JSON specification does not restrict the size or precision of numbers, however it is not possible to serialize
+numbers of arbitrary size or precision using [JsonPrimitive()].
+
+If [Double] is used, then the numbers are limited in precision, meaning that large numbers are truncated.
+When using Kotlin/JVM [BigDecimal] can be used instead, but [JsonPrimitive()] will encode the value as a string, not a
+number.
+
+```kotlin
+import java.math.BigDecimal
+
+val format = Json { prettyPrint = true }
+
+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)
+ }
+
+ println(format.encodeToString(piObject))
+}
+```
+
+> 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.
+
+```text
+{
+ "pi_double": 3.141592653589793,
+ "pi_string": "3.141592653589793238462643383279"
+}
+```
+
+<!--- TEST -->
+
+To avoid precision loss, the string value of `pi` can be encoded using [JsonUnquotedLiteral].
+
+```kotlin
+import java.math.BigDecimal
+
+val format = Json { prettyPrint = true }
+
+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)
+ }
+
+ println(format.encodeToString(piObject))
+}
+```
+
+> You can get the full code [here](../guide/example/example-json-20.kt).
+
+`pi_literal` now accurately matches the value defined.
+
+```text
+{
+ "pi_literal": 3.141592653589793238462643383279,
+ "pi_double": 3.141592653589793,
+ "pi_string": "3.141592653589793238462643383279"
+}
+```
+
+<!--- TEST -->
+
+To decode `pi` back to a [BigDecimal], the string content of the [JsonPrimitive] can be used.
+
+(This demonstration uses a [JsonPrimitive] for simplicity. For a more re-usable method of handling serialization, see
+[Json Transformations](#json-transformations) below.)
+
+
+```kotlin
+import java.math.BigDecimal
+
+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)
+}
+```
+
+> 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.
+
+```text
+3.141592653589793238462643383279
+```
+
+<!--- TEST -->
+
+#### Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden
+
+To avoid creating an inconsistent state, encoding a String equal to `"null"` is forbidden.
+Use [JsonNull] or [JsonPrimitive] instead.
+
+```kotlin
+fun main() {
+ // caution: creating null with JsonUnquotedLiteral will cause an exception!
+ JsonUnquotedLiteral("null")
+}
+```
+
+> 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
+```
+
+<!--- TEST LINES_START -->
+
+
## Json transformations
To affect the shape and contents of JSON output after serialization, or adapt input to deserialization,
@@ -679,7 +951,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-23.kt).
The output shows that both cases are correctly deserialized into a Kotlin [List].
@@ -731,7 +1003,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-24.kt).
You end up with a single JSON object, not an array with one element:
@@ -776,7 +1048,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-25.kt).
See the effect of the custom serializer:
@@ -849,7 +1121,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-26.kt).
No class discriminator is added in the JSON output:
@@ -896,10 +1168,10 @@
class ResponseSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Response<T>> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("Response", PolymorphicKind.SEALED) {
- element("Ok", buildClassSerialDescriptor("Ok") {
- element<String>("message")
+ element("Ok", dataSerializer.descriptor)
+ element("Error", buildClassSerialDescriptor("Error") {
+ element<String>("message")
})
- element("Error", dataSerializer.descriptor)
}
override fun deserialize(decoder: Decoder): Response<T> {
@@ -945,7 +1217,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-27.kt).
This gives you fine-grained control on the representation of the `Response` class in the JSON output:
@@ -1010,7 +1282,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-28.kt).
```text
UnknownProject(name=example, details={"type":"unknown","maintainer":"Unknown","license":"Apache 2.0"})
@@ -1025,8 +1297,10 @@
<!-- references -->
[RFC-4627]: https://www.ietf.org/rfc/rfc4627.txt
+[BigDecimal]: https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html
<!-- stdlib references -->
+[Double]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/
[Double.NaN]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-double/-na-n.html
[List]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/
[Map]: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-map/
@@ -1034,60 +1308,69 @@
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
-[InheritableSerialInfo]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-inheritable-serial-info/index.html
-[KSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
-[Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
+[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
+[InheritableSerialInfo]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-inheritable-serial-info/index.html
+[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
+[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding -->
-[Encoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
-[Decoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
+[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
+[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
<!--- MODULE /kotlinx-serialization-json -->
<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
-[Json]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
-[Json()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html
-[JsonBuilder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html
-[JsonBuilder.prettyPrint]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/pretty-print.html
-[JsonBuilder.isLenient]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/is-lenient.html
-[JsonBuilder.ignoreUnknownKeys]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html
-[JsonNames]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html
-[JsonBuilder.useAlternativeNames]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html
-[JsonBuilder.coerceInputValues]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html
-[JsonBuilder.encodeDefaults]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html
-[JsonBuilder.explicitNulls]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html
-[JsonBuilder.allowStructuredMapKeys]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html
-[JsonBuilder.allowSpecialFloatingPointValues]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-special-floating-point-values.html
-[JsonBuilder.classDiscriminator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/class-discriminator.html
-[JsonClassDiscriminator]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-class-discriminator/index.html
-[JsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/index.html
-[Json.parseToJsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html
-[JsonPrimitive]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/index.html
-[JsonPrimitive.content]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/content.html
-[JsonPrimitive()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html
-[JsonArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-array/index.html
-[JsonObject]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/index.html
-[_jsonPrimitive]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-primitive.html
-[_jsonArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-array.html
-[_jsonObject]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-object.html
-[int]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int.html
-[intOrNull]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int-or-null.html
-[long]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long.html
-[longOrNull]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long-or-null.html
-[buildJsonArray]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-array.html
-[buildJsonObject]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html
-[Json.decodeFromJsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html
-[JsonTransformingSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/index.html
-[Json.encodeToString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html
-[JsonContentPolymorphicSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/index.html
-[JsonEncoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/index.html
-[JsonDecoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/index.html
-[JsonDecoder.decodeJsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html
-[JsonEncoder.encodeJsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html
-[JsonDecoder.json]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/json.html
-[JsonEncoder.json]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/json.html
-[Json.encodeToJsonElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html
+[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
+[Json()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html
+[JsonBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/index.html
+[JsonBuilder.prettyPrint]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/pretty-print.html
+[JsonBuilder.isLenient]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/is-lenient.html
+[JsonBuilder.ignoreUnknownKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/ignore-unknown-keys.html
+[JsonNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-names/index.html
+[JsonBuilder.useAlternativeNames]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/use-alternative-names.html
+[JsonBuilder.coerceInputValues]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/coerce-input-values.html
+[JsonBuilder.encodeDefaults]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/encode-defaults.html
+[JsonBuilder.explicitNulls]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/explicit-nulls.html
+[JsonBuilder.allowStructuredMapKeys]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/allow-structured-map-keys.html
+[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
+[JsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-element/index.html
+[Json.parseToJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/parse-to-json-element.html
+[JsonPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/index.html
+[JsonPrimitive.content]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive/content.html
+[JsonPrimitive()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-primitive.html
+[JsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-array/index.html
+[JsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-object/index.html
+[_jsonPrimitive]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-primitive.html
+[_jsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-array.html
+[_jsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/json-object.html
+[int]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int.html
+[intOrNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/int-or-null.html
+[long]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long.html
+[longOrNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/long-or-null.html
+[buildJsonArray]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-array.html
+[buildJsonObject]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/build-json-object.html
+[Json.decodeFromJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/decode-from-json-element.html
+[JsonUnquotedLiteral]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-unquoted-literal.html
+[JsonNull]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-null/index.html
+[JsonTransformingSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-transforming-serializer/index.html
+[Json.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html
+[JsonContentPolymorphicSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/index.html
+[JsonEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/index.html
+[JsonDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/index.html
+[JsonDecoder.decodeJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/decode-json-element.html
+[JsonEncoder.encodeJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/encode-json-element.html
+[JsonDecoder.json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-decoder/json.html
+[JsonEncoder.json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-encoder/json.html
+[Json.encodeToJsonElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/encode-to-json-element.html
<!--- END -->
diff --git a/docs/polymorphism.md b/docs/polymorphism.md
index c41228a..2661e04 100644
--- a/docs/polymorphism.md
+++ b/docs/polymorphism.md
@@ -41,11 +41,11 @@
### Static types
-Kotlin serialization is fully static with respect to types by default. The structure of encoded objects is determined
+Kotlin Serialization is fully static with respect to types by default. The structure of encoded objects is determined
by *compile-time* types of objects. Let's examine this aspect in more detail and learn how
to serialize polymorphic data structures, where the type of data is determined at runtime.
-To show the static nature of Kotlin serialization let us make the following setup. An `open class Project`
+To show the static nature of Kotlin Serialization let us make the following setup. An `open class Project`
has just the `name` property, while its derived `class OwnedProject` adds an `owner` property.
In the below example, we serialize `data` variable with a static type of
`Project` that is initialized with an instance of `OwnedProject` at runtime.
@@ -92,7 +92,7 @@
```text
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found.
-Mark the class as @Serializable or provide the serializer explicitly.
+Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
```
<!--- TEST LINES_START -->
@@ -123,8 +123,9 @@
This is close to the best design for a serializable hierarchy of classes, but running it produces the following error:
```text
-Exception in thread "main" kotlinx.serialization.SerializationException: Class 'OwnedProject' is not registered for polymorphic serialization in the scope of 'Project'.
-Mark the base class as 'sealed' or register the serializer explicitly.
+Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.
+Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.
+To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'.
```
<!--- TEST LINES_START -->
@@ -194,7 +195,7 @@
<!--- TEST -->
-In general, Kotlin serialization is designed to work correctly only when the compile-time type used during serialization
+In general, Kotlin Serialization is designed to work correctly only when the compile-time type used during serialization
is the same one as the compile-time type used during deserialization. You can always specify the type explicitly
when calling serialization functions. The previous example can be corrected to use `Project` type for serialization
by calling `Json.encodeToString<Project>(data)`.
@@ -407,6 +408,8 @@
{"type":"owned","name":"kotlinx.coroutines","owner":"kotlin"}
```
+> Note: On Kotlin/Native, you should use `format.encodeToString(PolymorphicSerializer(Project::class), data))` instead due to limited reflection capabilities.
+
<!--- TEST LINES_START -->
### Property of an interface type
@@ -495,7 +498,7 @@
```text
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.
-Mark the class as @Serializable or provide the serializer explicitly.
+Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
```
<!--- TEST LINES_START -->
@@ -539,11 +542,11 @@
> 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.
-Mark the class as @Serializable or provide the serializer explicitly.
+Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
```
<!--- TEST LINES_START -->
@@ -592,7 +595,7 @@
### Explicitly marking polymorphic class properties
The property of an interface type is implicitly considered polymorphic, since interfaces are all about runtime polymorphism.
-However, Kotlin serialization does not compile a serializable class with a property of a non-serializable class type.
+However, Kotlin Serialization does not compile a serializable class with a property of a non-serializable class type.
If we have a property of `Any` class or other non-serializable class, then we must explicitly provide its serialization
strategy via the [`@Serializable`][Serializable] annotation as we saw in
the [Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property) section.
@@ -708,7 +711,7 @@
data class OkResponse<out T>(val data: T) : Response<T>()
```
-Kotlin serialization does not have a builtin strategy to represent the actually provided argument type for the
+Kotlin Serialization does not have a builtin strategy to represent the actually provided argument type for the
type parameter `T` when serializing a property of the polymorphic type `OkResponse<T>`. We have to provide this
strategy explicitly when defining the serializers module for the `Response`. In the below example we
use `OkResponse.serializer(...)` to retrieve
@@ -829,7 +832,8 @@
We get the following exception.
```text
-Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'unknown'
+Exception in thread "main" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $
+Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule.
```
<!--- TEST LINES_START -->
@@ -1006,31 +1010,31 @@
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
-[PolymorphicSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic-serializer/index.html
-[Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
-[Polymorphic]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic/index.html
-[DeserializationStrategy]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html
-[SerializationStrategy]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html
+[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
+[PolymorphicSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic-serializer/index.html
+[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
+[Polymorphic]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-polymorphic/index.html
+[DeserializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html
+[SerializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.modules -->
-[SerializersModule]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html
-[SerializersModule()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html
-[_polymorphic]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/polymorphic.html
-[subclass]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html
-[plus]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/plus.html
-[SerializersModuleBuilder.include]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/include.html
-[PolymorphicModuleBuilder.defaultDeserializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default-deserializer.html
-[PolymorphicModuleBuilder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/index.html
-[SerializersModuleBuilder.polymorphicDefaultSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/polymorphic-default-serializer.html
-[SerializersModuleBuilder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html
+[SerializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html
+[SerializersModule()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html
+[_polymorphic]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/polymorphic.html
+[subclass]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/subclass.html
+[plus]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/plus.html
+[SerializersModuleBuilder.include]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/include.html
+[PolymorphicModuleBuilder.defaultDeserializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/default-deserializer.html
+[PolymorphicModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-polymorphic-module-builder/index.html
+[SerializersModuleBuilder.polymorphicDefaultSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/polymorphic-default-serializer.html
+[SerializersModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html
<!--- MODULE /kotlinx-serialization-json -->
<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
-[Json.encodeToString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html
-[Json]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
+[Json.encodeToString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/encode-to-string.html
+[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
<!--- END -->
diff --git a/docs/serialization-guide.md b/docs/serialization-guide.md
index e3f28ea..50cb130 100644
--- a/docs/serialization-guide.md
+++ b/docs/serialization-guide.md
@@ -49,6 +49,8 @@
* <a name='deserializing-collections'></a>[Deserializing collections](builtin-classes.md#deserializing-collections)
* <a name='maps'></a>[Maps](builtin-classes.md#maps)
* <a name='unit-and-singleton-objects'></a>[Unit and singleton objects](builtin-classes.md#unit-and-singleton-objects)
+ * <a name='duration'></a>[Duration](builtin-classes.md#duration)
+* <a name='nothing'></a>[Nothing](builtin-classes.md#nothing)
<!--- END -->
**Chapter 3.** [Serializers](serializers.md)
@@ -69,7 +71,9 @@
* <a name='serializing-3rd-party-classes'></a>[Serializing 3rd party classes](serializers.md#serializing-3rd-party-classes)
* <a name='passing-a-serializer-manually'></a>[Passing a serializer manually](serializers.md#passing-a-serializer-manually)
* <a name='specifying-serializer-on-a-property'></a>[Specifying serializer on a property](serializers.md#specifying-serializer-on-a-property)
+ * <a name='specifying-serializer-for-a-particular-type'></a>[Specifying serializer for a particular type](serializers.md#specifying-serializer-for-a-particular-type)
* <a name='specifying-serializers-for-a-file'></a>[Specifying serializers for a file](serializers.md#specifying-serializers-for-a-file)
+ * <a name='specifying-serializer-globally-using-typealias'></a>[Specifying serializer globally using typealias](serializers.md#specifying-serializer-globally-using-typealias)
* <a name='custom-serializers-for-a-generic-type'></a>[Custom serializers for a generic type](serializers.md#custom-serializers-for-a-generic-type)
* <a name='format-specific-serializers'></a>[Format-specific serializers](serializers.md#format-specific-serializers)
* <a name='contextual-serialization'></a>[Contextual serialization](serializers.md#contextual-serialization)
@@ -116,11 +120,17 @@
* <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)
* <a name='parsing-to-json-element'></a>[Parsing to Json element](json.md#parsing-to-json-element)
* <a name='types-of-json-elements'></a>[Types of Json elements](json.md#types-of-json-elements)
* <a name='json-element-builders'></a>[Json element builders](json.md#json-element-builders)
* <a name='decoding-json-elements'></a>[Decoding Json elements](json.md#decoding-json-elements)
+ * <a name='encoding-literal-json-content-experimental'></a>[Encoding literal Json content (experimental)](json.md#encoding-literal-json-content-experimental)
+ * <a name='serializing-large-decimal-numbers'></a>[Serializing large decimal numbers](json.md#serializing-large-decimal-numbers)
+ * <a name='using-jsonunquotedliteral-to-create-a-literal-unquoted-value-of-null-is-forbidden'></a>[Using `JsonUnquotedLiteral` to create a literal unquoted value of `null` is forbidden](json.md#using-jsonunquotedliteral-to-create-a-literal-unquoted-value-of-null-is-forbidden)
* <a name='json-transformations'></a>[Json transformations](json.md#json-transformations)
* <a name='array-wrapping'></a>[Array wrapping](json.md#array-wrapping)
* <a name='array-unwrapping'></a>[Array unwrapping](json.md#array-unwrapping)
@@ -153,10 +163,10 @@
* <a name='format-specific-types'></a>[Format-specific types](formats.md#format-specific-types)
<!--- END -->
-**Appendix A.** [Serialization and inline classes (experimental, IR-specific)](inline-classes.md)
+**Appendix A.** [Serialization and value classes (IR-only)](value-classes.md)
-<!--- TOC_REF inline-classes.md -->
-* <a name='serializable-inline-classes'></a>[Serializable inline classes](inline-classes.md#serializable-inline-classes)
-* <a name='unsigned-types-support-json-only'></a>[Unsigned types support (JSON only)](inline-classes.md#unsigned-types-support-json-only)
-* <a name='using-inline-classes-in-your-custom-serializers'></a>[Using inline classes in your custom serializers](inline-classes.md#using-inline-classes-in-your-custom-serializers)
+<!--- TOC_REF value-classes.md -->
+* <a name='serializable-value-classes'></a>[Serializable value classes](value-classes.md#serializable-value-classes)
+* <a name='unsigned-types-support-json-only'></a>[Unsigned types support (JSON only)](value-classes.md#unsigned-types-support-json-only)
+* <a name='using-value-classes-in-your-custom-serializers'></a>[Using value classes in your custom serializers](value-classes.md#using-value-classes-in-your-custom-serializers)
<!--- END -->
diff --git a/docs/serializers.md b/docs/serializers.md
index fc4e1a6..e6bf78e 100644
--- a/docs/serializers.md
+++ b/docs/serializers.md
@@ -24,7 +24,9 @@
* [Serializing 3rd party classes](#serializing-3rd-party-classes)
* [Passing a serializer manually](#passing-a-serializer-manually)
* [Specifying serializer on a property](#specifying-serializer-on-a-property)
+ * [Specifying serializer for a particular type](#specifying-serializer-for-a-particular-type)
* [Specifying serializers for a file](#specifying-serializers-for-a-file)
+ * [Specifying serializer globally using typealias](#specifying-serializer-globally-using-typealias)
* [Custom serializers for a generic type](#custom-serializers-for-a-generic-type)
* [Format-specific serializers](#format-specific-serializers)
* [Contextual serialization](#contextual-serialization)
@@ -712,7 +714,7 @@
### Passing a serializer manually
All `encodeToXxx` and `decodeFromXxx` functions have an overload with the first serializer parameter.
-When a non-serializable class, like `Date`, is the top-level class being serialized we can use those.
+When a non-serializable class, like `Date`, is the top-level class being serialized, we can use those.
```kotlin
fun main() {
@@ -769,6 +771,45 @@
<!--- TEST -->
+### Specifying serializer for a particular type
+
+[`@Serializable`][Serializable] annotation can also be applied directly to the types.
+This is handy when a class that requires a custom serializer, such as `Date`, happens to be a generic type argument.
+The most common use case for that is when you have a list of dates:
+
+<!--- INCLUDE
+import java.util.Date
+import java.text.SimpleDateFormat
+
+object DateAsLongSerializer : KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
+ override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
+}
+-->
+
+```kotlin
+@Serializable
+class ProgrammingLanguage(
+ val name: String,
+ val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date>
+)
+
+fun main() {
+ val df = SimpleDateFormat("yyyy-MM-ddX")
+ val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00")))
+ println(Json.encodeToString(data))
+}
+```
+
+> You can get the full code [here](../guide/example/example-serializer-16.kt).
+
+```text
+{"name":"Kotlin","releaseDates":[1688601600000,1682380800000,1672185600000]}
+```
+
+<!--- TEST -->
+
### Specifying serializers for a file
A serializer for a specific type, like `Date`, can be specified for a whole source code file with the file-level
@@ -802,7 +843,7 @@
println(Json.encodeToString(data))
}
```
-> You can get the full code [here](../guide/example/example-serializer-16.kt).
+> You can get the full code [here](../guide/example/example-serializer-17.kt).
```text
{"name":"Kotlin","stableReleaseDate":1455494400000}
@@ -810,6 +851,60 @@
<!--- TEST -->
+### Specifying serializer globally using typealias
+
+kotlinx.serialization tends to be the always-explicit framework when it comes to serialization strategies: normally,
+they should be explicitly mentioned in `@Serializable` annotation. Therefore, we do not provide any kind of global serializer
+configuration (except for [context serializer](#contextual-serialization) mentioned later).
+
+However, in projects with a large number of files and classes, it may be too cumbersome to specify `@file:UseSerializers`
+every time, especially for classes like `Date` or `Instant` that have a fixed strategy of serialization across the project.
+For such cases, it is possible to specify serializers using `typealias`es, as they preserve annotations, including serialization-related ones:
+<!--- INCLUDE
+import java.util.Date
+import java.text.SimpleDateFormat
+
+object DateAsLongSerializer : KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsLong", PrimitiveKind.LONG)
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
+ override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
+}
+
+object DateAsSimpleTextSerializer: KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG)
+ private val format = SimpleDateFormat("yyyy-MM-dd")
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value))
+ override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString())
+}
+-->
+
+```kotlin
+typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date
+
+typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date
+```
+
+Using these new different types, it is possible to serialize a Date differently without additional annotations:
+
+```kotlin
+@Serializable
+class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong)
+
+fun main() {
+ val format = SimpleDateFormat("yyyy-MM-ddX")
+ val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00"))
+ println(Json.encodeToString(data))
+}
+```
+
+> You can get the full code [here](../guide/example/example-serializer-18.kt).
+
+```text
+{"stableReleaseDate":"2016-02-15","lastReleaseTimestamp":1657152000000}
+```
+
+<!--- TEST -->
+
### Custom serializers for a generic type
Let us take a look at the following example of the generic `Box<T>` class.
@@ -850,7 +945,7 @@
}
```
-> You can get the full code [here](../guide/example/example-serializer-17.kt).
+> You can get the full code [here](../guide/example/example-serializer-19.kt).
The resulting JSON looks like the `Project` class was serialized directly.
@@ -914,11 +1009,11 @@
To actually serialize this class we must provide the corresponding context when calling the `encodeToXxx`/`decodeFromXxx`
functions. Without it we'll get a "Serializer for class 'Date' is not found" exception.
-> See [here](../guide/example/example-serializer-18.kt) for an example that produces that exception.
+> See [here](../guide/example/example-serializer-20.kt) for an example that produces that exception.
<!--- TEST LINES_START
Exception in thread "main" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.
-Mark the class as @Serializable or provide the serializer explicitly.
+Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.
-->
<!--- INCLUDE
@@ -973,7 +1068,7 @@
}
```
-> You can get the full code [here](../guide/example/example-serializer-19.kt).
+> You can get the full code [here](../guide/example/example-serializer-21.kt).
```text
{"name":"Kotlin","stableReleaseDate":1455494400000}
```
@@ -1032,7 +1127,7 @@
}
```
-> You can get the full code [here](../guide/example/example-serializer-20.kt).
+> You can get the full code [here](../guide/example/example-serializer-22.kt).
This gets all the `Project` properties serialized:
@@ -1073,7 +1168,7 @@
}
```
-> You can get the full code [here](../guide/example/example-serializer-21.kt).
+> You can get the full code [here](../guide/example/example-serializer-23.kt).
The output is shown below.
@@ -1093,66 +1188,66 @@
<!--- MODULE /kotlinx-serialization-core -->
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
-[Serializable]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
-[KSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
-[KSerializer.descriptor]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/descriptor.html
-[SerializationStrategy.serialize]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/serialize.html
-[SerializationStrategy]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html
-[DeserializationStrategy.deserialize]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/deserialize.html
-[DeserializationStrategy]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html
-[Serializable.with]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/with.html
-[SerialName]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
-[UseSerializers]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-serializers/index.html
-[ContextualSerializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual-serializer/index.html
-[Contextual]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual/index.html
-[UseContextualSerialization]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-contextual-serialization/index.html
-[Serializer]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/index.html
-[Serializer.forClass]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/for-class.html
+[Serializable]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/index.html
+[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
+[KSerializer.descriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/descriptor.html
+[SerializationStrategy.serialize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/serialize.html
+[SerializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serialization-strategy/index.html
+[DeserializationStrategy.deserialize]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/deserialize.html
+[DeserializationStrategy]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-deserialization-strategy/index.html
+[Serializable.with]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializable/with.html
+[SerialName]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serial-name/index.html
+[UseSerializers]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-serializers/index.html
+[ContextualSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual-serializer/index.html
+[Contextual]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-contextual/index.html
+[UseContextualSerialization]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-use-contextual-serialization/index.html
+[Serializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/index.html
+[Serializer.forClass]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-serializer/for-class.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.builtins -->
-[ListSerializer()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-list-serializer.html
-[SetSerializer()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-set-serializer.html
-[MapSerializer()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-map-serializer.html
+[ListSerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-list-serializer.html
+[SetSerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-set-serializer.html
+[MapSerializer()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.builtins/-map-serializer.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding -->
-[Encoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
-[Encoder.encodeString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-string.html
-[Decoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
-[Decoder.decodeString]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-string.html
-[Encoder.encodeSerializableValue]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html
-[Decoder.decodeSerializableValue]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html
-[encodeStructure]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html
-[CompositeEncoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/index.html
-[decodeStructure]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/decode-structure.html
-[CompositeDecoder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html
-[CompositeDecoder.decodeElementIndex]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html
-[CompositeDecoder.decodeIntElement]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-int-element.html
-[CompositeDecoder.decodeSequentially]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html
+[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
+[Encoder.encodeString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-string.html
+[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
+[Decoder.decodeString]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-string.html
+[Encoder.encodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-serializable-value.html
+[Decoder.decodeSerializableValue]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/decode-serializable-value.html
+[encodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html
+[CompositeEncoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/index.html
+[decodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/decode-structure.html
+[CompositeDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html
+[CompositeDecoder.decodeElementIndex]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-element-index.html
+[CompositeDecoder.decodeIntElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-int-element.html
+[CompositeDecoder.decodeSequentially]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-sequentially.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.descriptors -->
-[PrimitiveSerialDescriptor()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-serial-descriptor.html
-[PrimitiveKind]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-kind/index.html
-[SerialDescriptor]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html
-[buildClassSerialDescriptor]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/build-class-serial-descriptor.html
-[ClassSerialDescriptorBuilder.element]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/element.html
-[SerialKind]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-kind/index.html
+[PrimitiveSerialDescriptor()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-serial-descriptor.html
+[PrimitiveKind]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-primitive-kind/index.html
+[SerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html
+[buildClassSerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/build-class-serial-descriptor.html
+[ClassSerialDescriptorBuilder.element]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/element.html
+[SerialKind]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-kind/index.html
<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.modules -->
-[SerializersModule]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html
-[SerializersModule()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html
-[SerializersModuleBuilder]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html
-[_contextual]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/contextual.html
+[SerializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module/index.html
+[SerializersModule()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module.html
+[SerializersModuleBuilder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/-serializers-module-builder/index.html
+[_contextual]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.modules/contextual.html
<!--- MODULE /kotlinx-serialization-json -->
<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
-[Json]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
-[Json()]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html
-[JsonBuilder.serializersModule]: https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/serializers-module.html
+[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
+[Json()]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json.html
+[JsonBuilder.serializersModule]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-builder/serializers-module.html
<!--- END -->
diff --git a/docs/value-classes.md b/docs/value-classes.md
new file mode 100644
index 0000000..fed90f6
--- /dev/null
+++ b/docs/value-classes.md
@@ -0,0 +1,204 @@
+# Serialization and value classes (IR-specific)
+
+This appendix describes how value classes are handled by kotlinx.serialization.
+
+> Features described are available on JVM/IR (enabled by default), JS/IR and Native backends.
+
+**Table of contents**
+
+<!--- TOC -->
+
+* [Serializable value classes](#serializable-value-classes)
+* [Unsigned types support (JSON only)](#unsigned-types-support-json-only)
+* [Using value classes in your custom serializers](#using-value-classes-in-your-custom-serializers)
+
+<!--- END -->
+
+## Serializable value classes
+
+We can mark value class as serializable:
+
+```kotlin
+@Serializable
+@JvmInline
+value class Color(val rgb: Int)
+```
+
+Value class in Kotlin is stored as its underlying type when possible (i.e. no boxing is required).
+Serialization framework does not impose any additional restrictions and uses the underlying type where possible as well.
+
+```kotlin
+@Serializable
+data class NamedColor(val color: Color, val name: String)
+
+fun main() {
+ println(Json.encodeToString(NamedColor(Color(0), "black")))
+}
+```
+
+In this example, `NamedColor` is serialized as two primitives: `color: Int` and `name: String` without an allocation
+of `Color` class. When we run the example, encoding data with JSON format, we get the following
+output:
+
+```text
+{"color": 0, "name": "black"}
+```
+
+As we see, `Color` class is not included during the encoding, only its underlying data. This invariant holds even if the actual value class
+is [allocated](https://kotlinlang.org/docs/reference/inline-classes.html#representation) — for example, when value
+class is used as a generic type argument:
+
+```kotlin
+@Serializable
+class Palette(val colors: List<Color>)
+
+fun main() {
+ println(Json.encodeToString(Palette(listOf(Color(0), Color(255), Color(128)))))
+}
+```
+
+The snippet produces the following output:
+
+```text
+{"colors":[0, 255, 128]}
+```
+
+## Unsigned types support (JSON only)
+
+Kotlin standard library provides ready-to-use unsigned arithmetics, leveraging value classes
+to represent unsigned types: `UByte`, `UShort`, `UInt` and `ULong`.
+[Json] format has built-in support for them: these types are serialized as theirs string
+representations in unsigned form.
+These types are handled as regular serializable types by the compiler plugin and can be freely used in serializable classes:
+
+```kotlin
+@Serializable
+class Counter(val counted: UByte, val description: String)
+
+fun main() {
+ val counted = 239.toUByte()
+ println(Json.encodeToString(Counter(counted, "tries")))
+}
+```
+
+The output is following:
+
+```text
+{"counted":239,"description":"tries"}
+```
+
+> Unsigned types are currently supported only in JSON format. Other formats such as ProtoBuf and CBOR
+> use built-in serializers that use an underlying signed representation for unsigned types.
+
+## Using value classes in your custom serializers
+
+Let's return to our `NamedColor` example and try to write a custom serializer for it. Normally, as shown
+in [Hand-written composite serializer](serializers.md#hand-written-composite-serializer), we would write the following code
+in `serialize` method:
+
+```kotlin
+override fun serialize(encoder: Encoder, value: NamedColor) {
+ encoder.encodeStructure(descriptor) {
+ encodeSerializableElement(descriptor, 0, Color.serializer(), value.color)
+ encodeStringElement(descriptor, 1, value.name)
+ }
+}
+```
+
+However, since `Color` is used as a type argument in [encodeSerializableElement][CompositeEncoder.encodeSerializableElement] function, `value.color` will be boxed
+to `Color` wrapper before passing it to the function, preventing the value class optimization. To avoid this, we can use
+special [encodeInlineElement][CompositeEncoder.encodeInlineElement] function instead. It uses [serial descriptor][SerialDescriptor] of `Color` ([retrieved][SerialDescriptor.getElementDescriptor] from serial descriptor of `NamedColor`) instead of [KSerializer],
+does not have type parameters and does not accept any values. Instead, it returns [Encoder]. Using it, we can encode
+unboxed value:
+
+```kotlin
+override fun serialize(encoder: Encoder, value: NamedColor) {
+ encoder.encodeStructure(descriptor) {
+ encodeInlineElement(descriptor, 0).encodeInt(value.color)
+ encodeStringElement(descriptor, 1, value.name)
+ }
+}
+```
+
+The same principle goes also with [CompositeDecoder]: it has [decodeInlineElement][CompositeDecoder.decodeInlineElement] function that returns [Decoder].
+
+If your class should be represented as a primitive (as shown in [Primitive serializer](serializers.md#primitive-serializer) section),
+and you cannot use [encodeStructure][Encoder.encodeStructure] function, there is a complementary function in [Encoder] called [encodeInline][Encoder.encodeInline].
+We will use it to show an example how one can represent a class as an unsigned integer.
+
+Let's start with a UID class:
+
+```kotlin
+@Serializable(UIDSerializer::class)
+class UID(val uid: Int)
+```
+
+`uid` type is `Int`, but suppose we want it to be an unsigned integer in JSON. We can start writing the
+following custom serializer:
+
+```kotlin
+object UIDSerializer: KSerializer<UID> {
+ override val descriptor = UInt.serializer().descriptor
+}
+```
+
+Note that we are using here descriptor from `UInt.serializer()` — it means that the class' representation looks like a
+UInt's one.
+
+Then the `serialize` method:
+
+```kotlin
+override fun serialize(encoder: Encoder, value: UID) {
+ encoder.encodeInline(descriptor).encodeInt(value.uid)
+}
+```
+
+That's where the magic happens — despite we called a regular [encodeInt][Encoder.encodeInt] with a `uid: Int` argument, the output will contain
+an unsigned int because of the special encoder from `encodeInline` function. Since JSON format supports unsigned integers, it
+recognizes theirs descriptors when they're passed into `encodeInline` and handles consecutive calls as for unsigned integers.
+
+The `deserialize` method looks symmetrically:
+
+```kotlin
+override fun deserialize(decoder: Decoder): UID {
+ return UID(decoder.decodeInline(descriptor).decodeInt())
+}
+```
+
+> Disclaimer: You can also write such a serializer for value class itself (imagine UID being the value class — there's no need to change anything in the serializer).
+> However, do not use anything in custom serializers for value classes besides `encodeInline`. As we discussed, calls to value class serializer may be
+> optimized and replaced with a `encodeInlineElement` calls.
+> `encodeInline` and `encodeInlineElement` calls with the same descriptor are considered equivalent and can be replaced with each other — formats should return the same `Encoder`.
+> If you embed custom logic in custom value class serializer, you may get different results depending on whether this serializer was called at all
+> (and this, in turn, depends on whether value class was boxed or not).
+
+---
+
+<!--- MODULE /kotlinx-serialization-core -->
+<!--- INDEX kotlinx-serialization-core/kotlinx.serialization -->
+
+[KSerializer]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-k-serializer/index.html
+
+<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.encoding -->
+
+[CompositeEncoder.encodeSerializableElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-serializable-element.html
+[CompositeEncoder.encodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-encoder/encode-inline-element.html
+[Encoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/index.html
+[CompositeDecoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/index.html
+[CompositeDecoder.decodeInlineElement]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-composite-decoder/decode-inline-element.html
+[Decoder]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-decoder/index.html
+[Encoder.encodeStructure]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/encode-structure.html
+[Encoder.encodeInline]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-inline.html
+[Encoder.encodeInt]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.encoding/-encoder/encode-int.html
+
+<!--- INDEX kotlinx-serialization-core/kotlinx.serialization.descriptors -->
+
+[SerialDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html
+[SerialDescriptor.getElementDescriptor]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/get-element-descriptor.html
+
+<!--- MODULE /kotlinx-serialization-json -->
+<!--- INDEX kotlinx-serialization-json/kotlinx.serialization.json -->
+
+[Json]: https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json/index.html
+
+<!--- END -->
diff --git a/dokka-templates/README.md b/dokka-templates/README.md
new file mode 100644
index 0000000..2b16f5b
--- /dev/null
+++ b/dokka-templates/README.md
@@ -0,0 +1,12 @@
+# Dokka's template customization
+To provide unified navigation for all parts of [kotlinlang.org](https://kotlinlang.org/),
+the Kotlin Website Team uses this directory to place custom templates in this folder
+during the website build time on TeamCity.
+
+It is not practical to place these templates in the kotlinx.serialization repository because they change from time to time
+and aren't related to the library's release cycle.
+
+The folder is defined as a source for custom templates by the templatesDir property through Dokka's plugin configuration.
+
+[Here](https://kotlin.github.io/dokka/1.7.20-SNAPSHOT/user_guide/output-formats/html/#custom-html-pages), you can
+find more about the customization of Dokka's HTML output.
\ No newline at end of file
diff --git a/dokka/moduledoc.md b/dokka/moduledoc.md
index e3c1261..cd0462d 100644
--- a/dokka/moduledoc.md
+++ b/dokka/moduledoc.md
@@ -5,6 +5,10 @@
# Module kotlinx-serialization-json
Stable and ready to use JSON format implementation, `JsonElement` API to operate with JSON trees and JSON-specific serializers.
+# Module kotlinx-serialization-json-okio
+Extensions for kotlinx.serialization.json.Json for integration with the popular [Okio](https://square.github.io/okio/) library.
+Currently experimental.
+
# Module kotlinx-serialization-cbor
Concise Binary Object Representation (CBOR) format implementation, as per [RFC 7049](https://tools.ietf.org/html/rfc7049).
@@ -17,7 +21,7 @@
Allows converting arbitrary hierarchy of Kotlin classes to a flat key-value structure à la Java Properties.
# Module kotlinx-serialization-protobuf
-Protocol buffers serialization format implementation, mostly compliant to [proto2](https://developers.google.com/protocol-buffers/docs/proto) specification.
+[Protocol buffers](https://protobuf.dev/) serialization format implementation.
# Package kotlinx.serialization
Basic core concepts and annotations that set up serialization process.
@@ -42,8 +46,14 @@
JSON serialization format implementation, JSON tree data structures with builders for them,
and JSON-specific serializers.
+# Package kotlinx.serialization.json.okio
+Extensions for kotlinx.serialization.json.Json for integration with the popular [Okio](https://square.github.io/okio/) library.
+
# Package kotlinx.serialization.protobuf
-Protocol buffers serialization format implementation, mostly compliant to [proto2](https://developers.google.com/protocol-buffers/docs/proto) specification.
+[Protocol buffers](https://protobuf.dev/) serialization format implementation.
+
+# Package kotlinx.serialization.protobuf.schema
+Experimental generator of ProtoBuf schema from Kotlin classes.
# Package kotlinx.serialization.properties
Properties serialization format implementation that represents the input data as a plain map of properties.
diff --git a/formats/README.md b/formats/README.md
index eb29d61..724b06a 100644
--- a/formats/README.md
+++ b/formats/README.md
@@ -5,141 +5,32 @@
For convenience, they have same `groupId`, versioning and release cycle as core library.
-## JSON
-
-* Artifact id: `kotlinx-serialization-json`
-* Platform: all supported platforms
-* Status: stable
-
-## HOCON
-
-* Artifact id: `kotlinx-serialization-hocon`
-* Platform: JVM only
-* Status: experimental
-
-Allows deserialization of `Config` object from popular [lightbend/config](https://github.com/lightbend/config) library
-into Kotlin objects.
-You can learn about "Human-Optimized Config Object Notation" or HOCON from library's [readme](https://github.com/lightbend/config#using-hocon-the-json-superset).
-
-## ProtoBuf
-
-* Artifact id: `kotlinx-serialization-protobuf`
-* Platform: all supported platforms
-* Status: experimental
-
-## CBOR
-
-* Artifact id: `kotlinx-serialization-cbor`
-* Platform: all supported platforms
-* Status: experimental
-
-## Properties
-
-* Artifact id: `kotlinx-serialization-properties`
-* Platform: all supported platforms
-* Status: experimental
-
-Allows converting arbitrary hierarchy of Kotlin classes to a flat key-value structure à la Java Properties.
+| Format | Artifact id | Platform | Status | Notes |
+|------------|------------------------------------|---------------------------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| JSON | `kotlinx-serialization-json` | all supported platforms | stable | |
+| JSON-Okio | `kotlinx-serialization-json-okio` | all supported by Okio platforms | experimental | Extensions on Json for integration with [Okio](https://square.github.io/okio/) library. |
+| HOCON | `kotlinx-serialization-hocon` | JVM only | experimental | Allows deserialization of `Config` object from popular [lightbend/config](https://github.com/lightbend/config) library into Kotlin objects.You can learn about "Human-Optimized Config Object Notation" or HOCON from library's [readme](https://github.com/lightbend/config#using-hocon-the-json-superset). |
+| ProtoBuf | `kotlinx-serialization-protobuf` | all supported platforms | experimental | |
+| CBOR | `kotlinx-serialization-cbor` | all supported platforms | experimental | |
+| Properties | `kotlinx-serialization-properties` | all supported platforms | experimental | Allows converting arbitrary hierarchy of Kotlin classes to a flat key-value structure à la Java Properties. |
## Other community-supported formats
-### Avro
-
-* GitHub repo: [sksamuel/avro4k](https://github.com/sksamuel/avro4k)
-* Artifact ID: `com.sksamuel.avro4k:avro4k`
-* Platform: JVM only
-
-This library allows serialization and deserialization of objects to and from [Avro](https://avro.apache.org). It will read and write from Avro binary or json streams or generate Avro Generic Records directly. It will also generate Avro schemas from data classes. The library allows for easy extension and overrides for custom schema formats, compatiblity with schemas defined outside out of the JVM and for types not supported out of the box.
-
-### Bson
-
-* GitHub repo: [jershell/kbson](https://github.com/jershell/kbson)
-* Artifact ID: `com.github.jershell:kbson`
-* Platform: JVM only
-
-Allows serialization and deserialization of objects to and from [BSON](https://docs.mongodb.com/manual/reference/bson-types/).
-
-### Ktoml
-* GitHub repo: [akuleshov7/ktoml](https://github.com/akuleshov7/ktoml)
-* Artifact ID: `com.akuleshov7:ktoml-core`
-* Platforms: multiplatform, all Kotlin supported platforms
-
-Fully Native and Multiplatform Kotlin serialization library for serialization/deserialization of TOML format.
-This library contains no Java code and no Java dependencies and it implements multiplatform parser, decoder and encoder of TOML.
-
-### Minecraft NBT (Multiplatform)
-
-* GitHub repo: [BenWoodworth/knbt](https://github.com/BenWoodworth/knbt)
-* Artifact ID: `net.benwoodworth.knbt:knbt`
-* Platform: all supported platforms
-
-Implements the [NBT format](https://minecraft.fandom.com/wiki/NBT_format) for kotlinx.serialization, and
-provides a type-safe DSL for constructing NBT tags.
-
-### MsgPack (Multiplatform)
-
-* GitHub repo: [esensar/kotlinx-serialization-msgpack](https://github.com/esensar/kotlinx-serialization-msgpack)
-* Artifact ID: `com.ensarsarajcic.kotlinx:serialization-msgpack`
-* Platform: all supported platforms
-
-Allows serialization and deserialization of objects to and from [MsgPack](https://msgpack.org/).
-
-### SharedPreferences
-
-* GitHub repo: [EdwarDDay/serialization.kprefs](https://github.com/EdwarDDay/serialization.kprefs)
-* Artifact ID: `net.edwardday.serialization:kprefs`
-* Platform: Android only
-
-This library allows serialization and deserialization of objects into and from Android
-[SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences).
-
-### XML
-* GitHub repo: [pdvrieze/xmlutil](https://github.com/pdvrieze/xmlutil)
-* Artifact ID: `io.github.pdvrieze.xmlutil:serialization`
-* Platform: all supported platforms
-
-This library allows for reading and writing of XML documents with the serialization library.
-It is multiplatform, providing both a shared parser/writer for xml as well as platform-specific
-parsers where available. The library is designed to handle existing xml formats that use features that would
-not be available in other formats such as JSON.
-
-### YAML
-
-* GitHub repo: [charleskorn/kaml](https://github.com/charleskorn/kaml)
-* Artifact ID: `com.charleskorn.kaml:kaml`
-* Platform: JVM only
-
-Allows serialization and deserialization of objects to and from [YAML](http://yaml.org).
-
-### YAML (Multiplatform)
-
-* GitHub repo: [him188/yamlkt](https://github.com/him188/yamlkt)
-* Artifact ID: `net.mamoe.yamlkt:yamlkt`
-* Platform: all supported platforms
-
-Allows serialization and deserialization of objects to and from [YAML](http://yaml.org).
-Basic serial operations have been implemented, but some features such as compound keys and polymorphism are still work in progress.
-
-### CBOR
-
-* GitHub repo: [L-Briand/obor](https://github.com/L-Briand/obor)
-* Artifact ID: `net.orandja.obor:obor`
-* Platform: JVM, Android
-
-Allow serialization and deserialization of objects to and from [CBOR](https://cbor.io/). This codec can be used to read and write from Java InputStream and OutputStream.
-
-### Amazon Ion (binary only)
-
-* GitHub repo: [dimitark/kotlinx-serialization-ion](https://github.com/dimitark/kotlinx-serialization-ion)
-* Artifact ID: `com.github.dimitark:kotlinx-serialization-ion`
-* Platform: JVM
-
-Allow serialization and deserialization of objects to and from [Amazon Ion](https://amzn.github.io/ion-docs/). It stores the data in a flat binary format. Upon destialization, it retains the references between the objects.
-
-### android.os.Bundle
-
-* GitHub repo: [AhmedMourad0/bundlizer](https://github.com/AhmedMourad0/bundlizer)
-* Artifact ID: `dev.ahmedmourad.bundlizer:bundlizer-core`
-* Platform: Android
-
-Allow serialization and deserialization of objects to and from [android.os.Bundle](https://developer.android.com/reference/android/os/Bundle).
+| Format | GitHub repo and Artifact | Platform | Notes |
+|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| Avro | [sksamuel/avro4k](https://github.com/sksamuel/avro4k) <br> `com.sksamuel.avro4k:avro4k` | JVM only | This library allows serialization and deserialization of objects to and from [Avro](https://avro.apache.org). It will read and write from Avro binary or json streams or generate Avro Generic Records directly. It will also generate Avro schemas from data classes. The library allows for easy extension and overrides for custom schema formats, compatiblity with schemas defined outside out of the JVM and for types not supported out of the box. |
+| Bson | [jershell/kbson](https://github.com/jershell/kbson) <br> `com.github.jershell:kbson` | JVM only | Allows serialization and deserialization of objects to and from [BSON](https://docs.mongodb.com/manual/reference/bson-types/). |
+| TOML | [Peanuuutz/tomlkt](https://github.com/Peanuuutz/tomlkt) <br> `net.peanuuutz.tomlkt:tomlkt` | all supported platforms | Multiplatform encoder and decoder for [TOML](http://toml.io/) 1.0.0 compliant. This library aims to provide similar API to the official JSON format (such as TomlLiteral, TomlTable), while adding TOML specific features (such as @TomlComment, @TomlMultilineString). |
+| TOML | [akuleshov7/ktoml](https://github.com/akuleshov7/ktoml) <br> `com.akuleshov7:ktoml-core` | all supported platforms | Fully Native and Multiplatform Kotlin serialization library for serialization/deserialization of TOML format. This library contains no Java code and no Java dependencies and it implements multiplatform parser, decoder and encoder of TOML. |
+| Minecraft NBT | [BenWoodworth/knbt](https://github.com/BenWoodworth/knbt) <br> `net.benwoodworth.knbt:knbt` | all supported platforms | Implements the [NBT format](https://minecraft.wiki/w/NBT_format) for kotlinx.serialization, and provides a type-safe DSL for constructing NBT tags. |
+| MsgPack | [esensar/kotlinx-serialization-msgpack](https://github.com/esensar/kotlinx-serialization-msgpack) <br> `com.ensarsarajcic.kotlinx:serialization-msgpack` | all supported platforms | Allows serialization and deserialization of objects to and from [MsgPack](https://msgpack.org/). |
+| SharedPreferences | [EdwarDDay/serialization.kprefs](https://github.com/EdwarDDay/serialization.kprefs) <br> `net.edwardday.serialization:kprefs` | Android only | This library allows serialization and deserialization of objects into and from Android [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences). |
+| XML | [pdvrieze/xmlutil](https://github.com/pdvrieze/xmlutil) <br> `io.github.pdvrieze.xmlutil:serialization` | all supported platforms | This library allows for reading and writing of XML documents with the serialization library. It is multiplatform, providing both a shared parser/writer for xml as well as platform-specific parsers where available. The library is designed to handle existing xml formats that use features that would not be available in other formats such as JSON. |
+| YAML | [charleskorn/kaml](https://github.com/charleskorn/kaml) <br> `com.charleskorn.kaml:kaml` | JVM only | Allows serialization and deserialization of objects to and from [YAML](http://yaml.org). |
+| YAML | [him188/yamlkt](https://github.com/him188/yamlkt) <br> `net.mamoe.yamlkt:yamlkt` | all supported platforms | Allows serialization and deserialization of objects to and from [YAML](http://yaml.org). Basic serial operations have been implemented, but some features such as compound keys and polymorphism are still work in progress. |
+| CBOR | [L-Briand/obor](https://github.com/L-Briand/obor) <br> `net.orandja.obor:obor` | JVM, Android | Allow serialization and deserialization of objects to and from [CBOR](https://cbor.io/). This codec can be used to read and write from Java InputStream and OutputStream. |
+| Amazon Ion (binary only) | [dimitark/kotlinx-serialization-ion](https://github.com/dimitark/kotlinx-serialization-ion) <br> `com.github.dimitark:kotlinx-serialization-ion` | JVM only | Allow serialization and deserialization of objects to and from [Amazon Ion](https://amzn.github.io/ion-docs/). It stores the data in a flat binary format. Upon destialization, it retains the references between the objects. |
+| android.os.Bundle | [AhmedMourad0/bundlizer](https://github.com/AhmedMourad0/bundlizer) <br> `dev.ahmedmourad.bundlizer:bundlizer-core` | Android | Allow serialization and deserialization of objects to and from [android.os.Bundle](https://developer.android.com/reference/android/os/Bundle). |
+| CSV | [hfhbd/kotlinx-serialization-csv](https://github.com/hfhbd/kotlinx-serialization-csv) <br> `app.softwork:kotlinx-serialization-csv` | all supported platforms | Allows serialization and deserialization of CSV files. There are still some limitations (ordered properties). |
+| Fixed Length Format | [hfhbd/kotlinx-serialization-csv](https://github.com/hfhbd/kotlinx-serialization-csv) <br> `app.softwork:kotlinx-serialization-flf` | all supported platforms | Allows serialization and deserialization of [Fixed Length Format files](https://www.ibm.com/docs/en/psfa/7.2.1?topic=format-fixed-length-files). Each property must be annotated with `@FixedLength` and there are still some limitations due to missing delimiters. |
+| JSON5 | [xn32/json5k](https://github.com/xn32/json5k) <br> `io.github.xn32:json5k` | JVM, Native | Library for the serialization to and deserialization from [JSON5](https://json5.org) text. |
diff --git a/formats/cbor/api/kotlinx-serialization-cbor.api b/formats/cbor/api/kotlinx-serialization-cbor.api
index 825c55f..cc75fab 100644
--- a/formats/cbor/api/kotlinx-serialization-cbor.api
+++ b/formats/cbor/api/kotlinx-serialization-cbor.api
@@ -1,7 +1,7 @@
public abstract interface annotation class kotlinx/serialization/cbor/ByteString : java/lang/annotation/Annotation {
}
-public final class kotlinx/serialization/cbor/ByteString$Impl : kotlinx/serialization/cbor/ByteString {
+public synthetic class kotlinx/serialization/cbor/ByteString$Impl : kotlinx/serialization/cbor/ByteString {
public fun <init> ()V
}
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
index a9bb476..9e76a8f 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt
@@ -38,7 +38,7 @@
/**
* The default instance of [Cbor]
*/
- public companion object Default : Cbor(false, false, EmptySerializersModule)
+ public companion object Default : Cbor(false, false, EmptySerializersModule())
override fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray {
val output = ByteArrayOutput()
diff --git a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
index 0b7a0e0..b77a18c 100644
--- a/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
+++ b/formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt
@@ -70,6 +70,8 @@
if (encodeByteArrayAsByteString && serializer.descriptor == ByteArraySerializer().descriptor) {
encoder.encodeByteString(value as ByteArray)
} else {
+ encodeByteArrayAsByteString = encodeByteArrayAsByteString || serializer.descriptor.isInlineByteString()
+
super.encodeSerializableValue(serializer, value)
}
}
@@ -278,6 +280,7 @@
@Suppress("UNCHECKED_CAST")
decoder.nextByteString() as T
} else {
+ decodeByteArrayAsByteString = decodeByteArrayAsByteString || deserializer.descriptor.isInlineByteString()
super.decodeSerializableValue(deserializer)
}
}
@@ -636,6 +639,11 @@
return getElementAnnotations(index).find { it is ByteString } != null
}
+private fun SerialDescriptor.isInlineByteString(): Boolean {
+ // inline item classes should only have 1 item
+ return isInline && isByteString(0)
+}
+
private val normalizeBaseBits = SINGLE_PRECISION_NORMALIZE_BASE.toBits()
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 86aafe4..b4d3c53 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -19,7 +19,6 @@
@Serializable
data class PolyBox(@Polymorphic val boxed: SimpleAbstract)
-@SharedImmutable
val SimplePolymorphicModule = SerializersModule {
polymorphic(SimpleAbstract::class) {
subclass(SimpleIntInheritor.serializer())
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
index edbe5e6..f615d5e 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt
@@ -634,6 +634,34 @@
}
@Test
+ fun testReadValueClassWithByteString() {
+ assertContentEquals(
+ expected = byteArrayOf(0x11, 0x22, 0x33),
+ actual = Cbor.decodeFromHexString<ValueClassWithByteString>("43112233").x
+ )
+ }
+
+ @Test
+ fun testReadValueClassCustomByteString() {
+ assertEquals(
+ expected = ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)),
+ actual = Cbor.decodeFromHexString("43112233")
+ )
+ }
+
+ @Test
+ fun testReadValueClassWithUnlabeledByteString() {
+ assertContentEquals(
+ expected = byteArrayOf(
+ 0x11,
+ 0x22,
+ 0x33
+ ),
+ actual = Cbor.decodeFromHexString<ValueClassWithUnlabeledByteString>("43112233").x.x
+ )
+ }
+
+ @Test
fun testIgnoresTagsOnStrings() {
/*
* 84 # array(4)
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
index c546bdf..da7b128 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt
@@ -122,4 +122,28 @@
actual = Cbor.encodeToHexString(TypeWithNullableCustomByteString(null))
)
}
+
+ @Test
+ fun testWriteValueClassWithByteString() {
+ assertEquals(
+ expected = "43112233",
+ actual = Cbor.encodeToHexString(ValueClassWithByteString(byteArrayOf(0x11, 0x22, 0x33)))
+ )
+ }
+
+ @Test
+ fun testWriteValueClassCustomByteString() {
+ assertEquals(
+ expected = "43112233",
+ actual = Cbor.encodeToHexString(ValueClassWithCustomByteString(CustomByteString(0x11, 0x22, 0x33)))
+ )
+ }
+
+ @Test
+ fun testWriteValueClassWithUnlabeledByteString() {
+ assertEquals(
+ expected = "43112233",
+ actual = Cbor.encodeToHexString(ValueClassWithUnlabeledByteString(ValueClassWithUnlabeledByteString.Inner(byteArrayOf(0x11, 0x22, 0x33))))
+ )
+ }
}
diff --git a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
index ad55d04..e4418f4 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt
@@ -8,6 +8,7 @@
import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
+import kotlin.jvm.*
@Serializable
data class Simple(val a: String)
@@ -110,4 +111,20 @@
data class TypeWithCustomByteString(@ByteString val x: CustomByteString)
@Serializable
-data class TypeWithNullableCustomByteString(@ByteString val x: CustomByteString?)
\ No newline at end of file
+data class TypeWithNullableCustomByteString(@ByteString val x: CustomByteString?)
+
+@JvmInline
+@Serializable
+value class ValueClassWithByteString(@ByteString val x: ByteArray)
+
+@JvmInline
+@Serializable
+value class ValueClassWithCustomByteString(@ByteString val x: CustomByteString)
+
+@JvmInline
+@Serializable
+value class ValueClassWithUnlabeledByteString(@ByteString val x: Inner) {
+ @JvmInline
+ @Serializable
+ value class Inner(val x: ByteArray)
+}
\ No newline at end of file
diff --git a/formats/hocon/api/kotlinx-serialization-hocon.api b/formats/hocon/api/kotlinx-serialization-hocon.api
index a29292d..4afe9d3 100644
--- a/formats/hocon/api/kotlinx-serialization-hocon.api
+++ b/formats/hocon/api/kotlinx-serialization-hocon.api
@@ -22,8 +22,34 @@
public final fun setUseConfigNamingConvention (Z)V
}
+public abstract interface class kotlinx/serialization/hocon/HoconDecoder {
+ public abstract fun decodeConfigValue (Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+}
+
+public abstract interface class kotlinx/serialization/hocon/HoconEncoder {
+ public abstract fun encodeConfigValue (Lcom/typesafe/config/ConfigValue;)V
+}
+
public final class kotlinx/serialization/hocon/HoconKt {
public static final fun Hocon (Lkotlinx/serialization/hocon/Hocon;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/hocon/Hocon;
public static synthetic fun Hocon$default (Lkotlinx/serialization/hocon/Hocon;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/serialization/hocon/Hocon;
}
+public final class kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer : kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer;
+ public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/typesafe/config/ConfigMemorySize;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/typesafe/config/ConfigMemorySize;)V
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+}
+
+public final class kotlinx/serialization/hocon/serializers/JavaDurationSerializer : kotlinx/serialization/KSerializer {
+ public static final field INSTANCE Lkotlinx/serialization/hocon/serializers/JavaDurationSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/time/Duration;
+ public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/time/Duration;)V
+}
+
diff --git a/formats/hocon/build.gradle b/formats/hocon/build.gradle
index 0da4b08..c5fae36 100644
--- a/formats/hocon/build.gradle
+++ b/formats/hocon/build.gradle
@@ -1,3 +1,6 @@
+import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -12,6 +15,15 @@
}
}
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ if (rootProject.ext.kotlin_lv_override != null) {
+ languageVersion = rootProject.ext.kotlin_lv_override
+ freeCompilerArgs += "-Xsuppress-version-warnings"
+ }
+ }
+}
+
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
@@ -19,8 +31,9 @@
dependencies {
- implementation project(':kotlinx-serialization-core')
+ api project(':kotlinx-serialization-core')
api 'org.jetbrains.kotlin:kotlin-stdlib'
+ api 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
api 'com.typesafe:config:1.4.1'
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 e872835..163e562 100644
--- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt
@@ -6,11 +6,16 @@
import com.typesafe.config.*
import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE
+import kotlinx.serialization.hocon.internal.SuppressAnimalSniffer
+import kotlinx.serialization.hocon.internal.*
+import kotlinx.serialization.hocon.serializers.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.*
+import kotlin.time.*
/**
* Allows [deserialization][decodeFromConfig]
@@ -19,6 +24,24 @@
* [Config] object represents "Human-Optimized Config Object Notation" —
* [HOCON][https://github.com/lightbend/config#using-hocon-the-json-superset].
*
+ * [Duration] objects are encoded/decoded using "HOCON duration format" -
+ * [Duration format][https://github.com/lightbend/config/blob/main/HOCON.md#duration-format]
+ * [Duration] objects encoded using time unit short names: d, h, m, s, ms, us, ns.
+ * Encoding use the largest time unit.
+ * Example:
+ * 120.seconds -> 2 m
+ * 121.seconds -> 121 s
+ * 120.minutes -> 2 h
+ * 122.minutes -> 122 m
+ * 24.hours -> 1 d
+ * All restrictions on the maximum and minimum duration are specified in [Duration].
+ *
+ * It is also possible to encode and decode [java.time.Duration] and [com.typesafe.config.ConfigMemorySize]
+ * with provided serializers: [JavaDurationSerializer] and [ConfigMemorySizeSerializer].
+ * Because these types are not @[Serializable] by default,
+ * one has to apply these serializers manually — either via @Serializable(with=...) / @file:UseSerializers
+ * or using [Contextual] and [SerializersModule] mechanisms.
+ *
* @param [useConfigNamingConvention] switches naming resolution to config naming convention (hyphen separated).
* @param serializersModule A [SerializersModule] which should contain registered serializers
* for [Contextual] and [Polymorphic] serialization, if you have any.
@@ -62,9 +85,9 @@
* The default instance of Hocon parser.
*/
@ExperimentalSerializationApi
- public companion object Default : Hocon(false, false, false, "type", EmptySerializersModule)
+ public companion object Default : Hocon(false, false, false, "type", EmptySerializersModule())
- private abstract inner class ConfigConverter<T> : TaggedDecoder<T>() {
+ private abstract inner class ConfigConverter<T> : TaggedDecoder<T>(), HoconDecoder {
override val serializersModule: SerializersModule
get() = this@Hocon.serializersModule
@@ -86,6 +109,14 @@
private fun getTaggedNumber(tag: T) = validateAndCast<Number>(tag)
+ @Suppress("UNCHECKED_CAST")
+ protected fun <E> decodeDuration(tag: T): E =
+ getValueFromTaggedConfig(tag, ::decodeDurationImpl) as E
+
+ @SuppressAnimalSniffer
+ private fun decodeDurationImpl(conf: Config, path: String): Duration =
+ conf.decodeJavaDuration(path).toKotlinDuration()
+
override fun decodeTaggedString(tag: T) = validateAndCast<String>(tag)
override fun decodeTaggedBoolean(tag: T) = validateAndCast<Boolean>(tag)
@@ -110,9 +141,13 @@
val s = validateAndCast<String>(tag)
return enumDescriptor.getElementIndexOrThrow(s)
}
+
+ override fun <E> decodeConfigValue(extractValueAtPath: (Config, String) -> E): E =
+ getValueFromTaggedConfig(currentTag, extractValueAtPath)
+
}
- 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 {
@@ -128,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
@@ -138,19 +175,21 @@
}
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
- if (deserializer !is AbstractPolymorphicSerializer<*> || useArrayPolymorphism) {
- return deserializer.deserialize(this)
+ return when {
+ deserializer.descriptor.isDuration -> decodeDuration(currentTag)
+ deserializer !is AbstractPolymorphicSerializer<*> || useArrayPolymorphism -> deserializer.deserialize(this)
+ else -> {
+ val config = if (currentTagOrNull != null) conf.getConfig(currentTag) else conf
+
+ val reader = ConfigReader(config)
+ val type = reader.decodeTaggedString(classDiscriminator)
+ val actualSerializer = deserializer.findPolymorphicSerializerOrNull(reader, type)
+ ?: throw SerializerNotFoundException(type)
+
+ @Suppress("UNCHECKED_CAST")
+ (actualSerializer as DeserializationStrategy<T>).deserialize(reader)
+ }
}
-
- val config = if (currentTagOrNull != null) conf.getConfig(currentTag) else conf
-
- val reader = ConfigReader(config)
- val type = reader.decodeTaggedString(classDiscriminator)
- val actualSerializer = deserializer.findPolymorphicSerializerOrNull(reader, type)
- ?: throw SerializerNotFoundException(type)
-
- @Suppress("UNCHECKED_CAST")
- return (actualSerializer as DeserializationStrategy<T>).deserialize(reader)
}
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
@@ -171,11 +210,38 @@
}
}
- private inner class ListConfigReader(private val list: ConfigList) : ConfigConverter<Int>() {
+ 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
+
+ override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when {
+ deserializer.descriptor.isDuration -> decodeDuration(ind)
+ else -> super.decodeSerializableValue(deserializer)
+ }
+
+ 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)
@@ -209,8 +275,14 @@
private val indexSize = values.size * 2
+ override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T = when {
+ deserializer.descriptor.isDuration -> decodeDuration(ind)
+ else -> super.decodeSerializableValue(deserializer)
+ }
+
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/main/kotlin/kotlinx/serialization/hocon/HoconDecoder.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconDecoder.kt
new file mode 100644
index 0000000..a6006ff
--- /dev/null
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconDecoder.kt
@@ -0,0 +1,47 @@
+package kotlinx.serialization.hocon
+
+import com.typesafe.config.Config
+import kotlinx.serialization.ExperimentalSerializationApi
+
+/**
+ * Decoder used by Hocon during deserialization.
+ * This interface allows to call methods from the Lightbend/config library on the [Config] object to intercept default deserialization process.
+ *
+ * Usage example (nested config serialization):
+ * ```
+ * @Serializable
+ * data class Example(
+ * @Serializable(NestedConfigSerializer::class)
+ * val d: Config
+ * )
+ * object NestedConfigSerializer : KSerializer<Config> {
+ * override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("package.Config", PrimitiveKind.STRING)
+ *
+ * override fun deserialize(decoder: Decoder): Config =
+ * if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.getConfig(path) }
+ * else throw SerializationException("This class can be decoded only by Hocon format")
+ *
+ * override fun serialize(encoder: Encoder, value: Config) {
+ * if (encoder is AbstractHoconEncoder) encoder.encodeConfigValue(value.root())
+ * else throw SerializationException("This class can be encoded only by Hocon format")
+ * }
+ * }
+ *
+ * val nestedConfig = ConfigFactory.parseString("nested { value = \"test\" }")
+ * val globalConfig = Hocon.encodeToConfig(Example(nestedConfig)) // d: { nested: { value = "test" } }
+ * val newNestedConfig = Hocon.decodeFromConfig(Example.serializer(), globalConfig)
+ * ```
+ */
+@ExperimentalSerializationApi
+sealed interface HoconDecoder {
+
+ /**
+ * Decodes the value at the current path from the input.
+ * Allows to call methods on a [Config] instance.
+ *
+ * @param E type of value
+ * @param extractValueAtPath lambda for extracting value, where conf - original config object, path - current path expression being decoded.
+ * @return result of lambda execution
+ */
+ fun <E> decodeConfigValue(extractValueAtPath: (conf: Config, path: String) -> E): E
+}
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
index e753319..750b544 100644
--- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt
@@ -1,140 +1,43 @@
-/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
package kotlinx.serialization.hocon
-import com.typesafe.config.*
-import kotlinx.serialization.*
-import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.encoding.*
-import kotlinx.serialization.internal.*
-import kotlinx.serialization.modules.*
+import com.typesafe.config.ConfigValue
+import kotlinx.serialization.ExperimentalSerializationApi
+/**
+ * Encoder used by Hocon during serialization.
+ * This interface allows intercepting serialization process and insertion of arbitrary [ConfigValue] into the output.
+ *
+ * Usage example (nested config serialization):
+ * ```
+ * @Serializable
+ * data class Example(
+ * @Serializable(NestedConfigSerializer::class)
+ * val d: Config
+ * )
+ * object NestedConfigSerializer : KSerializer<Config> {
+ * override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("package.Config", PrimitiveKind.STRING)
+ *
+ * override fun deserialize(decoder: Decoder): Config =
+ * if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.getConfig(path) }
+ * else throw SerializationException("This class can be decoded only by Hocon format")
+ *
+ * override fun serialize(encoder: Encoder, value: Config) {
+ * if (encoder is HoconEncoder) encoder.encodeConfigValue(value.root())
+ * else throw SerializationException("This class can be encoded only by Hocon format")
+ * }
+ * }
+ * val nestedConfig = ConfigFactory.parseString("nested { value = \"test\" }")
+ * val globalConfig = Hocon.encodeToConfig(Example(nestedConfig)) // d: { nested: { value = "test" } }
+ * val newNestedConfig = Hocon.decodeFromConfig(Example.serializer(), globalConfig)
+ * ```
+ */
@ExperimentalSerializationApi
-internal abstract class AbstractHoconEncoder(
- private val hocon: Hocon,
- private val valueConsumer: (ConfigValue) -> Unit,
-) : NamedValueEncoder() {
+sealed interface HoconEncoder {
- override val serializersModule: SerializersModule
- get() = hocon.serializersModule
-
- private var writeDiscriminator: Boolean = false
-
- override fun elementName(descriptor: SerialDescriptor, index: Int): String {
- return descriptor.getConventionElementName(index, hocon.useConfigNamingConvention)
- }
-
- override fun composeName(parentName: String, childName: String): String = childName
-
- protected abstract fun encodeTaggedConfigValue(tag: String, value: ConfigValue)
- protected abstract fun getCurrent(): ConfigValue
-
- override fun encodeTaggedValue(tag: String, value: Any) = encodeTaggedConfigValue(tag, configValueOf(value))
- override fun encodeTaggedNull(tag: String) = encodeTaggedConfigValue(tag, configValueOf(null))
- override fun encodeTaggedChar(tag: String, value: Char) = encodeTaggedString(tag, value.toString())
-
- override fun encodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor, ordinal: Int) {
- encodeTaggedString(tag, enumDescriptor.getElementName(ordinal))
- }
-
- override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = hocon.encodeDefaults
-
- override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
- if (serializer !is AbstractPolymorphicSerializer<*> || hocon.useArrayPolymorphism) {
- serializer.serialize(this, value)
- return
- }
-
- @Suppress("UNCHECKED_CAST")
- val casted = serializer as AbstractPolymorphicSerializer<Any>
- val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
- writeDiscriminator = true
-
- actualSerializer.serialize(this, value)
- }
-
- override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
- val consumer =
- if (currentTagOrNull == null) valueConsumer
- else { value -> encodeTaggedConfigValue(currentTag, value) }
- val kind = descriptor.hoconKind(hocon.useArrayPolymorphism)
-
- return when {
- kind.listLike -> HoconConfigListEncoder(hocon, consumer)
- kind.objLike -> HoconConfigEncoder(hocon, consumer)
- kind == StructureKind.MAP -> HoconConfigMapEncoder(hocon, consumer)
- else -> this
- }.also { encoder ->
- if (writeDiscriminator) {
- encoder.encodeTaggedString(hocon.classDiscriminator, descriptor.serialName)
- writeDiscriminator = false
- }
- }
- }
-
- override fun endEncode(descriptor: SerialDescriptor) {
- valueConsumer(getCurrent())
- }
-
- private fun configValueOf(value: Any?) = ConfigValueFactory.fromAnyRef(value)
-}
-
-@ExperimentalSerializationApi
-internal class HoconConfigEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
- AbstractHoconEncoder(hocon, configConsumer) {
-
- private val configMap = mutableMapOf<String, ConfigValue>()
-
- override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
- configMap[tag] = value
- }
-
- override fun getCurrent(): ConfigValue = ConfigValueFactory.fromMap(configMap)
-}
-
-@ExperimentalSerializationApi
-internal class HoconConfigListEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
- AbstractHoconEncoder(hocon, configConsumer) {
-
- private val values = mutableListOf<ConfigValue>()
-
- override fun elementName(descriptor: SerialDescriptor, index: Int): String = index.toString()
-
- override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
- values.add(tag.toInt(), value)
- }
-
- override fun getCurrent(): ConfigValue = ConfigValueFactory.fromIterable(values)
-}
-
-@ExperimentalSerializationApi
-internal class HoconConfigMapEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
- AbstractHoconEncoder(hocon, configConsumer) {
-
- private val configMap = mutableMapOf<String, ConfigValue>()
-
- private lateinit var key: String
- private var isKey: Boolean = true
-
- override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
- if (isKey) {
- key = when (value.valueType()) {
- ConfigValueType.OBJECT, ConfigValueType.LIST -> throw InvalidKeyKindException(value)
- else -> value.unwrappedNullable().toString()
- }
- isKey = false
- } else {
- configMap[key] = value
- isKey = true
- }
- }
-
- override fun getCurrent(): ConfigValue = ConfigValueFactory.fromMap(configMap)
-
- // Without cast to `Any?` Kotlin will assume unwrapped value as non-nullable by default
- // and will call `Any.toString()` instead of extension-function `Any?.toString()`.
- // We can't cast value in place using `(value.unwrapped() as Any?).toString()` because of warning "No cast needed".
- private fun ConfigValue.unwrappedNullable(): Any? = unwrapped()
+ /**
+ * Appends the given [ConfigValue] element to the current output.
+ *
+ * @param value to insert
+ */
+ fun encodeConfigValue(value: ConfigValue)
}
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoders.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoders.kt
new file mode 100644
index 0000000..f8c113f
--- /dev/null
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoders.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.hocon
+
+import com.typesafe.config.*
+import kotlin.time.*
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.hocon.internal.*
+import kotlinx.serialization.internal.*
+import kotlinx.serialization.modules.*
+
+@ExperimentalSerializationApi
+internal abstract class AbstractHoconEncoder(
+ private val hocon: Hocon,
+ private val valueConsumer: (ConfigValue) -> Unit,
+) : NamedValueEncoder(), HoconEncoder {
+
+ override val serializersModule: SerializersModule
+ get() = hocon.serializersModule
+
+ private var writeDiscriminator: Boolean = false
+
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
+ return descriptor.getConventionElementName(index, hocon.useConfigNamingConvention)
+ }
+
+ override fun composeName(parentName: String, childName: String): String = childName
+
+ protected abstract fun encodeTaggedConfigValue(tag: String, value: ConfigValue)
+ protected abstract fun getCurrent(): ConfigValue
+
+ override fun encodeTaggedValue(tag: String, value: Any) = encodeTaggedConfigValue(tag, configValueOf(value))
+ override fun encodeTaggedNull(tag: String) = encodeTaggedConfigValue(tag, configValueOf(null))
+ override fun encodeTaggedChar(tag: String, value: Char) = encodeTaggedString(tag, value.toString())
+
+ override fun encodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor, ordinal: Int) {
+ encodeTaggedString(tag, enumDescriptor.getElementName(ordinal))
+ }
+
+ override fun shouldEncodeElementDefault(descriptor: SerialDescriptor, index: Int): Boolean = hocon.encodeDefaults
+
+ override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
+ when {
+ serializer.descriptor.isDuration -> encodeString(encodeDuration(value as Duration))
+ serializer !is AbstractPolymorphicSerializer<*> || hocon.useArrayPolymorphism -> serializer.serialize(this, value)
+ else -> {
+ @Suppress("UNCHECKED_CAST")
+ val casted = serializer as AbstractPolymorphicSerializer<Any>
+ val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
+ writeDiscriminator = true
+
+ actualSerializer.serialize(this, value)
+ }
+ }
+ }
+
+ override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
+ val consumer =
+ if (currentTagOrNull == null) valueConsumer
+ else { value -> encodeTaggedConfigValue(currentTag, value) }
+ val kind = descriptor.hoconKind(hocon.useArrayPolymorphism)
+
+ return when {
+ kind.listLike -> HoconConfigListEncoder(hocon, consumer)
+ kind.objLike -> HoconConfigEncoder(hocon, consumer)
+ kind == StructureKind.MAP -> HoconConfigMapEncoder(hocon, consumer)
+ else -> this
+ }.also { encoder ->
+ if (writeDiscriminator) {
+ encoder.encodeTaggedString(hocon.classDiscriminator, descriptor.serialName)
+ writeDiscriminator = false
+ }
+ }
+ }
+
+ override fun endEncode(descriptor: SerialDescriptor) {
+ valueConsumer(getCurrent())
+ }
+
+ override fun encodeConfigValue(value: ConfigValue) {
+ encodeTaggedConfigValue(currentTag, value)
+ }
+
+ private fun configValueOf(value: Any?) = ConfigValueFactory.fromAnyRef(value)
+}
+
+@ExperimentalSerializationApi
+internal class HoconConfigEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
+ AbstractHoconEncoder(hocon, configConsumer) {
+
+ private val configMap = mutableMapOf<String, ConfigValue>()
+
+ override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
+ configMap[tag] = value
+ }
+
+ override fun getCurrent(): ConfigValue = ConfigValueFactory.fromMap(configMap)
+}
+
+@ExperimentalSerializationApi
+internal class HoconConfigListEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
+ AbstractHoconEncoder(hocon, configConsumer) {
+
+ private val values = mutableListOf<ConfigValue>()
+
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String = index.toString()
+
+ override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
+ values.add(tag.toInt(), value)
+ }
+
+ override fun getCurrent(): ConfigValue = ConfigValueFactory.fromIterable(values)
+}
+
+@ExperimentalSerializationApi
+internal class HoconConfigMapEncoder(hocon: Hocon, configConsumer: (ConfigValue) -> Unit) :
+ AbstractHoconEncoder(hocon, configConsumer) {
+
+ private val configMap = mutableMapOf<String, ConfigValue>()
+
+ private lateinit var key: String
+ private var isKey: Boolean = true
+
+ override fun encodeTaggedConfigValue(tag: String, value: ConfigValue) {
+ if (isKey) {
+ key = when (value.valueType()) {
+ ConfigValueType.OBJECT, ConfigValueType.LIST -> throw InvalidKeyKindException(value)
+ else -> value.unwrappedNullable().toString()
+ }
+ isKey = false
+ } else {
+ configMap[key] = value
+ isKey = true
+ }
+ }
+
+ override fun getCurrent(): ConfigValue = ConfigValueFactory.fromMap(configMap)
+
+ // Without cast to `Any?` Kotlin will assume unwrapped value as non-nullable by default
+ // and will call `Any.toString()` instead of extension-function `Any?.toString()`.
+ // We can't cast value in place using `(value.unwrapped() as Any?).toString()` because of warning "No cast needed".
+ private fun ConfigValue.unwrappedNullable(): Any? = unwrapped()
+}
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconExceptions.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconExceptions.kt
index 52e711a..9e103f0 100644
--- a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconExceptions.kt
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconExceptions.kt
@@ -20,3 +20,6 @@
"Value of type '${value.valueType()}' can't be used in HOCON as a key in the map. " +
"It should have either primitive or enum kind."
)
+
+internal fun throwUnsupportedFormatException(serializerName: String): Nothing =
+ throw SerializationException("$serializerName is supported only in Hocon format.")
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/HoconDuration.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/HoconDuration.kt
new file mode 100644
index 0000000..5fcf443
--- /dev/null
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/HoconDuration.kt
@@ -0,0 +1,62 @@
+package kotlinx.serialization.hocon.internal
+
+import com.typesafe.config.*
+import java.time.Duration as JDuration
+import kotlin.time.Duration
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+
+/**
+ * Encode [Duration] objects using time unit short names: d, h, m, s, ms, us, ns.
+ * Example:
+ * 120.seconds -> 2 m;
+ * 121.seconds -> 121 s;
+ * 120.minutes -> 2 h;
+ * 122.minutes -> 122 m;
+ * 24.hours -> 1 d.
+ * Encoding uses the largest time unit.
+ * All restrictions on the maximum and minimum duration are specified in [Duration].
+ * @return encoded value
+ */
+internal fun encodeDuration(value: Duration): String = value.toComponents { seconds, nanoseconds ->
+ when {
+ nanoseconds == 0 -> {
+ if (seconds % 60 == 0L) { // minutes
+ if (seconds % 3600 == 0L) { // hours
+ if (seconds % 86400 == 0L) { // days
+ "${seconds / 86400} d"
+ } else {
+ "${seconds / 3600} h"
+ }
+ } else {
+ "${seconds / 60} m"
+ }
+ } else {
+ "$seconds s"
+ }
+ }
+ nanoseconds % 1_000_000 == 0 -> "${seconds * 1_000 + nanoseconds / 1_000_000} ms"
+ nanoseconds % 1_000 == 0 -> "${seconds * 1_000_000 + nanoseconds / 1_000} us"
+ else -> "${value.inWholeNanoseconds} ns"
+ }
+}
+
+/**
+ * Decode [JDuration] from [Config].
+ * See https://github.com/lightbend/config/blob/main/HOCON.md#duration-format
+ *
+ * @param path in config
+ */
+@SuppressAnimalSniffer
+internal fun Config.decodeJavaDuration(path: String): JDuration = try {
+ getDuration(path)
+} catch (e: ConfigException) {
+ throw SerializationException("Value at $path cannot be read as Duration because it is not a valid HOCON duration value", e)
+}
+
+/**
+ * Returns `true` if this descriptor is equals to descriptor in [kotlinx.serialization.internal.DurationSerializer].
+ */
+internal val SerialDescriptor.isDuration: Boolean
+ get() = this == Duration.serializer().descriptor
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/SuppressAnimalSniffer.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/SuppressAnimalSniffer.kt
new file mode 100644
index 0000000..fa348bb
--- /dev/null
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/SuppressAnimalSniffer.kt
@@ -0,0 +1,10 @@
+package kotlinx.serialization.hocon.internal
+
+/**
+ * Suppresses Animal Sniffer plugin errors for certain methods.
+ * Such methods include references to Java 8 methods that are not
+ * available in Android API, but can be desugared by R8.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+internal annotation class SuppressAnimalSniffer
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer.kt
new file mode 100644
index 0000000..804a3a6
--- /dev/null
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer.kt
@@ -0,0 +1,70 @@
+package kotlinx.serialization.hocon.serializers
+
+import com.typesafe.config.*
+import java.math.BigInteger
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.hocon.*
+
+/**
+ * Serializer for [ConfigMemorySize].
+ * All possible Hocon size formats [https://github.com/lightbend/config/blob/main/HOCON.md#size-in-bytes-format] are accepted for decoding.
+ * During encoding, the serializer emits values using powers of two: byte, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB.
+ * Encoding uses the largest possible integer value.
+ * Example:
+ * 1024 byte -> 1 KiB;
+ * 1024 KiB -> 1 MiB;
+ * 1025 KiB -> 1025 KiB.
+ * Usage example:
+ * ```
+ * @Serializable
+ * data class ConfigMemory(
+ * @Serializable(ConfigMemorySizeSerializer::class)
+ * val size: ConfigMemorySize
+ * )
+ * val config = ConfigFactory.parseString("size = 1 MiB")
+ * val configMemory = Hocon.decodeFromConfig(ConfigMemory.serializer(), config)
+ * val newConfig = Hocon.encodeToConfig(ConfigMemory.serializer(), configMemory)
+ * ```
+ */
+@ExperimentalSerializationApi
+object ConfigMemorySizeSerializer : KSerializer<ConfigMemorySize> {
+
+ // For powers of two.
+ private val memoryUnitFormats = listOf("byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB")
+
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("hocon.com.typesafe.config.ConfigMemorySize", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): ConfigMemorySize =
+ if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.decodeMemorySize(path) }
+ else throwUnsupportedFormatException("ConfigMemorySizeSerializer")
+
+ override fun serialize(encoder: Encoder, value: ConfigMemorySize) {
+ if (encoder is HoconEncoder) {
+ // We determine that it is divisible by 1024 (2^10).
+ // And if it is divisible, then the number itself is shifted to the right by 10.
+ // And so on until we find one that is no longer divisible by 1024.
+ // ((n & ((1 << m) - 1)) == 0)
+ val andVal = BigInteger.valueOf(1023) // ((2^10) - 1) = 0x3ff = 1023
+ var bytes = value.toBytesBigInteger()
+ var unitIndex = 0
+ while (bytes.and(andVal) == BigInteger.ZERO) { // n & 0x3ff == 0
+ if (unitIndex < memoryUnitFormats.lastIndex) {
+ bytes = bytes.shiftRight(10)
+ unitIndex++
+ } else break
+ }
+ encoder.encodeString("$bytes ${memoryUnitFormats[unitIndex]}")
+ } else {
+ throwUnsupportedFormatException("ConfigMemorySizeSerializer")
+ }
+ }
+
+ private fun Config.decodeMemorySize(path: String): ConfigMemorySize = try {
+ getMemorySize(path)
+ } catch (e: ConfigException) {
+ throw SerializationException("Value at $path cannot be read as ConfigMemorySize because it is not a valid HOCON Size value", e)
+ }
+}
diff --git a/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/JavaDurationSerializer.kt b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/JavaDurationSerializer.kt
new file mode 100644
index 0000000..c5075e3
--- /dev/null
+++ b/formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/JavaDurationSerializer.kt
@@ -0,0 +1,52 @@
+package kotlinx.serialization.hocon.serializers
+
+import java.time.Duration as JDuration
+import kotlin.time.*
+import kotlinx.serialization.*
+import kotlinx.serialization.ExperimentalSerializationApi
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.hocon.*
+import kotlinx.serialization.hocon.internal.*
+
+/**
+ * Serializer for [java.time.Duration].
+ * All possible Hocon duration formats [https://github.com/lightbend/config/blob/main/HOCON.md#duration-format] are accepted for decoding.
+ * During encoding, the serializer emits values using time unit short names: d, h, m, s, ms, us, ns.
+ * The largest integer time unit is encoded.
+ * Example:
+ * 120.seconds -> 2 m;
+ * 121.seconds -> 121 s;
+ * 120.minutes -> 2 h;
+ * 122.minutes -> 122 m;
+ * 24.hours -> 1 d.
+ * When encoding, there is a conversion to [kotlin.time.Duration].
+ * All restrictions on the maximum and minimum duration are specified in [kotlin.time.Duration].
+ * Usage example:
+ * ```
+ * @Serializable
+ * data class ExampleDuration(
+ * @Serializable(JDurationSerializer::class)
+ * val duration: java.time.Duration
+ * )
+ * val config = ConfigFactory.parseString("duration = 1 day")
+ * val exampleDuration = Hocon.decodeFromConfig(ExampleDuration.serializer(), config)
+ * val newConfig = Hocon.encodeToConfig(ExampleDuration.serializer(), exampleDuration)
+ * ```
+ */
+@ExperimentalSerializationApi
+@SuppressAnimalSniffer
+object JavaDurationSerializer : KSerializer<JDuration> {
+
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor("hocon.java.time.Duration", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): JDuration =
+ if (decoder is HoconDecoder) decoder.decodeConfigValue { conf, path -> conf.decodeJavaDuration(path) }
+ else throwUnsupportedFormatException("JavaDurationSerializer")
+
+ override fun serialize(encoder: Encoder, value: JDuration) {
+ if (encoder is HoconEncoder) encoder.encodeString(encodeDuration(value.toKotlinDuration()))
+ else throwUnsupportedFormatException("JavaDurationSerializer")
+ }
+}
diff --git a/formats/hocon/src/mainModule/kotlin/module-info.java b/formats/hocon/src/mainModule/kotlin/module-info.java
index b828065..a21583c 100644
--- a/formats/hocon/src/mainModule/kotlin/module-info.java
+++ b/formats/hocon/src/mainModule/kotlin/module-info.java
@@ -1,6 +1,7 @@
module kotlinx.serialization.hocon {
requires transitive kotlin.stdlib;
requires transitive kotlinx.serialization.core;
+ requires transitive kotlin.stdlib.jdk8;
requires transitive typesafe.config;
exports kotlinx.serialization.hocon;
diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconDurationTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconDurationTest.kt
new file mode 100644
index 0000000..32fc185
--- /dev/null
+++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconDurationTest.kt
@@ -0,0 +1,196 @@
+package kotlinx.serialization.hocon
+
+import kotlin.test.*
+import kotlin.time.*
+import kotlin.time.Duration.Companion.INFINITE
+import kotlin.time.Duration.Companion.days
+import kotlin.time.Duration.Companion.hours
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.minutes
+import kotlin.time.Duration.Companion.nanoseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.serialization.*
+import org.junit.Assert.*
+import org.junit.Test
+
+class HoconDurationTest {
+
+ @Serializable
+ data class Simple(val d: Duration)
+
+ @Serializable
+ data class Nullable(val d: Duration?)
+
+ @Serializable
+ data class ConfigList(val ld: List<Duration>)
+
+ @Serializable
+ data class ConfigMap(val mp: Map<String, Duration>)
+
+ @Serializable
+ data class ConfigMapDurationKey(val mp: Map<Duration, Duration>)
+
+ @Serializable
+ data class Complex(
+ val i: Int,
+ val s: Simple,
+ val n: Nullable,
+ val l: List<Simple>,
+ val ln: List<Nullable>,
+ val f: Boolean,
+ val ld: List<Duration>,
+ val mp: Map<String, Duration>,
+ val mpp: Map<Duration, Duration>
+ )
+
+ @Test
+ fun testSerializeDuration() {
+ Hocon.encodeToConfig(Simple(10.minutes)).assertContains("d = 10 m")
+ Hocon.encodeToConfig(Simple(120.seconds)).assertContains("d = 2 m")
+
+ Hocon.encodeToConfig(Simple(1.hours)).assertContains("d = 1 h")
+ Hocon.encodeToConfig(Simple(120.minutes)).assertContains("d = 2 h")
+ Hocon.encodeToConfig(Simple((3600 * 3).seconds)).assertContains("d = 3 h")
+
+ Hocon.encodeToConfig(Simple(3.days)).assertContains("d = 3 d")
+ Hocon.encodeToConfig(Simple(24.hours)).assertContains("d = 1 d")
+ Hocon.encodeToConfig(Simple((1440 * 2).minutes)).assertContains("d = 2 d")
+ Hocon.encodeToConfig(Simple((86400 * 4).seconds)).assertContains("d = 4 d")
+
+ Hocon.encodeToConfig(Simple(1.seconds)).assertContains("d = 1 s")
+ Hocon.encodeToConfig(Simple(2.minutes + 1.seconds)).assertContains("d = 121 s")
+ Hocon.encodeToConfig(Simple(1.hours + 1.seconds)).assertContains("d = 3601 s")
+ Hocon.encodeToConfig(Simple(1.days + 5.seconds)).assertContains("d = 86405 s")
+
+ Hocon.encodeToConfig(Simple(9.nanoseconds)).assertContains("d = 9 ns")
+ Hocon.encodeToConfig(Simple(1_000_000.nanoseconds + 5.seconds)).assertContains("d = 5001 ms")
+ Hocon.encodeToConfig(Simple(1_000.nanoseconds + 9.seconds)).assertContains("d = 9000001 us")
+ Hocon.encodeToConfig(Simple(1_000_005.nanoseconds + 5.seconds)).assertContains("d = 5001000005 ns")
+ Hocon.encodeToConfig(Simple(1_002.nanoseconds + 9.seconds)).assertContains("d = 9000001002 ns")
+ Hocon.encodeToConfig(Simple(1_000_000_001.nanoseconds)).assertContains("d = 1000000001 ns")
+
+ // for INFINITE nanoseconds=0
+ Hocon.encodeToConfig(Simple(INFINITE)).assertContains("d = ${Long.MAX_VALUE} s")
+ Hocon.encodeToConfig(Simple(Long.MAX_VALUE.days)).assertContains("d = ${Long.MAX_VALUE} s")
+
+ Hocon.encodeToConfig(Simple((-10).days)).assertContains("d = -10 d")
+ }
+
+ @Test
+ fun testSerializeNullableDuration() {
+ Hocon.encodeToConfig(Nullable(null)).assertContains("d = null")
+ Hocon.encodeToConfig(Nullable(6.seconds)).assertContains("d = 6 s")
+ }
+
+ @Test
+ fun testSerializeListOfDuration() {
+ Hocon.encodeToConfig(ConfigList(listOf(1.days, 1.minutes, 5.nanoseconds))).assertContains("ld: [ 1 d, 1 m, 5 ns ]")
+ }
+
+ @Test
+ fun testSerializeMapOfDuration() {
+ Hocon.encodeToConfig(ConfigMap(mapOf("day" to 2.days, "hour" to 5.hours, "minute" to 3.minutes)))
+ .assertContains("mp: { day = 2 d, hour = 5 h, minute = 3 m }")
+ Hocon.encodeToConfig(ConfigMapDurationKey(mapOf(1.hours to 3600.seconds)))
+ .assertContains("mp: { 1 h = 1 h }")
+ }
+
+ @Test
+ fun testSerializeComplexDuration() {
+ val obj = Complex(
+ i = 6,
+ s = Simple(5.minutes),
+ n = Nullable(null),
+ l = listOf(Simple(1.minutes), Simple(2.seconds)),
+ ln = listOf(Nullable(null), Nullable(6.hours)),
+ f = true,
+ ld = listOf(1.days, 1.minutes, 5.nanoseconds),
+ mp = mapOf("day" to 2.days, "hour" to 5.hours, "minute" to 3.minutes),
+ mpp = mapOf(1.hours to 3600.seconds)
+ )
+ Hocon.encodeToConfig(obj)
+ .assertContains("""
+ i = 6
+ s: { d = 5 m }
+ n: { d = null }
+ l: [ { d = 1 m }, { d = 2 s } ]
+ ln: [ { d = null }, { d = 6 h } ]
+ f = true
+ ld: [ 1 d, 1 m, 5 ns ]
+ mp: { day = 2 d, hour = 5 h, minute = 3 m }
+ mpp: { 1 h = 1 h }
+ """.trimIndent())
+ }
+
+ @Test
+ fun testDeserializeDuration() {
+ var obj = deserializeConfig("d = 10s", Simple.serializer())
+ assertEquals(10.seconds, obj.d)
+ obj = deserializeConfig("d = 10 hours", Simple.serializer())
+ assertEquals(10.hours, obj.d)
+ obj = deserializeConfig("d = 5 ms", Simple.serializer())
+ assertEquals(5.milliseconds, obj.d)
+ obj = deserializeConfig("d = -5 days", Simple.serializer())
+ assertEquals((-5).days, obj.d)
+ }
+
+ @Test
+ fun testDeserializeNullableDuration() {
+ var obj = deserializeConfig("d = null", Nullable.serializer())
+ assertNull(obj.d)
+
+ obj = deserializeConfig("d = 5 days", Nullable.serializer())
+ assertEquals(5.days, obj.d!!)
+ }
+
+ @Test
+ fun testDeserializeListOfDuration() {
+ val obj = deserializeConfig("ld: [ 1d, 1m, 5ns ]", ConfigList.serializer())
+ assertEquals(listOf(1.days, 1.minutes, 5.nanoseconds), obj.ld)
+ }
+
+ @Test
+ fun testDeserializeMapOfDuration() {
+ val obj = deserializeConfig("""
+ mp: { day = 2d, hour = 5 hours, minute = 3 minutes }
+ """.trimIndent(), ConfigMap.serializer())
+ assertEquals(mapOf("day" to 2.days, "hour" to 5.hours, "minute" to 3.minutes), obj.mp)
+
+ val objDurationKey = deserializeConfig("""
+ mp: { 1 hour = 3600s }
+ """.trimIndent(), ConfigMapDurationKey.serializer())
+ assertEquals(mapOf(1.hours to 3600.seconds), objDurationKey.mp)
+ }
+
+ @Test
+ fun testDeserializeComplexDuration() {
+ val obj = deserializeConfig("""
+ i = 6
+ s: { d = 5m }
+ n: { d = null }
+ l: [ { d = 1m }, { d = 2s } ]
+ ln: [ { d = null }, { d = 6h } ]
+ f = true
+ ld: [ 1d, 1m, 5ns ]
+ mp: { day = 2d, hour = 5 hours, minute = 3 minutes }
+ mpp: { 1 hour = 3600s }
+ """.trimIndent(), Complex.serializer())
+ assertEquals(5.minutes, obj.s.d)
+ assertNull(obj.n.d)
+ assertEquals(listOf(Simple(1.minutes), Simple(2.seconds)), obj.l)
+ assertEquals(listOf(Nullable(null), Nullable(6.hours)), obj.ln)
+ assertEquals(6, obj.i)
+ assertTrue(obj.f)
+ assertEquals(listOf(1.days, 1.minutes, 5.nanoseconds), obj.ld)
+ assertEquals(mapOf("day" to 2.days, "hour" to 5.hours, "minute" to 3.minutes), obj.mp)
+ assertEquals(mapOf(1.hours to 3600.seconds), obj.mpp)
+ }
+
+ @Test
+ fun testThrowsWhenNotTimeUnitHocon() {
+ val message = "Value at d cannot be read as Duration because it is not a valid HOCON duration value"
+ assertFailsWith<SerializationException>(message) {
+ deserializeConfig("d = 10 unknown", Simple.serializer())
+ }
+ }
+}
diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconJavaDurationTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconJavaDurationTest.kt
new file mode 100644
index 0000000..fda78d7
--- /dev/null
+++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconJavaDurationTest.kt
@@ -0,0 +1,177 @@
+@file:UseSerializers(JavaDurationSerializer::class)
+package kotlinx.serialization.hocon
+
+import java.time.Duration
+import java.time.Duration.*
+import kotlin.test.assertFailsWith
+import kotlinx.serialization.*
+import kotlinx.serialization.hocon.serializers.JavaDurationSerializer
+import org.junit.*
+import org.junit.Assert.*
+
+class HoconJavaDurationTest {
+
+ @Serializable
+ data class Simple(val d: Duration)
+
+ @Serializable
+ data class Nullable(val d: Duration?)
+
+ @Serializable
+ data class ConfigList(val ld: List<Duration>)
+
+ @Serializable
+ data class ConfigMap(val mp: Map<String, Duration>)
+
+ @Serializable
+ data class ConfigMapDurationKey(val mp: Map<Duration, Duration>)
+
+ @Serializable
+ data class Complex(
+ val i: Int,
+ val s: Simple,
+ val n: Nullable,
+ val l: List<Simple>,
+ val ln: List<Nullable>,
+ val f: Boolean,
+ val ld: List<Duration>,
+ val mp: Map<String, Duration>,
+ val mpp: Map<Duration, Duration>
+ )
+
+ private fun testJavaDuration(simple: Simple, str: String) {
+ val res = Hocon.encodeToConfig(simple)
+ res.assertContains(str)
+ assertEquals(simple, Hocon.decodeFromConfig(Simple.serializer(), res))
+ }
+
+ @Test
+ fun testSerializeDuration() {
+ testJavaDuration(Simple(ofMinutes(10)), "d = 10 m")
+ testJavaDuration(Simple(ofSeconds(120)), "d = 2 m")
+ testJavaDuration(Simple(ofHours(1)), "d = 1 h")
+ testJavaDuration(Simple(ofMinutes(120)), "d = 2 h")
+ testJavaDuration(Simple(ofSeconds(3600 * 3)), "d = 3 h")
+ testJavaDuration(Simple(ofDays(3)), "d = 3 d")
+ testJavaDuration(Simple(ofHours(24)), "d = 1 d")
+ testJavaDuration(Simple(ofMinutes(1440 * 2)), "d = 2 d")
+ testJavaDuration(Simple(ofSeconds(86400 * 4)), "d = 4 d")
+ testJavaDuration(Simple(ofSeconds(1)), "d = 1 s")
+ testJavaDuration(Simple(ofMinutes(2).plusSeconds(1)), "d = 121 s")
+ testJavaDuration(Simple(ofHours(1).plusSeconds(1)), "d = 3601 s")
+ testJavaDuration(Simple(ofDays(1).plusSeconds(5)), "d = 86405 s")
+ testJavaDuration(Simple(ofNanos(9)), "d = 9 ns")
+ testJavaDuration(Simple(ofNanos(1_000_000).plusSeconds(5)), "d = 5001 ms")
+ testJavaDuration(Simple(ofNanos(1_000).plusSeconds(9)), "d = 9000001 us")
+ testJavaDuration(Simple(ofNanos(1_000_005).plusSeconds(5)), "d = 5001000005 ns")
+ testJavaDuration(Simple(ofNanos(1_002).plusSeconds(9)), "d = 9000001002 ns")
+ testJavaDuration(Simple(ofNanos(1_000_000_001)), "d = 1000000001 ns")
+ testJavaDuration(Simple(ofDays(-10)), "d = -10 d")
+ }
+
+ @Test
+ fun testSerializeNullableDuration() {
+ Hocon.encodeToConfig(Nullable(null)).assertContains("d = null")
+ Hocon.encodeToConfig(Nullable(ofSeconds(6))).assertContains("d = 6 s")
+ }
+
+ @Test
+ fun testSerializeListOfDuration() {
+ Hocon.encodeToConfig(ConfigList(listOf(ofDays(1), ofMinutes(1), ofNanos(5)))).assertContains("ld: [ 1 d, 1 m, 5 ns ]")
+ }
+
+ @Test
+ fun testSerializeMapOfDuration() {
+ Hocon.encodeToConfig(ConfigMap(mapOf("day" to ofDays(2), "hour" to ofHours(5), "minute" to ofMinutes(3))))
+ .assertContains("mp: { day = 2 d, hour = 5 h, minute = 3 m }")
+ Hocon.encodeToConfig(ConfigMapDurationKey(mapOf(ofHours(1) to ofSeconds(3600))))
+ .assertContains("mp: { 1 h = 1 h }")
+ }
+
+ @Test
+ fun testSerializeComplexDuration() {
+ val obj = Complex(
+ i = 6,
+ s = Simple(ofMinutes(5)),
+ n = Nullable(null),
+ l = listOf(Simple(ofMinutes(1)), Simple(ofSeconds(2))),
+ ln = listOf(Nullable(null), Nullable(ofHours(6))),
+ f = true,
+ ld = listOf(ofDays(1), ofMinutes(1), ofNanos(5)),
+ mp = mapOf("day" to ofDays(2), "hour" to ofHours(5), "minute" to ofMinutes(3)),
+ mpp = mapOf(ofHours(1) to ofSeconds(3600))
+ )
+ Hocon.encodeToConfig(obj)
+ .assertContains("""
+ i = 6
+ s: { d = 5 m }
+ n: { d = null }
+ l: [ { d = 1 m }, { d = 2 s } ]
+ ln: [ { d = null }, { d = 6 h } ]
+ f = true
+ ld: [ 1 d, 1 m, 5 ns ]
+ mp: { day = 2 d, hour = 5 h, minute = 3 m }
+ mpp: { 1 h = 1 h }
+ """.trimIndent())
+ }
+
+ @Test
+ fun testDeserializeNullableDuration() {
+ var obj = deserializeConfig("d = null", Nullable.serializer())
+ assertNull(obj.d)
+
+ obj = deserializeConfig("d = 5 days", Nullable.serializer())
+ assertEquals(ofDays(5), obj.d!!)
+ }
+
+ @Test
+ fun testDeserializeListOfDuration() {
+ val obj = deserializeConfig("ld: [ 1d, 1m, 5ns ]", ConfigList.serializer())
+ assertEquals(listOf(ofDays(1), ofMinutes(1), ofNanos(5)), obj.ld)
+ }
+
+ @Test
+ fun testDeserializeMapOfDuration() {
+ val obj = deserializeConfig("""
+ mp: { day = 2d, hour = 5 hours, minute = 3 minutes }
+ """.trimIndent(), ConfigMap.serializer())
+ assertEquals(mapOf("day" to ofDays(2), "hour" to ofHours(5), "minute" to ofMinutes(3)), obj.mp)
+
+ val objDurationKey = deserializeConfig("""
+ mp: { 1 hour = 3600s }
+ """.trimIndent(), ConfigMapDurationKey.serializer())
+ assertEquals(mapOf(ofHours(1) to ofSeconds(3600)), objDurationKey.mp)
+ }
+
+ @Test
+ fun testDeserializeComplexDuration() {
+ val obj = deserializeConfig("""
+ i = 6
+ s: { d = 5m }
+ n: { d = null }
+ l: [ { d = 1m }, { d = 2s } ]
+ ln: [ { d = null }, { d = 6h } ]
+ f = true
+ ld: [ 1d, 1m, 5ns ]
+ mp: { day = 2d, hour = 5 hours, minute = 3 minutes }
+ mpp: { 1 hour = 3600s }
+ """.trimIndent(), Complex.serializer())
+ assertEquals(ofMinutes(5), obj.s.d)
+ assertNull(obj.n.d)
+ assertEquals(listOf(Simple(ofMinutes(1)), Simple(ofSeconds(2))), obj.l)
+ assertEquals(listOf(Nullable(null), Nullable(ofHours(6))), obj.ln)
+ assertEquals(6, obj.i)
+ assertTrue(obj.f)
+ assertEquals(listOf(ofDays(1), ofMinutes(1), ofNanos(5)), obj.ld)
+ assertEquals(mapOf("day" to ofDays(2), "hour" to ofHours(5), "minute" to ofMinutes(3)), obj.mp)
+ assertEquals(mapOf(ofHours(1) to ofSeconds(3600)), obj.mpp)
+ }
+
+ @Test
+ fun testThrowsWhenNotTimeUnitHocon() {
+ val message = "Value at d cannot be read as Duration because it is not a valid HOCON duration value"
+ assertFailsWith<SerializationException>(message) {
+ deserializeConfig("d = 10 unknown", Simple.serializer())
+ }
+ }
+}
diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconMemorySizeTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconMemorySizeTest.kt
new file mode 100644
index 0000000..da8b00e
--- /dev/null
+++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconMemorySizeTest.kt
@@ -0,0 +1,175 @@
+@file:UseSerializers(ConfigMemorySizeSerializer::class)
+package kotlinx.serialization.hocon
+
+import com.typesafe.config.*
+import com.typesafe.config.ConfigMemorySize.ofBytes
+import java.math.BigInteger
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.hocon.serializers.ConfigMemorySizeSerializer
+import kotlinx.serialization.modules.*
+import org.junit.Assert.*
+import org.junit.Test
+import kotlin.test.assertFailsWith
+
+class HoconMemorySizeTest {
+
+ @Serializable
+ data class Simple(val size: ConfigMemorySize)
+
+ @Serializable
+ data class Nullable(val size: ConfigMemorySize?)
+
+ @Serializable
+ data class ConfigList(val l: List<ConfigMemorySize>)
+
+ @Serializable
+ data class ConfigMap(val mp: Map<String, ConfigMemorySize>)
+
+ @Serializable
+ data class ConfigMapMemoryKey(val mp: Map<ConfigMemorySize, ConfigMemorySize>)
+
+ @Serializable
+ data class Complex(
+ val i: Int,
+ val s: Simple,
+ val n: Nullable,
+ val l: List<Simple>,
+ val ln: List<Nullable>,
+ val f: Boolean,
+ val ld: List<ConfigMemorySize>,
+ val mp: Map<String, ConfigMemorySize>,
+ val mpp: Map<ConfigMemorySize, ConfigMemorySize>
+ )
+
+ private fun testMemorySize(simple: Simple, str: String) {
+ val res = Hocon.encodeToConfig(simple)
+ res.assertContains(str)
+ assertEquals(simple, Hocon.decodeFromConfig(Simple.serializer(), res))
+ }
+
+ @Test
+ fun testSerializeMemorySize() {
+ testMemorySize(Simple(ofBytes(10)), "size = 10 byte")
+ testMemorySize(Simple(ofBytes(1000)), "size = 1000 byte")
+
+ val oneKib = BigInteger.valueOf(1024)
+ testMemorySize(Simple(ofBytes(oneKib)), "size = 1 KiB")
+ testMemorySize(Simple(ofBytes(oneKib + BigInteger.ONE)), "size = 1025 byte")
+
+ val oneMib = oneKib * oneKib
+ testMemorySize(Simple(ofBytes(oneMib)), "size = 1 MiB")
+ testMemorySize(Simple(ofBytes(oneMib + BigInteger.ONE)), "size = ${oneMib + BigInteger.ONE} byte")
+ testMemorySize(Simple(ofBytes(oneMib + oneKib)), "size = 1025 KiB")
+
+ val oneGib = oneMib * oneKib
+ testMemorySize(Simple(ofBytes(oneGib)), "size = 1 GiB")
+ testMemorySize(Simple(ofBytes(oneGib + BigInteger.ONE)), "size = ${oneGib + BigInteger.ONE} byte")
+ testMemorySize(Simple(ofBytes(oneGib + oneKib)), "size = ${oneMib + BigInteger.ONE} KiB")
+ testMemorySize(Simple(ofBytes(oneGib + oneMib)), "size = 1025 MiB")
+
+ val oneTib = oneGib * (oneKib)
+ testMemorySize(Simple(ofBytes(oneTib)), "size = 1 TiB")
+ testMemorySize(Simple(ofBytes(oneTib + BigInteger.ONE)), "size = ${oneTib.add(BigInteger.ONE)} byte")
+ testMemorySize(Simple(ofBytes(oneTib + oneKib)), "size = ${oneGib + BigInteger.ONE} KiB")
+ testMemorySize(Simple(ofBytes(oneTib + oneMib)), "size = ${oneMib + BigInteger.ONE} MiB")
+ testMemorySize(Simple(ofBytes(oneTib + oneGib)), "size = 1025 GiB")
+
+ val onePib = oneTib * oneKib
+ testMemorySize(Simple(ofBytes(onePib)), "size = 1 PiB")
+ testMemorySize(Simple(ofBytes(onePib + BigInteger.ONE)), "size = ${onePib + BigInteger.ONE} byte")
+
+ val oneEib = onePib * oneKib
+ testMemorySize(Simple(ofBytes(oneEib)), "size = 1 EiB")
+ testMemorySize(Simple(ofBytes(oneEib + BigInteger.ONE)), "size = ${oneEib + BigInteger.ONE} byte")
+
+ val oneZib = oneEib * oneKib
+ testMemorySize(Simple(ofBytes(oneZib)), "size = 1 ZiB")
+ testMemorySize(Simple(ofBytes(oneZib + BigInteger.ONE)), "size = ${oneZib + BigInteger.ONE} byte")
+
+ val oneYib = oneZib * oneKib
+ testMemorySize(Simple(ofBytes(oneYib)), "size = 1 YiB")
+ testMemorySize(Simple(ofBytes(oneYib + BigInteger.ONE)), "size = ${oneYib + BigInteger.ONE} byte")
+ testMemorySize(Simple(ofBytes(oneYib * oneKib)), "size = $oneKib YiB")
+ }
+
+ @Test
+ fun testSerializeNullableMemorySize() {
+ Hocon.encodeToConfig(Nullable(null)).assertContains("size = null")
+ Hocon.encodeToConfig(Nullable(ofBytes(1024 * 6))).assertContains("size = 6 KiB")
+ }
+
+ @Test
+ fun testSerializeListOfMemorySize() {
+ Hocon.encodeToConfig(ConfigList(listOf(ofBytes(1), ofBytes(1024 * 1024), ofBytes(1024))))
+ .assertContains("l: [ 1 byte, 1 MiB, 1 KiB ]")
+ }
+
+ @Test
+ fun testSerializeMapOfMemorySize() {
+ Hocon.encodeToConfig(ConfigMap(mapOf("one" to ofBytes(2000), "two" to ofBytes(1024 * 1024 * 1024))))
+ .assertContains("mp: { one = 2000 byte, two = 1 GiB }")
+ Hocon.encodeToConfig(ConfigMapMemoryKey((mapOf(ofBytes(1024) to ofBytes(1024)))))
+ .assertContains("mp: { 1 KiB = 1 KiB }")
+ }
+
+ @Test
+ fun testDeserializeNullableMemorySize() {
+ var obj = deserializeConfig("size = null", Nullable.serializer())
+ assertNull(obj.size)
+ obj = deserializeConfig("size = 5 byte", Nullable.serializer())
+ assertEquals(ofBytes(5), obj.size)
+ }
+
+ @Test
+ fun testDeserializeListOfMemorySize() {
+ val obj = deserializeConfig("l: [ 1b, 1MB, 1Ki ]", ConfigList.serializer())
+ assertEquals(listOf(ofBytes(1), ofBytes(1_000_000), ofBytes(1024)), obj.l)
+ }
+
+ @Test
+ fun testDeserializeMapOfMemorySize() {
+ val obj = deserializeConfig("""
+ mp: { one = 2kB, two = 5 MB }
+ """.trimIndent(), ConfigMap.serializer())
+ assertEquals(mapOf("one" to ofBytes(2000), "two" to ofBytes(5_000_000)), obj.mp)
+
+ val objDurationKey = deserializeConfig("""
+ mp: { 1024b = 1Ki }
+ """.trimIndent(), ConfigMapMemoryKey.serializer())
+ assertEquals(mapOf(ofBytes(1024) to ofBytes(1024)), objDurationKey.mp)
+ }
+
+ @Test
+ fun testDeserializeComplexMemorySize() {
+ val obj = deserializeConfig("""
+ i = 6
+ s: { size = 5 MB }
+ n: { size = null }
+ l: [ { size = 1 kB }, { size = 2b } ]
+ ln: [ { size = null }, { size = 1 Mi } ]
+ f = true
+ ld: [ 1 kB, 1 m]
+ mp: { one = 2kB, two = 5 MB }
+ mpp: { 1024b = 1Ki }
+ """.trimIndent(), Complex.serializer())
+ assertEquals(ofBytes(5_000_000), obj.s.size)
+ assertNull(obj.n.size)
+ assertEquals(listOf(Simple(ofBytes(1000)), Simple(ofBytes(2))), obj.l)
+ assertEquals(listOf(Nullable(null), Nullable(ofBytes(1024 * 1024))), obj.ln)
+ assertEquals(6, obj.i)
+ assertTrue(obj.f)
+ assertEquals(listOf(ofBytes(1000), ofBytes(1048576)), obj.ld)
+ assertEquals(mapOf("one" to ofBytes(2000), "two" to ofBytes(5_000_000)), obj.mp)
+ assertEquals(mapOf(ofBytes(1024) to ofBytes(1024)), obj.mpp)
+ }
+
+ @Test
+ fun testThrowsWhenNotSizeFormatHocon() {
+ val message = "Value at size cannot be read as ConfigMemorySize because it is not a valid HOCON Size value"
+ assertFailsWith<SerializationException>(message) {
+ deserializeConfig("size = 1 unknown", Simple.serializer())
+ }
+ }
+}
diff --git a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconObjectsTest.kt b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconObjectsTest.kt
index a52974f..8726ea3 100644
--- a/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconObjectsTest.kt
+++ b/formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconObjectsTest.kt
@@ -6,17 +6,21 @@
import com.typesafe.config.*
import kotlinx.serialization.*
+import kotlinx.serialization.modules.*
import org.junit.*
import org.junit.Assert.*
internal inline fun <reified T> deserializeConfig(
configString: String,
deserializer: DeserializationStrategy<T>,
- useNamingConvention: Boolean = false
+ useNamingConvention: Boolean = false,
+ modules: SerializersModule = Hocon.serializersModule
): T {
val ucnc = useNamingConvention
- return Hocon { useConfigNamingConvention = ucnc }
- .decodeFromConfig(deserializer, ConfigFactory.parseString(configString))
+ return Hocon {
+ useConfigNamingConvention = ucnc
+ serializersModule = modules
+ }.decodeFromConfig(deserializer, ConfigFactory.parseString(configString))
}
class ConfigParserObjectsTest {
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-okio/api/kotlinx-serialization-json-okio.api b/formats/json-okio/api/kotlinx-serialization-json-okio.api
new file mode 100644
index 0000000..75effa1
--- /dev/null
+++ b/formats/json-okio/api/kotlinx-serialization-json-okio.api
@@ -0,0 +1,7 @@
+public final class kotlinx/serialization/json/okio/OkioStreamsKt {
+ public static final fun decodeBufferedSourceToSequence (Lkotlinx/serialization/json/Json;Lokio/BufferedSource;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;)Lkotlin/sequences/Sequence;
+ public static synthetic fun decodeBufferedSourceToSequence$default (Lkotlinx/serialization/json/Json;Lokio/BufferedSource;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;ILjava/lang/Object;)Lkotlin/sequences/Sequence;
+ public static final fun decodeFromBufferedSource (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Lokio/BufferedSource;)Ljava/lang/Object;
+ public static final fun encodeToBufferedSink (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Lokio/BufferedSink;)V
+}
+
diff --git a/formats/json-okio/build.gradle.kts b/formats/json-okio/build.gradle.kts
new file mode 100644
index 0000000..a51fff0
--- /dev/null
+++ b/formats/json-okio/build.gradle.kts
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import Java9Modularity.configureJava9ModuleInfo
+import org.jetbrains.dokka.gradle.*
+import java.net.*
+
+plugins {
+ kotlin("multiplatform")
+ kotlin("plugin.serialization")
+}
+
+apply(from = rootProject.file("gradle/native-targets.gradle"))
+apply(from = rootProject.file("gradle/configure-source-sets.gradle"))
+
+kotlin {
+ sourceSets {
+ configureEach {
+ languageSettings {
+ optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
+ }
+ }
+ val commonMain by getting {
+ dependencies {
+ api(project(":kotlinx-serialization-core"))
+ api(project(":kotlinx-serialization-json"))
+ implementation("com.squareup.okio:okio:${property("okio_version")}")
+ }
+ }
+ val commonTest by getting {
+ dependencies {
+ implementation("com.squareup.okio:okio:${property("okio_version")}")
+ }
+ }
+ }
+}
+
+project.configureJava9ModuleInfo()
+
+tasks.named<DokkaTaskPartial>("dokkaHtmlPartial") {
+ dokkaSourceSets {
+ configureEach {
+ externalDocumentationLink {
+ url.set(URL("https://square.github.io/okio/3.x/okio/"))
+ packageListUrl.set(
+ file("dokka/okio.package.list").toURI().toURL()
+ )
+ }
+ }
+ }
+}
+
+
+// TODO: Remove this after okio will be updated to the version with 1.9.20 stdlib dependency
+configurations.all {
+ resolutionStrategy.eachDependency {
+ if (requested.name == "kotlin-stdlib-wasm") {
+ useTarget("org.jetbrains.kotlin:kotlin-stdlib-wasm-js:${requested.version}")
+ }
+ }
+}
diff --git a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
new file mode 100644
index 0000000..968f533
--- /dev/null
+++ b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.okio
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.DecodeSequenceMode
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.json.okio.internal.JsonToOkioStreamWriter
+import kotlinx.serialization.json.internal.decodeToSequenceByReader
+import kotlinx.serialization.json.okio.internal.OkioSerialReader
+import okio.*
+
+/**
+ * Serializes the [value] with [serializer] into a [sink] using JSON format and UTF-8 encoding.
+ *
+ * @throws [SerializationException] if the given value cannot be serialized to JSON.
+ * @throws [okio.IOException] If an I/O error occurs and sink can't be written to.
+ */
+@ExperimentalSerializationApi
+public fun <T> Json.encodeToBufferedSink(
+ serializer: SerializationStrategy<T>,
+ value: T,
+ sink: BufferedSink
+) {
+ val writer = JsonToOkioStreamWriter(sink)
+ try {
+ encodeByWriter(this, writer, serializer, value)
+ } finally {
+ writer.release()
+ }
+}
+
+/**
+ * Serializes given [value] to a [sink] using UTF-8 encoding and serializer retrieved from the reified type parameter.
+ *
+ * @throws [SerializationException] if the given value cannot be serialized to JSON.
+ * @throws [okio.IOException] If an I/O error occurs and sink can't be written to.
+ */
+@ExperimentalSerializationApi
+public inline fun <reified T> Json.encodeToBufferedSink(
+ value: T,
+ sink: BufferedSink
+): Unit = encodeToBufferedSink(serializersModule.serializer(), value, sink)
+
+
+/**
+ * Deserializes JSON from [source] using UTF-8 encoding to a value of type [T] using [deserializer].
+ *
+ * Note that this functions expects that exactly one object would be present in the source
+ * and throws an exception if there are any dangling bytes after an object.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public fun <T> Json.decodeFromBufferedSource(
+ deserializer: DeserializationStrategy<T>,
+ source: BufferedSource
+): T {
+ return decodeByReader(this, deserializer, OkioSerialReader(source))
+}
+
+/**
+ * Deserializes the contents of given [source] to the value of type [T] using UTF-8 encoding and
+ * deserializer retrieved from the reified type parameter.
+ *
+ * Note that this functions expects that exactly one object would be present in the stream
+ * and throws an exception if there are any dangling bytes after an object.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public inline fun <reified T> Json.decodeFromBufferedSource(source: BufferedSource): T =
+ decodeFromBufferedSource(serializersModule.serializer(), source)
+
+
+/**
+ * Transforms the given [source] into lazily deserialized sequence of elements of type [T] using UTF-8 encoding and [deserializer].
+ * Unlike [decodeFromBufferedSource], [source] is allowed to have more than one element, separated as [format] declares.
+ *
+ * Elements must all be of type [T].
+ * Elements are parsed lazily when resulting [Sequence] is evaluated.
+ * Resulting sequence is tied to the stream and can be evaluated only once.
+ *
+ * **Resource caution:** this method neither closes the [source] when the parsing is finished nor provides a method to close it manually.
+ * It is a caller responsibility to hold a reference to a source and close it. Moreover, because source is parsed lazily,
+ * closing it before returned sequence is evaluated completely will result in [Exception] from decoder.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public fun <T> Json.decodeBufferedSourceToSequence(
+ source: BufferedSource,
+ deserializer: DeserializationStrategy<T>,
+ format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
+): Sequence<T> {
+ return decodeToSequenceByReader(this, OkioSerialReader(source), deserializer, format)
+}
+
+/**
+ * Transforms the given [source] into lazily deserialized sequence of elements of type [T] using UTF-8 encoding and deserializer retrieved from the reified type parameter.
+ * Unlike [decodeFromBufferedSource], [source] is allowed to have more than one element, separated as [format] declares.
+ *
+ * Elements must all be of type [T].
+ * Elements are parsed lazily when resulting [Sequence] is evaluated.
+ * Resulting sequence is tied to the stream and constrained to be evaluated only once.
+ *
+ * **Resource caution:** this method does not close [source] when the parsing is finished neither provides method to close it manually.
+ * It is a caller responsibility to hold a reference to a source and close it. Moreover, because source is parsed lazily,
+ * closing it before returned sequence is evaluated fully would result in [Exception] from decoder.
+ *
+ * @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
+ * @throws [okio.IOException] If an I/O error occurs and source can't be read from.
+ */
+@ExperimentalSerializationApi
+public inline fun <reified T> Json.decodeBufferedSourceToSequence(
+ source: BufferedSource,
+ format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
+): Sequence<T> = decodeBufferedSourceToSequence(source, serializersModule.serializer(), format)
diff --git a/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
new file mode 100644
index 0000000..1de8971
--- /dev/null
+++ b/formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.okio.internal
+
+import kotlinx.serialization.json.internal.*
+import okio.*
+
+// Copied from kotlinx/serialization/json/internal/StringOps.kt
+private fun toHexChar(i: Int) : Char {
+ val d = i and 0xf
+ return if (d < 10) (d + '0'.code).toChar()
+ else (d - 10 + 'a'.code).toChar()
+}
+
+// Copied from kotlinx/serialization/json/internal/StringOps.kt
+private val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
+ for (c in 0..0x1f) {
+ val c1 = toHexChar(c shr 12)
+ val c2 = toHexChar(c shr 8)
+ val c3 = toHexChar(c shr 4)
+ val c4 = toHexChar(c)
+ this[c] = "\\u$c1$c2$c3$c4"
+ }
+ this['"'.code] = "\\\""
+ this['\\'.code] = "\\\\"
+ this['\t'.code] = "\\t"
+ this['\b'.code] = "\\b"
+ this['\n'.code] = "\\n"
+ this['\r'.code] = "\\r"
+ this[0x0c] = "\\f"
+}
+
+
+
+internal class JsonToOkioStreamWriter(private val sink: BufferedSink) : InternalJsonWriter {
+ override fun writeLong(value: Long) {
+ write(value.toString())
+ }
+
+ override fun writeChar(char: Char) {
+ sink.writeUtf8CodePoint(char.code)
+ }
+
+ override fun write(text: String) {
+ sink.writeUtf8(text)
+ }
+
+ override fun writeQuoted(text: String) {
+ sink.writeUtf8CodePoint('"'.code)
+ var lastPos = 0
+ for (i in text.indices) {
+ val c = text[i].code
+ if (c < ESCAPE_STRINGS.size && ESCAPE_STRINGS[c] != null) {
+ sink.writeUtf8(text, lastPos, i) // flush prev
+ sink.writeUtf8(ESCAPE_STRINGS[c]!!)
+ lastPos = i + 1
+ }
+ }
+
+ if (lastPos != 0) sink.writeUtf8(text, lastPos, text.length)
+ else sink.writeUtf8(text)
+ sink.writeUtf8CodePoint('"'.code)
+ }
+
+ override fun release() {
+ // no-op, see https://github.com/Kotlin/kotlinx.serialization/pull/1982#discussion_r915043700
+ }
+}
+
+// Max value for a code point placed in one Char
+private const val SINGLE_CHAR_MAX_CODEPOINT = Char.MAX_VALUE.code
+// Value added to the high UTF-16 surrogate after shifting
+private const val HIGH_SURROGATE_HEADER = 0xd800 - (0x010000 ushr 10)
+// Value added to the low UTF-16 surrogate after masking
+private const val LOW_SURROGATE_HEADER = 0xdc00
+
+
+internal class OkioSerialReader(private val source: BufferedSource): InternalJsonReader {
+ /*
+ A sequence of code points is read from UTF-8, some of it can take 2 characters.
+ In case the last code point requires 2 characters, and the array is already full, we buffer the second character
+ */
+ private var bufferedChar: Char? = null
+
+ override fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int {
+ var i = 0
+
+ if (bufferedChar != null) {
+ buffer[bufferOffset + i] = bufferedChar!!
+ i++
+ bufferedChar = null
+ }
+
+ while (i < count && !source.exhausted()) {
+ val codePoint = source.readUtf8CodePoint()
+ if (codePoint <= SINGLE_CHAR_MAX_CODEPOINT) {
+ buffer[bufferOffset + i] = codePoint.toChar()
+ i++
+ } else {
+ // an example of working with surrogates is taken from okio library with minor changes, see https://github.com/square/okio
+ // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
+ // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
+ // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
+ val upChar = ((codePoint ushr 10) + HIGH_SURROGATE_HEADER).toChar()
+ val lowChar = ((codePoint and 0x03ff) + LOW_SURROGATE_HEADER).toChar()
+
+ buffer[bufferOffset + i] = upChar
+ i++
+
+ if (i < count) {
+ buffer[bufferOffset + i] = lowChar
+ i++
+ } else {
+ // if char array is full - buffer lower surrogate
+ bufferedChar = lowChar
+ }
+ }
+ }
+ return if (i > 0) i else -1
+ }
+}
+
diff --git a/formats/json-okio/dokka/okio.package.list b/formats/json-okio/dokka/okio.package.list
new file mode 100644
index 0000000..96713f5
--- /dev/null
+++ b/formats/json-okio/dokka/okio.package.list
@@ -0,0 +1,550 @@
+$dokka.format:html-v1
+$dokka.linkExtension:html
+$dokka.location:okio////PointingToDeclaration/okio/okio/index.html
+$dokka.location:okio//Okio/#/PointingToDeclaration/okio/okio/-okio.html
+$dokka.location:okio//Utf8/#/PointingToDeclaration/okio/okio/-utf8.html
+$dokka.location:okio//appendingSink/java.io.File#/PointingToDeclaration/okio/okio/appending-sink.html
+$dokka.location:okio//asResourceFileSystem/java.lang.ClassLoader#/PointingToDeclaration/okio/okio/as-resource-file-system.html
+$dokka.location:okio//blackholeSink/#/PointingToDeclaration/okio/okio/blackhole-sink.html
+$dokka.location:okio//buffer/okio.Sink#/PointingToDeclaration/okio/okio/buffer.html
+$dokka.location:okio//buffer/okio.Source#/PointingToDeclaration/okio/okio/buffer.html
+$dokka.location:okio//cipherSink/okio.Sink#javax.crypto.Cipher/PointingToDeclaration/okio/okio/cipher-sink.html
+$dokka.location:okio//cipherSource/okio.Source#javax.crypto.Cipher/PointingToDeclaration/okio/okio/cipher-source.html
+$dokka.location:okio//deflate/okio.Sink#java.util.zip.Deflater/PointingToDeclaration/okio/okio/deflate.html
+$dokka.location:okio//gzip/okio.Sink#/PointingToDeclaration/okio/okio/gzip.html
+$dokka.location:okio//gzip/okio.Source#/PointingToDeclaration/okio/okio/gzip.html
+$dokka.location:okio//hashingSink/okio.Sink#java.security.MessageDigest/PointingToDeclaration/okio/okio/hashing-sink.html
+$dokka.location:okio//hashingSink/okio.Sink#javax.crypto.Mac/PointingToDeclaration/okio/okio/hashing-sink.html
+$dokka.location:okio//hashingSource/okio.Source#java.security.MessageDigest/PointingToDeclaration/okio/okio/hashing-source.html
+$dokka.location:okio//hashingSource/okio.Source#javax.crypto.Mac/PointingToDeclaration/okio/okio/hashing-source.html
+$dokka.location:okio//inflate/okio.Source#java.util.zip.Inflater/PointingToDeclaration/okio/okio/inflate.html
+$dokka.location:okio//openZip/okio.FileSystem#okio.Path/PointingToDeclaration/okio/okio/open-zip.html
+$dokka.location:okio//sink/java.io.File#kotlin.Boolean/PointingToDeclaration/okio/okio/sink.html
+$dokka.location:okio//sink/java.io.OutputStream#/PointingToDeclaration/okio/okio/sink.html
+$dokka.location:okio//sink/java.net.Socket#/PointingToDeclaration/okio/okio/sink.html
+$dokka.location:okio//sink/java.nio.file.Path#kotlin.Array[java.nio.file.OpenOption]/PointingToDeclaration/okio/okio/sink.html
+$dokka.location:okio//source/java.io.File#/PointingToDeclaration/okio/okio/source.html
+$dokka.location:okio//source/java.io.InputStream#/PointingToDeclaration/okio/okio/source.html
+$dokka.location:okio//source/java.net.Socket#/PointingToDeclaration/okio/okio/source.html
+$dokka.location:okio//source/java.nio.file.Path#kotlin.Array[java.nio.file.OpenOption]/PointingToDeclaration/okio/okio/source.html
+$dokka.location:okio//use/TypeParam(bounds=[okio.Closeable?])#kotlin.Function1[TypeParam(bounds=[okio.Closeable?]),TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/use.html
+$dokka.location:okio//utf8Size/kotlin.String#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/utf8-size.html
+$dokka.location:okio//withLock/okio.Lock#kotlin.Function0[TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/with-lock.html
+$dokka.location:okio/ArrayIndexOutOfBoundsException///PointingToDeclaration/okio/okio/-array-index-out-of-bounds-exception/index.html
+$dokka.location:okio/ArrayIndexOutOfBoundsException/ArrayIndexOutOfBoundsException/#kotlin.String?/PointingToDeclaration/okio/okio/-array-index-out-of-bounds-exception/-array-index-out-of-bounds-exception.html
+$dokka.location:okio/AsyncTimeout.Companion///PointingToDeclaration/okio/okio/-async-timeout/-companion/index.html
+$dokka.location:okio/AsyncTimeout.Companion/condition/#/PointingToDeclaration/okio/okio/-async-timeout/-companion/condition.html
+$dokka.location:okio/AsyncTimeout.Companion/lock/#/PointingToDeclaration/okio/okio/-async-timeout/-companion/lock.html
+$dokka.location:okio/AsyncTimeout///PointingToDeclaration/okio/okio/-async-timeout/index.html
+$dokka.location:okio/AsyncTimeout/AsyncTimeout/#/PointingToDeclaration/okio/okio/-async-timeout/-async-timeout.html
+$dokka.location:okio/AsyncTimeout/enter/#/PointingToDeclaration/okio/okio/-async-timeout/enter.html
+$dokka.location:okio/AsyncTimeout/exit/#/PointingToDeclaration/okio/okio/-async-timeout/exit.html
+$dokka.location:okio/AsyncTimeout/sink/#okio.Sink/PointingToDeclaration/okio/okio/-async-timeout/sink.html
+$dokka.location:okio/AsyncTimeout/source/#okio.Source/PointingToDeclaration/okio/okio/-async-timeout/source.html
+$dokka.location:okio/AsyncTimeout/withTimeout/#kotlin.Function0[TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/-async-timeout/with-timeout.html
+$dokka.location:okio/Buffer.UnsafeCursor///PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/index.html
+$dokka.location:okio/Buffer.UnsafeCursor/UnsafeCursor/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/-unsafe-cursor.html
+$dokka.location:okio/Buffer.UnsafeCursor/buffer/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/buffer.html
+$dokka.location:okio/Buffer.UnsafeCursor/close/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/close.html
+$dokka.location:okio/Buffer.UnsafeCursor/data/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/data.html
+$dokka.location:okio/Buffer.UnsafeCursor/end/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/end.html
+$dokka.location:okio/Buffer.UnsafeCursor/expandBuffer/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/expand-buffer.html
+$dokka.location:okio/Buffer.UnsafeCursor/next/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/next.html
+$dokka.location:okio/Buffer.UnsafeCursor/offset/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/offset.html
+$dokka.location:okio/Buffer.UnsafeCursor/readWrite/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/read-write.html
+$dokka.location:okio/Buffer.UnsafeCursor/resizeBuffer/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/resize-buffer.html
+$dokka.location:okio/Buffer.UnsafeCursor/seek/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/seek.html
+$dokka.location:okio/Buffer.UnsafeCursor/start/#/PointingToDeclaration/okio/okio/-buffer/-unsafe-cursor/start.html
+$dokka.location:okio/Buffer///PointingToDeclaration/okio/okio/-buffer/index.html
+$dokka.location:okio/Buffer/Buffer/#/PointingToDeclaration/okio/okio/-buffer/-buffer.html
+$dokka.location:okio/Buffer/buffer/#/PointingToDeclaration/okio/okio/-buffer/buffer.html
+$dokka.location:okio/Buffer/clear/#/PointingToDeclaration/okio/okio/-buffer/clear.html
+$dokka.location:okio/Buffer/clone/#/PointingToDeclaration/okio/okio/-buffer/clone.html
+$dokka.location:okio/Buffer/close/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]close.html
+$dokka.location:okio/Buffer/completeSegmentByteCount/#/PointingToDeclaration/okio/okio/-buffer/complete-segment-byte-count.html
+$dokka.location:okio/Buffer/copy/#/PointingToDeclaration/okio/okio/-buffer/copy.html
+$dokka.location:okio/Buffer/copyTo/#java.io.OutputStream#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/copy-to.html
+$dokka.location:okio/Buffer/copyTo/#okio.Buffer#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/copy-to.html
+$dokka.location:okio/Buffer/copyTo/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/copy-to.html
+$dokka.location:okio/Buffer/emit/#/PointingToDeclaration/okio/okio/-buffer/emit.html
+$dokka.location:okio/Buffer/emitCompleteSegments/#/PointingToDeclaration/okio/okio/-buffer/emit-complete-segments.html
+$dokka.location:okio/Buffer/equals/#kotlin.Any?/PointingToDeclaration/okio/okio/-buffer/[non-jvm]equals.html
+$dokka.location:okio/Buffer/exhausted/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]exhausted.html
+$dokka.location:okio/Buffer/flush/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]flush.html
+$dokka.location:okio/Buffer/get/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/get.html
+$dokka.location:okio/Buffer/hashCode/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]hash-code.html
+$dokka.location:okio/Buffer/hmacSha1/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/hmac-sha1.html
+$dokka.location:okio/Buffer/hmacSha256/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/hmac-sha256.html
+$dokka.location:okio/Buffer/hmacSha512/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/hmac-sha512.html
+$dokka.location:okio/Buffer/indexOf/#kotlin.Byte#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html
+$dokka.location:okio/Buffer/indexOf/#kotlin.Byte#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html
+$dokka.location:okio/Buffer/indexOf/#kotlin.Byte/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html
+$dokka.location:okio/Buffer/indexOf/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html
+$dokka.location:okio/Buffer/indexOf/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of.html
+$dokka.location:okio/Buffer/indexOfElement/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of-element.html
+$dokka.location:okio/Buffer/indexOfElement/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/[non-jvm]index-of-element.html
+$dokka.location:okio/Buffer/inputStream/#/PointingToDeclaration/okio/okio/-buffer/input-stream.html
+$dokka.location:okio/Buffer/isOpen/#/PointingToDeclaration/okio/okio/-buffer/is-open.html
+$dokka.location:okio/Buffer/md5/#/PointingToDeclaration/okio/okio/-buffer/md5.html
+$dokka.location:okio/Buffer/outputStream/#/PointingToDeclaration/okio/okio/-buffer/output-stream.html
+$dokka.location:okio/Buffer/peek/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]peek.html
+$dokka.location:okio/Buffer/rangeEquals/#kotlin.Long#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/[non-jvm]range-equals.html
+$dokka.location:okio/Buffer/rangeEquals/#kotlin.Long#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/[non-jvm]range-equals.html
+$dokka.location:okio/Buffer/read/#java.nio.ByteBuffer/PointingToDeclaration/okio/okio/-buffer/read.html
+$dokka.location:okio/Buffer/read/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read.html
+$dokka.location:okio/Buffer/read/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read.html
+$dokka.location:okio/Buffer/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read.html
+$dokka.location:okio/Buffer/readAll/#okio.Sink/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-all.html
+$dokka.location:okio/Buffer/readAndWriteUnsafe/#okio.Buffer.UnsafeCursor/PointingToDeclaration/okio/okio/-buffer/read-and-write-unsafe.html
+$dokka.location:okio/Buffer/readByte/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte.html
+$dokka.location:okio/Buffer/readByteArray/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-array.html
+$dokka.location:okio/Buffer/readByteArray/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-array.html
+$dokka.location:okio/Buffer/readByteString/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-string.html
+$dokka.location:okio/Buffer/readByteString/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-byte-string.html
+$dokka.location:okio/Buffer/readDecimalLong/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-decimal-long.html
+$dokka.location:okio/Buffer/readFrom/#java.io.InputStream#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/read-from.html
+$dokka.location:okio/Buffer/readFrom/#java.io.InputStream/PointingToDeclaration/okio/okio/-buffer/read-from.html
+$dokka.location:okio/Buffer/readFully/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-fully.html
+$dokka.location:okio/Buffer/readFully/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-fully.html
+$dokka.location:okio/Buffer/readHexadecimalUnsignedLong/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-hexadecimal-unsigned-long.html
+$dokka.location:okio/Buffer/readInt/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-int.html
+$dokka.location:okio/Buffer/readIntLe/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-int-le.html
+$dokka.location:okio/Buffer/readLong/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-long.html
+$dokka.location:okio/Buffer/readLongLe/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-long-le.html
+$dokka.location:okio/Buffer/readShort/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-short.html
+$dokka.location:okio/Buffer/readShortLe/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-short-le.html
+$dokka.location:okio/Buffer/readString/#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffer/read-string.html
+$dokka.location:okio/Buffer/readString/#kotlin.Long#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffer/read-string.html
+$dokka.location:okio/Buffer/readUnsafe/#okio.Buffer.UnsafeCursor/PointingToDeclaration/okio/okio/-buffer/read-unsafe.html
+$dokka.location:okio/Buffer/readUtf8/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8.html
+$dokka.location:okio/Buffer/readUtf8/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8.html
+$dokka.location:okio/Buffer/readUtf8CodePoint/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-code-point.html
+$dokka.location:okio/Buffer/readUtf8Line/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-line.html
+$dokka.location:okio/Buffer/readUtf8LineStrict/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-line-strict.html
+$dokka.location:okio/Buffer/readUtf8LineStrict/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]read-utf8-line-strict.html
+$dokka.location:okio/Buffer/request/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]request.html
+$dokka.location:okio/Buffer/require/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]require.html
+$dokka.location:okio/Buffer/select/#okio.Options/PointingToDeclaration/okio/okio/-buffer/[non-jvm]select.html
+$dokka.location:okio/Buffer/sha1/#/PointingToDeclaration/okio/okio/-buffer/sha1.html
+$dokka.location:okio/Buffer/sha256/#/PointingToDeclaration/okio/okio/-buffer/sha256.html
+$dokka.location:okio/Buffer/sha512/#/PointingToDeclaration/okio/okio/-buffer/sha512.html
+$dokka.location:okio/Buffer/size/#/PointingToDeclaration/okio/okio/-buffer/size.html
+$dokka.location:okio/Buffer/skip/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/skip.html
+$dokka.location:okio/Buffer/snapshot/#/PointingToDeclaration/okio/okio/-buffer/snapshot.html
+$dokka.location:okio/Buffer/snapshot/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/snapshot.html
+$dokka.location:okio/Buffer/timeout/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]timeout.html
+$dokka.location:okio/Buffer/toString/#/PointingToDeclaration/okio/okio/-buffer/[non-jvm]to-string.html
+$dokka.location:okio/Buffer/write/#java.nio.ByteBuffer/PointingToDeclaration/okio/okio/-buffer/write.html
+$dokka.location:okio/Buffer/write/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write.html
+$dokka.location:okio/Buffer/write/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffer/write.html
+$dokka.location:okio/Buffer/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/[non-jvm]write.html
+$dokka.location:okio/Buffer/write/#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write.html
+$dokka.location:okio/Buffer/write/#okio.ByteString/PointingToDeclaration/okio/okio/-buffer/write.html
+$dokka.location:okio/Buffer/write/#okio.Source#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write.html
+$dokka.location:okio/Buffer/writeAll/#okio.Source/PointingToDeclaration/okio/okio/-buffer/[non-jvm]write-all.html
+$dokka.location:okio/Buffer/writeByte/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-byte.html
+$dokka.location:okio/Buffer/writeDecimalLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-decimal-long.html
+$dokka.location:okio/Buffer/writeHexadecimalUnsignedLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-hexadecimal-unsigned-long.html
+$dokka.location:okio/Buffer/writeInt/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-int.html
+$dokka.location:okio/Buffer/writeIntLe/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-int-le.html
+$dokka.location:okio/Buffer/writeLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-long.html
+$dokka.location:okio/Buffer/writeLongLe/#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-long-le.html
+$dokka.location:okio/Buffer/writeShort/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-short.html
+$dokka.location:okio/Buffer/writeShortLe/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-short-le.html
+$dokka.location:okio/Buffer/writeString/#kotlin.String#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffer/write-string.html
+$dokka.location:okio/Buffer/writeString/#kotlin.String#kotlin.Int#kotlin.Int#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffer/write-string.html
+$dokka.location:okio/Buffer/writeTo/#java.io.OutputStream#kotlin.Long/PointingToDeclaration/okio/okio/-buffer/write-to.html
+$dokka.location:okio/Buffer/writeUtf8/#kotlin.String#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-utf8.html
+$dokka.location:okio/Buffer/writeUtf8/#kotlin.String/PointingToDeclaration/okio/okio/-buffer/write-utf8.html
+$dokka.location:okio/Buffer/writeUtf8CodePoint/#kotlin.Int/PointingToDeclaration/okio/okio/-buffer/write-utf8-code-point.html
+$dokka.location:okio/BufferedSink///PointingToDeclaration/okio/okio/-buffered-sink/index.html
+$dokka.location:okio/BufferedSink/buffer/#/PointingToDeclaration/okio/okio/-buffered-sink/buffer.html
+$dokka.location:okio/BufferedSink/emit/#/PointingToDeclaration/okio/okio/-buffered-sink/emit.html
+$dokka.location:okio/BufferedSink/emitCompleteSegments/#/PointingToDeclaration/okio/okio/-buffered-sink/emit-complete-segments.html
+$dokka.location:okio/BufferedSink/flush/#/PointingToDeclaration/okio/okio/-buffered-sink/flush.html
+$dokka.location:okio/BufferedSink/outputStream/#/PointingToDeclaration/okio/okio/-buffered-sink/output-stream.html
+$dokka.location:okio/BufferedSink/write/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write.html
+$dokka.location:okio/BufferedSink/write/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffered-sink/write.html
+$dokka.location:okio/BufferedSink/write/#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write.html
+$dokka.location:okio/BufferedSink/write/#okio.ByteString/PointingToDeclaration/okio/okio/-buffered-sink/write.html
+$dokka.location:okio/BufferedSink/write/#okio.Source#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-sink/write.html
+$dokka.location:okio/BufferedSink/writeAll/#okio.Source/PointingToDeclaration/okio/okio/-buffered-sink/write-all.html
+$dokka.location:okio/BufferedSink/writeByte/#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-byte.html
+$dokka.location:okio/BufferedSink/writeDecimalLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-sink/write-decimal-long.html
+$dokka.location:okio/BufferedSink/writeHexadecimalUnsignedLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-sink/write-hexadecimal-unsigned-long.html
+$dokka.location:okio/BufferedSink/writeInt/#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-int.html
+$dokka.location:okio/BufferedSink/writeIntLe/#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-int-le.html
+$dokka.location:okio/BufferedSink/writeLong/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-sink/write-long.html
+$dokka.location:okio/BufferedSink/writeLongLe/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-sink/write-long-le.html
+$dokka.location:okio/BufferedSink/writeShort/#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-short.html
+$dokka.location:okio/BufferedSink/writeShortLe/#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-short-le.html
+$dokka.location:okio/BufferedSink/writeString/#kotlin.String#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffered-sink/write-string.html
+$dokka.location:okio/BufferedSink/writeString/#kotlin.String#kotlin.Int#kotlin.Int#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffered-sink/write-string.html
+$dokka.location:okio/BufferedSink/writeUtf8/#kotlin.String#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-utf8.html
+$dokka.location:okio/BufferedSink/writeUtf8/#kotlin.String/PointingToDeclaration/okio/okio/-buffered-sink/write-utf8.html
+$dokka.location:okio/BufferedSink/writeUtf8CodePoint/#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-sink/write-utf8-code-point.html
+$dokka.location:okio/BufferedSource///PointingToDeclaration/okio/okio/-buffered-source/index.html
+$dokka.location:okio/BufferedSource/buffer/#/PointingToDeclaration/okio/okio/-buffered-source/buffer.html
+$dokka.location:okio/BufferedSource/exhausted/#/PointingToDeclaration/okio/okio/-buffered-source/exhausted.html
+$dokka.location:okio/BufferedSource/indexOf/#kotlin.Byte#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html
+$dokka.location:okio/BufferedSource/indexOf/#kotlin.Byte#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html
+$dokka.location:okio/BufferedSource/indexOf/#kotlin.Byte/PointingToDeclaration/okio/okio/-buffered-source/index-of.html
+$dokka.location:okio/BufferedSource/indexOf/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of.html
+$dokka.location:okio/BufferedSource/indexOf/#okio.ByteString/PointingToDeclaration/okio/okio/-buffered-source/index-of.html
+$dokka.location:okio/BufferedSource/indexOfElement/#okio.ByteString#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/index-of-element.html
+$dokka.location:okio/BufferedSource/indexOfElement/#okio.ByteString/PointingToDeclaration/okio/okio/-buffered-source/index-of-element.html
+$dokka.location:okio/BufferedSource/inputStream/#/PointingToDeclaration/okio/okio/-buffered-source/input-stream.html
+$dokka.location:okio/BufferedSource/peek/#/PointingToDeclaration/okio/okio/-buffered-source/peek.html
+$dokka.location:okio/BufferedSource/rangeEquals/#kotlin.Long#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-source/range-equals.html
+$dokka.location:okio/BufferedSource/rangeEquals/#kotlin.Long#okio.ByteString/PointingToDeclaration/okio/okio/-buffered-source/range-equals.html
+$dokka.location:okio/BufferedSource/read/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-buffered-source/read.html
+$dokka.location:okio/BufferedSource/read/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffered-source/read.html
+$dokka.location:okio/BufferedSource/readAll/#okio.Sink/PointingToDeclaration/okio/okio/-buffered-source/read-all.html
+$dokka.location:okio/BufferedSource/readByte/#/PointingToDeclaration/okio/okio/-buffered-source/read-byte.html
+$dokka.location:okio/BufferedSource/readByteArray/#/PointingToDeclaration/okio/okio/-buffered-source/read-byte-array.html
+$dokka.location:okio/BufferedSource/readByteArray/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/read-byte-array.html
+$dokka.location:okio/BufferedSource/readByteString/#/PointingToDeclaration/okio/okio/-buffered-source/read-byte-string.html
+$dokka.location:okio/BufferedSource/readByteString/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/read-byte-string.html
+$dokka.location:okio/BufferedSource/readDecimalLong/#/PointingToDeclaration/okio/okio/-buffered-source/read-decimal-long.html
+$dokka.location:okio/BufferedSource/readFully/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-buffered-source/read-fully.html
+$dokka.location:okio/BufferedSource/readFully/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/read-fully.html
+$dokka.location:okio/BufferedSource/readHexadecimalUnsignedLong/#/PointingToDeclaration/okio/okio/-buffered-source/read-hexadecimal-unsigned-long.html
+$dokka.location:okio/BufferedSource/readInt/#/PointingToDeclaration/okio/okio/-buffered-source/read-int.html
+$dokka.location:okio/BufferedSource/readIntLe/#/PointingToDeclaration/okio/okio/-buffered-source/read-int-le.html
+$dokka.location:okio/BufferedSource/readLong/#/PointingToDeclaration/okio/okio/-buffered-source/read-long.html
+$dokka.location:okio/BufferedSource/readLongLe/#/PointingToDeclaration/okio/okio/-buffered-source/read-long-le.html
+$dokka.location:okio/BufferedSource/readShort/#/PointingToDeclaration/okio/okio/-buffered-source/read-short.html
+$dokka.location:okio/BufferedSource/readShortLe/#/PointingToDeclaration/okio/okio/-buffered-source/read-short-le.html
+$dokka.location:okio/BufferedSource/readString/#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffered-source/read-string.html
+$dokka.location:okio/BufferedSource/readString/#kotlin.Long#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-buffered-source/read-string.html
+$dokka.location:okio/BufferedSource/readUtf8/#/PointingToDeclaration/okio/okio/-buffered-source/read-utf8.html
+$dokka.location:okio/BufferedSource/readUtf8/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/read-utf8.html
+$dokka.location:okio/BufferedSource/readUtf8CodePoint/#/PointingToDeclaration/okio/okio/-buffered-source/read-utf8-code-point.html
+$dokka.location:okio/BufferedSource/readUtf8Line/#/PointingToDeclaration/okio/okio/-buffered-source/read-utf8-line.html
+$dokka.location:okio/BufferedSource/readUtf8LineStrict/#/PointingToDeclaration/okio/okio/-buffered-source/read-utf8-line-strict.html
+$dokka.location:okio/BufferedSource/readUtf8LineStrict/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/read-utf8-line-strict.html
+$dokka.location:okio/BufferedSource/request/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/request.html
+$dokka.location:okio/BufferedSource/require/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/require.html
+$dokka.location:okio/BufferedSource/select/#okio.Options/PointingToDeclaration/okio/okio/-buffered-source/select.html
+$dokka.location:okio/BufferedSource/skip/#kotlin.Long/PointingToDeclaration/okio/okio/-buffered-source/skip.html
+$dokka.location:okio/ByteString.Companion///PointingToDeclaration/okio/okio/-byte-string/-companion/index.html
+$dokka.location:okio/ByteString.Companion/EMPTY/#/PointingToDeclaration/okio/okio/-byte-string/-companion/-e-m-p-t-y.html
+$dokka.location:okio/ByteString.Companion/decodeBase64/kotlin.String#/PointingToDeclaration/okio/okio/-byte-string/-companion/decode-base64.html
+$dokka.location:okio/ByteString.Companion/decodeHex/kotlin.String#/PointingToDeclaration/okio/okio/-byte-string/-companion/decode-hex.html
+$dokka.location:okio/ByteString.Companion/encode/kotlin.String#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-byte-string/-companion/encode.html
+$dokka.location:okio/ByteString.Companion/encodeUtf8/kotlin.String#/PointingToDeclaration/okio/okio/-byte-string/-companion/encode-utf8.html
+$dokka.location:okio/ByteString.Companion/of/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-byte-string/-companion/of.html
+$dokka.location:okio/ByteString.Companion/readByteString/java.io.InputStream#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/-companion/read-byte-string.html
+$dokka.location:okio/ByteString.Companion/toByteString/[Error type: Unresolved type for NSData]#/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html
+$dokka.location:okio/ByteString.Companion/toByteString/java.nio.ByteBuffer#/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html
+$dokka.location:okio/ByteString.Companion/toByteString/kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/-companion/to-byte-string.html
+$dokka.location:okio/ByteString///PointingToDeclaration/okio/okio/-byte-string/index.html
+$dokka.location:okio/ByteString/asByteBuffer/#/PointingToDeclaration/okio/okio/-byte-string/as-byte-buffer.html
+$dokka.location:okio/ByteString/base64/#/PointingToDeclaration/okio/okio/-byte-string/base64.html
+$dokka.location:okio/ByteString/base64Url/#/PointingToDeclaration/okio/okio/-byte-string/base64-url.html
+$dokka.location:okio/ByteString/compareTo/#okio.ByteString/PointingToDeclaration/okio/okio/-byte-string/compare-to.html
+$dokka.location:okio/ByteString/copyInto/#kotlin.Int#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/copy-into.html
+$dokka.location:okio/ByteString/endsWith/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-byte-string/ends-with.html
+$dokka.location:okio/ByteString/endsWith/#okio.ByteString/PointingToDeclaration/okio/okio/-byte-string/ends-with.html
+$dokka.location:okio/ByteString/equals/#kotlin.Any?/PointingToDeclaration/okio/okio/-byte-string/equals.html
+$dokka.location:okio/ByteString/get/#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/get.html
+$dokka.location:okio/ByteString/hashCode/#/PointingToDeclaration/okio/okio/-byte-string/hash-code.html
+$dokka.location:okio/ByteString/hex/#/PointingToDeclaration/okio/okio/-byte-string/hex.html
+$dokka.location:okio/ByteString/hmacSha1/#okio.ByteString/PointingToDeclaration/okio/okio/-byte-string/hmac-sha1.html
+$dokka.location:okio/ByteString/hmacSha256/#okio.ByteString/PointingToDeclaration/okio/okio/-byte-string/hmac-sha256.html
+$dokka.location:okio/ByteString/hmacSha512/#okio.ByteString/PointingToDeclaration/okio/okio/-byte-string/hmac-sha512.html
+$dokka.location:okio/ByteString/indexOf/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/index-of.html
+$dokka.location:okio/ByteString/indexOf/#okio.ByteString#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/index-of.html
+$dokka.location:okio/ByteString/lastIndexOf/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/last-index-of.html
+$dokka.location:okio/ByteString/lastIndexOf/#okio.ByteString#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/last-index-of.html
+$dokka.location:okio/ByteString/md5/#/PointingToDeclaration/okio/okio/-byte-string/md5.html
+$dokka.location:okio/ByteString/rangeEquals/#kotlin.Int#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/range-equals.html
+$dokka.location:okio/ByteString/rangeEquals/#kotlin.Int#okio.ByteString#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/range-equals.html
+$dokka.location:okio/ByteString/sha1/#/PointingToDeclaration/okio/okio/-byte-string/sha1.html
+$dokka.location:okio/ByteString/sha256/#/PointingToDeclaration/okio/okio/-byte-string/sha256.html
+$dokka.location:okio/ByteString/sha512/#/PointingToDeclaration/okio/okio/-byte-string/sha512.html
+$dokka.location:okio/ByteString/size/#/PointingToDeclaration/okio/okio/-byte-string/size.html
+$dokka.location:okio/ByteString/startsWith/#kotlin.ByteArray/PointingToDeclaration/okio/okio/-byte-string/starts-with.html
+$dokka.location:okio/ByteString/startsWith/#okio.ByteString/PointingToDeclaration/okio/okio/-byte-string/starts-with.html
+$dokka.location:okio/ByteString/string/#java.nio.charset.Charset/PointingToDeclaration/okio/okio/-byte-string/string.html
+$dokka.location:okio/ByteString/substring/#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-byte-string/substring.html
+$dokka.location:okio/ByteString/toAsciiLowercase/#/PointingToDeclaration/okio/okio/-byte-string/to-ascii-lowercase.html
+$dokka.location:okio/ByteString/toAsciiUppercase/#/PointingToDeclaration/okio/okio/-byte-string/to-ascii-uppercase.html
+$dokka.location:okio/ByteString/toByteArray/#/PointingToDeclaration/okio/okio/-byte-string/to-byte-array.html
+$dokka.location:okio/ByteString/toString/#/PointingToDeclaration/okio/okio/-byte-string/to-string.html
+$dokka.location:okio/ByteString/utf8/#/PointingToDeclaration/okio/okio/-byte-string/utf8.html
+$dokka.location:okio/ByteString/write/#java.io.OutputStream/PointingToDeclaration/okio/okio/-byte-string/write.html
+$dokka.location:okio/CipherSink///PointingToDeclaration/okio/okio/-cipher-sink/index.html
+$dokka.location:okio/CipherSink/CipherSink/#okio.BufferedSink#javax.crypto.Cipher/PointingToDeclaration/okio/okio/-cipher-sink/-cipher-sink.html
+$dokka.location:okio/CipherSink/cipher/#/PointingToDeclaration/okio/okio/-cipher-sink/cipher.html
+$dokka.location:okio/CipherSink/close/#/PointingToDeclaration/okio/okio/-cipher-sink/close.html
+$dokka.location:okio/CipherSink/flush/#/PointingToDeclaration/okio/okio/-cipher-sink/flush.html
+$dokka.location:okio/CipherSink/timeout/#/PointingToDeclaration/okio/okio/-cipher-sink/timeout.html
+$dokka.location:okio/CipherSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-cipher-sink/write.html
+$dokka.location:okio/CipherSource///PointingToDeclaration/okio/okio/-cipher-source/index.html
+$dokka.location:okio/CipherSource/CipherSource/#okio.BufferedSource#javax.crypto.Cipher/PointingToDeclaration/okio/okio/-cipher-source/-cipher-source.html
+$dokka.location:okio/CipherSource/cipher/#/PointingToDeclaration/okio/okio/-cipher-source/cipher.html
+$dokka.location:okio/CipherSource/close/#/PointingToDeclaration/okio/okio/-cipher-source/close.html
+$dokka.location:okio/CipherSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-cipher-source/read.html
+$dokka.location:okio/CipherSource/timeout/#/PointingToDeclaration/okio/okio/-cipher-source/timeout.html
+$dokka.location:okio/Closeable///PointingToDeclaration/okio/okio/-closeable/index.html
+$dokka.location:okio/Closeable/close/#/PointingToDeclaration/okio/okio/-closeable/close.html
+$dokka.location:okio/DeflaterSink///PointingToDeclaration/okio/okio/-deflater-sink/index.html
+$dokka.location:okio/DeflaterSink/DeflaterSink/#okio.Sink#java.util.zip.Deflater/PointingToDeclaration/okio/okio/-deflater-sink/-deflater-sink.html
+$dokka.location:okio/DeflaterSink/close/#/PointingToDeclaration/okio/okio/-deflater-sink/close.html
+$dokka.location:okio/DeflaterSink/flush/#/PointingToDeclaration/okio/okio/-deflater-sink/flush.html
+$dokka.location:okio/DeflaterSink/timeout/#/PointingToDeclaration/okio/okio/-deflater-sink/timeout.html
+$dokka.location:okio/DeflaterSink/toString/#/PointingToDeclaration/okio/okio/-deflater-sink/to-string.html
+$dokka.location:okio/DeflaterSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-deflater-sink/write.html
+$dokka.location:okio/EOFException///PointingToDeclaration/okio/okio/-e-o-f-exception/index.html
+$dokka.location:okio/EOFException/EOFException/#kotlin.String?/PointingToDeclaration/okio/okio/-e-o-f-exception/-e-o-f-exception.html
+$dokka.location:okio/FileHandle///PointingToDeclaration/okio/okio/-file-handle/index.html
+$dokka.location:okio/FileHandle/FileHandle/#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-handle/-file-handle.html
+$dokka.location:okio/FileHandle/appendingSink/#/PointingToDeclaration/okio/okio/-file-handle/appending-sink.html
+$dokka.location:okio/FileHandle/close/#/PointingToDeclaration/okio/okio/-file-handle/close.html
+$dokka.location:okio/FileHandle/flush/#/PointingToDeclaration/okio/okio/-file-handle/flush.html
+$dokka.location:okio/FileHandle/lock/#/PointingToDeclaration/okio/okio/-file-handle/lock.html
+$dokka.location:okio/FileHandle/position/#okio.Sink/PointingToDeclaration/okio/okio/-file-handle/position.html
+$dokka.location:okio/FileHandle/position/#okio.Source/PointingToDeclaration/okio/okio/-file-handle/position.html
+$dokka.location:okio/FileHandle/read/#kotlin.Long#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-file-handle/read.html
+$dokka.location:okio/FileHandle/read/#kotlin.Long#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/read.html
+$dokka.location:okio/FileHandle/readWrite/#/PointingToDeclaration/okio/okio/-file-handle/read-write.html
+$dokka.location:okio/FileHandle/reposition/#okio.Sink#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/reposition.html
+$dokka.location:okio/FileHandle/reposition/#okio.Source#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/reposition.html
+$dokka.location:okio/FileHandle/resize/#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/resize.html
+$dokka.location:okio/FileHandle/sink/#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/sink.html
+$dokka.location:okio/FileHandle/size/#/PointingToDeclaration/okio/okio/-file-handle/size.html
+$dokka.location:okio/FileHandle/source/#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/source.html
+$dokka.location:okio/FileHandle/write/#kotlin.Long#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/okio/okio/-file-handle/write.html
+$dokka.location:okio/FileHandle/write/#kotlin.Long#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-file-handle/write.html
+$dokka.location:okio/FileMetadata///PointingToDeclaration/okio/okio/-file-metadata/index.html
+$dokka.location:okio/FileMetadata/FileMetadata/#kotlin.Boolean#kotlin.Boolean#okio.Path?#kotlin.Long?#kotlin.Long?#kotlin.Long?#kotlin.Long?#kotlin.collections.Map[kotlin.reflect.KClass[*],kotlin.Any]/PointingToDeclaration/okio/okio/-file-metadata/-file-metadata.html
+$dokka.location:okio/FileMetadata/copy/#kotlin.Boolean#kotlin.Boolean#okio.Path?#kotlin.Long?#kotlin.Long?#kotlin.Long?#kotlin.Long?#kotlin.collections.Map[kotlin.reflect.KClass[*],kotlin.Any]/PointingToDeclaration/okio/okio/-file-metadata/copy.html
+$dokka.location:okio/FileMetadata/createdAtMillis/#/PointingToDeclaration/okio/okio/-file-metadata/created-at-millis.html
+$dokka.location:okio/FileMetadata/extra/#kotlin.reflect.KClass[TypeParam(bounds=[kotlin.Any])]/PointingToDeclaration/okio/okio/-file-metadata/extra.html
+$dokka.location:okio/FileMetadata/extras/#/PointingToDeclaration/okio/okio/-file-metadata/extras.html
+$dokka.location:okio/FileMetadata/isDirectory/#/PointingToDeclaration/okio/okio/-file-metadata/is-directory.html
+$dokka.location:okio/FileMetadata/isRegularFile/#/PointingToDeclaration/okio/okio/-file-metadata/is-regular-file.html
+$dokka.location:okio/FileMetadata/lastAccessedAtMillis/#/PointingToDeclaration/okio/okio/-file-metadata/last-accessed-at-millis.html
+$dokka.location:okio/FileMetadata/lastModifiedAtMillis/#/PointingToDeclaration/okio/okio/-file-metadata/last-modified-at-millis.html
+$dokka.location:okio/FileMetadata/size/#/PointingToDeclaration/okio/okio/-file-metadata/size.html
+$dokka.location:okio/FileMetadata/symlinkTarget/#/PointingToDeclaration/okio/okio/-file-metadata/symlink-target.html
+$dokka.location:okio/FileMetadata/toString/#/PointingToDeclaration/okio/okio/-file-metadata/to-string.html
+$dokka.location:okio/FileNotFoundException///PointingToDeclaration/okio/okio/-file-not-found-exception/index.html
+$dokka.location:okio/FileNotFoundException/FileNotFoundException/#kotlin.String?/PointingToDeclaration/okio/okio/-file-not-found-exception/-file-not-found-exception.html
+$dokka.location:okio/FileSystem.Companion///PointingToDeclaration/okio/okio/-file-system/-companion/index.html
+$dokka.location:okio/FileSystem.Companion/RESOURCES/#/PointingToDeclaration/okio/okio/-file-system/-companion/-r-e-s-o-u-r-c-e-s.html
+$dokka.location:okio/FileSystem.Companion/SYSTEM/#/PointingToDeclaration/okio/okio/-file-system/-companion/[native]-s-y-s-t-e-m.html
+$dokka.location:okio/FileSystem.Companion/SYSTEM_TEMPORARY_DIRECTORY/#/PointingToDeclaration/okio/okio/-file-system/-companion/-s-y-s-t-e-m_-t-e-m-p-o-r-a-r-y_-d-i-r-e-c-t-o-r-y.html
+$dokka.location:okio/FileSystem.Companion/asOkioFileSystem/java.nio.file.FileSystem#/PointingToDeclaration/okio/okio/-file-system/-companion/as-okio-file-system.html
+$dokka.location:okio/FileSystem///PointingToDeclaration/okio/okio/-file-system/index.html
+$dokka.location:okio/FileSystem/FileSystem/#/PointingToDeclaration/okio/okio/-file-system/-file-system.html
+$dokka.location:okio/FileSystem/appendingSink/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/appending-sink.html
+$dokka.location:okio/FileSystem/appendingSink/#okio.Path/PointingToDeclaration/okio/okio/-file-system/appending-sink.html
+$dokka.location:okio/FileSystem/atomicMove/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-file-system/atomic-move.html
+$dokka.location:okio/FileSystem/canonicalize/#okio.Path/PointingToDeclaration/okio/okio/-file-system/canonicalize.html
+$dokka.location:okio/FileSystem/copy/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-file-system/copy.html
+$dokka.location:okio/FileSystem/createDirectories/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/create-directories.html
+$dokka.location:okio/FileSystem/createDirectories/#okio.Path/PointingToDeclaration/okio/okio/-file-system/create-directories.html
+$dokka.location:okio/FileSystem/createDirectory/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/create-directory.html
+$dokka.location:okio/FileSystem/createDirectory/#okio.Path/PointingToDeclaration/okio/okio/-file-system/create-directory.html
+$dokka.location:okio/FileSystem/createSymlink/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-file-system/create-symlink.html
+$dokka.location:okio/FileSystem/delete/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/delete.html
+$dokka.location:okio/FileSystem/delete/#okio.Path/PointingToDeclaration/okio/okio/-file-system/delete.html
+$dokka.location:okio/FileSystem/deleteRecursively/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/delete-recursively.html
+$dokka.location:okio/FileSystem/deleteRecursively/#okio.Path/PointingToDeclaration/okio/okio/-file-system/delete-recursively.html
+$dokka.location:okio/FileSystem/exists/#okio.Path/PointingToDeclaration/okio/okio/-file-system/exists.html
+$dokka.location:okio/FileSystem/list/#okio.Path/PointingToDeclaration/okio/okio/-file-system/list.html
+$dokka.location:okio/FileSystem/listOrNull/#okio.Path/PointingToDeclaration/okio/okio/-file-system/list-or-null.html
+$dokka.location:okio/FileSystem/listRecursively/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/list-recursively.html
+$dokka.location:okio/FileSystem/listRecursively/#okio.Path/PointingToDeclaration/okio/okio/-file-system/list-recursively.html
+$dokka.location:okio/FileSystem/metadata/#okio.Path/PointingToDeclaration/okio/okio/-file-system/metadata.html
+$dokka.location:okio/FileSystem/metadataOrNull/#okio.Path/PointingToDeclaration/okio/okio/-file-system/metadata-or-null.html
+$dokka.location:okio/FileSystem/openReadOnly/#okio.Path/PointingToDeclaration/okio/okio/-file-system/open-read-only.html
+$dokka.location:okio/FileSystem/openReadWrite/#okio.Path#kotlin.Boolean#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/open-read-write.html
+$dokka.location:okio/FileSystem/openReadWrite/#okio.Path/PointingToDeclaration/okio/okio/-file-system/open-read-write.html
+$dokka.location:okio/FileSystem/read/#okio.Path#kotlin.Function1[okio.BufferedSource,TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/-file-system/read.html
+$dokka.location:okio/FileSystem/sink/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-file-system/sink.html
+$dokka.location:okio/FileSystem/sink/#okio.Path/PointingToDeclaration/okio/okio/-file-system/sink.html
+$dokka.location:okio/FileSystem/source/#okio.Path/PointingToDeclaration/okio/okio/-file-system/source.html
+$dokka.location:okio/FileSystem/write/#okio.Path#kotlin.Boolean#kotlin.Function1[okio.BufferedSink,TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/-file-system/write.html
+$dokka.location:okio/ForwardingFileSystem///PointingToDeclaration/okio/okio/-forwarding-file-system/index.html
+$dokka.location:okio/ForwardingFileSystem/ForwardingFileSystem/#okio.FileSystem/PointingToDeclaration/okio/okio/-forwarding-file-system/-forwarding-file-system.html
+$dokka.location:okio/ForwardingFileSystem/appendingSink/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/appending-sink.html
+$dokka.location:okio/ForwardingFileSystem/atomicMove/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/atomic-move.html
+$dokka.location:okio/ForwardingFileSystem/canonicalize/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/canonicalize.html
+$dokka.location:okio/ForwardingFileSystem/createDirectory/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/create-directory.html
+$dokka.location:okio/ForwardingFileSystem/createSymlink/#okio.Path#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/create-symlink.html
+$dokka.location:okio/ForwardingFileSystem/delegate/#/PointingToDeclaration/okio/okio/-forwarding-file-system/delegate.html
+$dokka.location:okio/ForwardingFileSystem/delete/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/delete.html
+$dokka.location:okio/ForwardingFileSystem/list/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/list.html
+$dokka.location:okio/ForwardingFileSystem/listOrNull/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/list-or-null.html
+$dokka.location:okio/ForwardingFileSystem/listRecursively/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/list-recursively.html
+$dokka.location:okio/ForwardingFileSystem/metadataOrNull/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/metadata-or-null.html
+$dokka.location:okio/ForwardingFileSystem/onPathParameter/#okio.Path#kotlin.String#kotlin.String/PointingToDeclaration/okio/okio/-forwarding-file-system/on-path-parameter.html
+$dokka.location:okio/ForwardingFileSystem/onPathResult/#okio.Path#kotlin.String/PointingToDeclaration/okio/okio/-forwarding-file-system/on-path-result.html
+$dokka.location:okio/ForwardingFileSystem/openReadOnly/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/open-read-only.html
+$dokka.location:okio/ForwardingFileSystem/openReadWrite/#okio.Path#kotlin.Boolean#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/open-read-write.html
+$dokka.location:okio/ForwardingFileSystem/sink/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-forwarding-file-system/sink.html
+$dokka.location:okio/ForwardingFileSystem/source/#okio.Path/PointingToDeclaration/okio/okio/-forwarding-file-system/source.html
+$dokka.location:okio/ForwardingFileSystem/toString/#/PointingToDeclaration/okio/okio/-forwarding-file-system/to-string.html
+$dokka.location:okio/ForwardingSink///PointingToDeclaration/okio/okio/-forwarding-sink/index.html
+$dokka.location:okio/ForwardingSink/ForwardingSink/#okio.Sink/PointingToDeclaration/okio/okio/-forwarding-sink/-forwarding-sink.html
+$dokka.location:okio/ForwardingSink/close/#/PointingToDeclaration/okio/okio/-forwarding-sink/close.html
+$dokka.location:okio/ForwardingSink/delegate/#/PointingToDeclaration/okio/okio/-forwarding-sink/delegate.html
+$dokka.location:okio/ForwardingSink/flush/#/PointingToDeclaration/okio/okio/-forwarding-sink/flush.html
+$dokka.location:okio/ForwardingSink/timeout/#/PointingToDeclaration/okio/okio/-forwarding-sink/timeout.html
+$dokka.location:okio/ForwardingSink/toString/#/PointingToDeclaration/okio/okio/-forwarding-sink/to-string.html
+$dokka.location:okio/ForwardingSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-forwarding-sink/write.html
+$dokka.location:okio/ForwardingSource///PointingToDeclaration/okio/okio/-forwarding-source/index.html
+$dokka.location:okio/ForwardingSource/ForwardingSource/#okio.Source/PointingToDeclaration/okio/okio/-forwarding-source/-forwarding-source.html
+$dokka.location:okio/ForwardingSource/close/#/PointingToDeclaration/okio/okio/-forwarding-source/close.html
+$dokka.location:okio/ForwardingSource/delegate/#/PointingToDeclaration/okio/okio/-forwarding-source/delegate.html
+$dokka.location:okio/ForwardingSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-forwarding-source/read.html
+$dokka.location:okio/ForwardingSource/timeout/#/PointingToDeclaration/okio/okio/-forwarding-source/timeout.html
+$dokka.location:okio/ForwardingSource/toString/#/PointingToDeclaration/okio/okio/-forwarding-source/to-string.html
+$dokka.location:okio/ForwardingTimeout///PointingToDeclaration/okio/okio/-forwarding-timeout/index.html
+$dokka.location:okio/ForwardingTimeout/ForwardingTimeout/#okio.Timeout/PointingToDeclaration/okio/okio/-forwarding-timeout/-forwarding-timeout.html
+$dokka.location:okio/ForwardingTimeout/clearDeadline/#/PointingToDeclaration/okio/okio/-forwarding-timeout/clear-deadline.html
+$dokka.location:okio/ForwardingTimeout/clearTimeout/#/PointingToDeclaration/okio/okio/-forwarding-timeout/clear-timeout.html
+$dokka.location:okio/ForwardingTimeout/deadlineNanoTime/#/PointingToDeclaration/okio/okio/-forwarding-timeout/deadline-nano-time.html
+$dokka.location:okio/ForwardingTimeout/deadlineNanoTime/#kotlin.Long/PointingToDeclaration/okio/okio/-forwarding-timeout/deadline-nano-time.html
+$dokka.location:okio/ForwardingTimeout/delegate/#/PointingToDeclaration/okio/okio/-forwarding-timeout/delegate.html
+$dokka.location:okio/ForwardingTimeout/hasDeadline/#/PointingToDeclaration/okio/okio/-forwarding-timeout/has-deadline.html
+$dokka.location:okio/ForwardingTimeout/setDelegate/#okio.Timeout/PointingToDeclaration/okio/okio/-forwarding-timeout/set-delegate.html
+$dokka.location:okio/ForwardingTimeout/throwIfReached/#/PointingToDeclaration/okio/okio/-forwarding-timeout/throw-if-reached.html
+$dokka.location:okio/ForwardingTimeout/timeout/#kotlin.Long#java.util.concurrent.TimeUnit/PointingToDeclaration/okio/okio/-forwarding-timeout/timeout.html
+$dokka.location:okio/ForwardingTimeout/timeoutNanos/#/PointingToDeclaration/okio/okio/-forwarding-timeout/timeout-nanos.html
+$dokka.location:okio/GzipSink///PointingToDeclaration/okio/okio/-gzip-sink/index.html
+$dokka.location:okio/GzipSink/GzipSink/#okio.Sink/PointingToDeclaration/okio/okio/-gzip-sink/-gzip-sink.html
+$dokka.location:okio/GzipSink/close/#/PointingToDeclaration/okio/okio/-gzip-sink/close.html
+$dokka.location:okio/GzipSink/deflater/#/PointingToDeclaration/okio/okio/-gzip-sink/deflater.html
+$dokka.location:okio/GzipSink/flush/#/PointingToDeclaration/okio/okio/-gzip-sink/flush.html
+$dokka.location:okio/GzipSink/timeout/#/PointingToDeclaration/okio/okio/-gzip-sink/timeout.html
+$dokka.location:okio/GzipSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-gzip-sink/write.html
+$dokka.location:okio/GzipSource///PointingToDeclaration/okio/okio/-gzip-source/index.html
+$dokka.location:okio/GzipSource/GzipSource/#okio.Source/PointingToDeclaration/okio/okio/-gzip-source/-gzip-source.html
+$dokka.location:okio/GzipSource/close/#/PointingToDeclaration/okio/okio/-gzip-source/close.html
+$dokka.location:okio/GzipSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-gzip-source/read.html
+$dokka.location:okio/GzipSource/timeout/#/PointingToDeclaration/okio/okio/-gzip-source/timeout.html
+$dokka.location:okio/HashingSink.Companion///PointingToDeclaration/okio/okio/-hashing-sink/-companion/index.html
+$dokka.location:okio/HashingSink.Companion/hmacSha1/#okio.Sink#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-sink/-companion/hmac-sha1.html
+$dokka.location:okio/HashingSink.Companion/hmacSha256/#okio.Sink#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-sink/-companion/hmac-sha256.html
+$dokka.location:okio/HashingSink.Companion/hmacSha512/#okio.Sink#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-sink/-companion/hmac-sha512.html
+$dokka.location:okio/HashingSink.Companion/md5/#okio.Sink/PointingToDeclaration/okio/okio/-hashing-sink/-companion/md5.html
+$dokka.location:okio/HashingSink.Companion/sha1/#okio.Sink/PointingToDeclaration/okio/okio/-hashing-sink/-companion/sha1.html
+$dokka.location:okio/HashingSink.Companion/sha256/#okio.Sink/PointingToDeclaration/okio/okio/-hashing-sink/-companion/sha256.html
+$dokka.location:okio/HashingSink.Companion/sha512/#okio.Sink/PointingToDeclaration/okio/okio/-hashing-sink/-companion/sha512.html
+$dokka.location:okio/HashingSink///PointingToDeclaration/okio/okio/-hashing-sink/index.html
+$dokka.location:okio/HashingSink/close/#/PointingToDeclaration/okio/okio/-hashing-sink/close.html
+$dokka.location:okio/HashingSink/flush/#/PointingToDeclaration/okio/okio/-hashing-sink/flush.html
+$dokka.location:okio/HashingSink/hash/#/PointingToDeclaration/okio/okio/-hashing-sink/hash.html
+$dokka.location:okio/HashingSink/timeout/#/PointingToDeclaration/okio/okio/-hashing-sink/timeout.html
+$dokka.location:okio/HashingSink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-hashing-sink/[non-jvm]write.html
+$dokka.location:okio/HashingSource.Companion///PointingToDeclaration/okio/okio/-hashing-source/-companion/index.html
+$dokka.location:okio/HashingSource.Companion/hmacSha1/#okio.Source#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-source/-companion/hmac-sha1.html
+$dokka.location:okio/HashingSource.Companion/hmacSha256/#okio.Source#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-source/-companion/hmac-sha256.html
+$dokka.location:okio/HashingSource.Companion/hmacSha512/#okio.Source#okio.ByteString/PointingToDeclaration/okio/okio/-hashing-source/-companion/hmac-sha512.html
+$dokka.location:okio/HashingSource.Companion/md5/#okio.Source/PointingToDeclaration/okio/okio/-hashing-source/-companion/md5.html
+$dokka.location:okio/HashingSource.Companion/sha1/#okio.Source/PointingToDeclaration/okio/okio/-hashing-source/-companion/sha1.html
+$dokka.location:okio/HashingSource.Companion/sha256/#okio.Source/PointingToDeclaration/okio/okio/-hashing-source/-companion/sha256.html
+$dokka.location:okio/HashingSource.Companion/sha512/#okio.Source/PointingToDeclaration/okio/okio/-hashing-source/-companion/sha512.html
+$dokka.location:okio/HashingSource///PointingToDeclaration/okio/okio/-hashing-source/index.html
+$dokka.location:okio/HashingSource/close/#/PointingToDeclaration/okio/okio/-hashing-source/close.html
+$dokka.location:okio/HashingSource/hash/#/PointingToDeclaration/okio/okio/-hashing-source/hash.html
+$dokka.location:okio/HashingSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-hashing-source/[non-jvm]read.html
+$dokka.location:okio/HashingSource/timeout/#/PointingToDeclaration/okio/okio/-hashing-source/timeout.html
+$dokka.location:okio/IOException///PointingToDeclaration/okio/okio/-i-o-exception/index.html
+$dokka.location:okio/IOException/IOException/#kotlin.String?#kotlin.Throwable?/PointingToDeclaration/okio/okio/-i-o-exception/-i-o-exception.html
+$dokka.location:okio/IOException/IOException/#kotlin.String?/PointingToDeclaration/okio/okio/-i-o-exception/-i-o-exception.html
+$dokka.location:okio/InflaterSource///PointingToDeclaration/okio/okio/-inflater-source/index.html
+$dokka.location:okio/InflaterSource/InflaterSource/#okio.Source#java.util.zip.Inflater/PointingToDeclaration/okio/okio/-inflater-source/-inflater-source.html
+$dokka.location:okio/InflaterSource/close/#/PointingToDeclaration/okio/okio/-inflater-source/close.html
+$dokka.location:okio/InflaterSource/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-inflater-source/read.html
+$dokka.location:okio/InflaterSource/readOrInflate/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-inflater-source/read-or-inflate.html
+$dokka.location:okio/InflaterSource/refill/#/PointingToDeclaration/okio/okio/-inflater-source/refill.html
+$dokka.location:okio/InflaterSource/timeout/#/PointingToDeclaration/okio/okio/-inflater-source/timeout.html
+$dokka.location:okio/Lock.Companion///PointingToDeclaration/okio/okio/-lock/-companion/index.html
+$dokka.location:okio/Lock.Companion/instance/#/PointingToDeclaration/okio/okio/-lock/-companion/instance.html
+$dokka.location:okio/Lock///PointingToDeclaration/okio/okio/-lock/index.html
+$dokka.location:okio/Lock/Lock/#/PointingToDeclaration/okio/okio/-lock/-lock.html
+$dokka.location:okio/Options.Companion///PointingToDeclaration/okio/okio/-options/-companion/index.html
+$dokka.location:okio/Options.Companion/of/#kotlin.Array[okio.ByteString]/PointingToDeclaration/okio/okio/-options/-companion/of.html
+$dokka.location:okio/Options///PointingToDeclaration/okio/okio/-options/index.html
+$dokka.location:okio/Options/get/#kotlin.Int/PointingToDeclaration/okio/okio/-options/get.html
+$dokka.location:okio/Options/size/#/PointingToDeclaration/okio/okio/-options/size.html
+$dokka.location:okio/Path.Companion///PointingToDeclaration/okio/okio/-path/-companion/index.html
+$dokka.location:okio/Path.Companion/DIRECTORY_SEPARATOR/#/PointingToDeclaration/okio/okio/-path/-companion/-d-i-r-e-c-t-o-r-y_-s-e-p-a-r-a-t-o-r.html
+$dokka.location:okio/Path.Companion/toOkioPath/java.io.File#kotlin.Boolean/PointingToDeclaration/okio/okio/-path/-companion/to-okio-path.html
+$dokka.location:okio/Path.Companion/toOkioPath/java.nio.file.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-path/-companion/to-okio-path.html
+$dokka.location:okio/Path.Companion/toPath/kotlin.String#kotlin.Boolean/PointingToDeclaration/okio/okio/-path/-companion/to-path.html
+$dokka.location:okio/Path///PointingToDeclaration/okio/okio/-path/index.html
+$dokka.location:okio/Path/compareTo/#okio.Path/PointingToDeclaration/okio/okio/-path/compare-to.html
+$dokka.location:okio/Path/div/#kotlin.String/PointingToDeclaration/okio/okio/-path/div.html
+$dokka.location:okio/Path/div/#okio.ByteString/PointingToDeclaration/okio/okio/-path/div.html
+$dokka.location:okio/Path/div/#okio.Path/PointingToDeclaration/okio/okio/-path/div.html
+$dokka.location:okio/Path/equals/#kotlin.Any?/PointingToDeclaration/okio/okio/-path/equals.html
+$dokka.location:okio/Path/hashCode/#/PointingToDeclaration/okio/okio/-path/hash-code.html
+$dokka.location:okio/Path/isAbsolute/#/PointingToDeclaration/okio/okio/-path/is-absolute.html
+$dokka.location:okio/Path/isRelative/#/PointingToDeclaration/okio/okio/-path/is-relative.html
+$dokka.location:okio/Path/isRoot/#/PointingToDeclaration/okio/okio/-path/is-root.html
+$dokka.location:okio/Path/name/#/PointingToDeclaration/okio/okio/-path/name.html
+$dokka.location:okio/Path/nameBytes/#/PointingToDeclaration/okio/okio/-path/name-bytes.html
+$dokka.location:okio/Path/normalized/#/PointingToDeclaration/okio/okio/-path/normalized.html
+$dokka.location:okio/Path/parent/#/PointingToDeclaration/okio/okio/-path/parent.html
+$dokka.location:okio/Path/relativeTo/#okio.Path/PointingToDeclaration/okio/okio/-path/relative-to.html
+$dokka.location:okio/Path/resolve/#kotlin.String#kotlin.Boolean/PointingToDeclaration/okio/okio/-path/resolve.html
+$dokka.location:okio/Path/resolve/#okio.ByteString#kotlin.Boolean/PointingToDeclaration/okio/okio/-path/resolve.html
+$dokka.location:okio/Path/resolve/#okio.Path#kotlin.Boolean/PointingToDeclaration/okio/okio/-path/resolve.html
+$dokka.location:okio/Path/root/#/PointingToDeclaration/okio/okio/-path/root.html
+$dokka.location:okio/Path/segments/#/PointingToDeclaration/okio/okio/-path/segments.html
+$dokka.location:okio/Path/segmentsBytes/#/PointingToDeclaration/okio/okio/-path/segments-bytes.html
+$dokka.location:okio/Path/toFile/#/PointingToDeclaration/okio/okio/-path/to-file.html
+$dokka.location:okio/Path/toNioPath/#/PointingToDeclaration/okio/okio/-path/to-nio-path.html
+$dokka.location:okio/Path/toString/#/PointingToDeclaration/okio/okio/-path/to-string.html
+$dokka.location:okio/Path/volumeLetter/#/PointingToDeclaration/okio/okio/-path/volume-letter.html
+$dokka.location:okio/Pipe///PointingToDeclaration/okio/okio/-pipe/index.html
+$dokka.location:okio/Pipe/Pipe/#kotlin.Long/PointingToDeclaration/okio/okio/-pipe/-pipe.html
+$dokka.location:okio/Pipe/cancel/#/PointingToDeclaration/okio/okio/-pipe/cancel.html
+$dokka.location:okio/Pipe/condition/#/PointingToDeclaration/okio/okio/-pipe/condition.html
+$dokka.location:okio/Pipe/fold/#okio.Sink/PointingToDeclaration/okio/okio/-pipe/fold.html
+$dokka.location:okio/Pipe/lock/#/PointingToDeclaration/okio/okio/-pipe/lock.html
+$dokka.location:okio/Pipe/sink/#/PointingToDeclaration/okio/okio/-pipe/sink.html
+$dokka.location:okio/Pipe/source/#/PointingToDeclaration/okio/okio/-pipe/source.html
+$dokka.location:okio/ProtocolException///PointingToDeclaration/okio/okio/-protocol-exception/index.html
+$dokka.location:okio/ProtocolException/ProtocolException/#kotlin.String/PointingToDeclaration/okio/okio/-protocol-exception/-protocol-exception.html
+$dokka.location:okio/Sink///PointingToDeclaration/okio/okio/-sink/index.html
+$dokka.location:okio/Sink/close/#/PointingToDeclaration/okio/okio/-sink/close.html
+$dokka.location:okio/Sink/flush/#/PointingToDeclaration/okio/okio/-sink/flush.html
+$dokka.location:okio/Sink/timeout/#/PointingToDeclaration/okio/okio/-sink/timeout.html
+$dokka.location:okio/Sink/write/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-sink/write.html
+$dokka.location:okio/Source///PointingToDeclaration/okio/okio/-source/index.html
+$dokka.location:okio/Source/close/#/PointingToDeclaration/okio/okio/-source/close.html
+$dokka.location:okio/Source/read/#okio.Buffer#kotlin.Long/PointingToDeclaration/okio/okio/-source/read.html
+$dokka.location:okio/Source/timeout/#/PointingToDeclaration/okio/okio/-source/timeout.html
+$dokka.location:okio/Throttler///PointingToDeclaration/okio/okio/-throttler/index.html
+$dokka.location:okio/Throttler/Throttler/#/PointingToDeclaration/okio/okio/-throttler/-throttler.html
+$dokka.location:okio/Throttler/bytesPerSecond/#kotlin.Long#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-throttler/bytes-per-second.html
+$dokka.location:okio/Throttler/condition/#/PointingToDeclaration/okio/okio/-throttler/condition.html
+$dokka.location:okio/Throttler/lock/#/PointingToDeclaration/okio/okio/-throttler/lock.html
+$dokka.location:okio/Throttler/sink/#okio.Sink/PointingToDeclaration/okio/okio/-throttler/sink.html
+$dokka.location:okio/Throttler/source/#okio.Source/PointingToDeclaration/okio/okio/-throttler/source.html
+$dokka.location:okio/Timeout.Companion///PointingToDeclaration/okio/okio/-timeout/-companion/index.html
+$dokka.location:okio/Timeout.Companion/NONE/#/PointingToDeclaration/okio/okio/-timeout/-companion/-n-o-n-e.html
+$dokka.location:okio/Timeout.Companion/minTimeout/#kotlin.Long#kotlin.Long/PointingToDeclaration/okio/okio/-timeout/-companion/min-timeout.html
+$dokka.location:okio/Timeout///PointingToDeclaration/okio/okio/-timeout/index.html
+$dokka.location:okio/Timeout/Timeout/#/PointingToDeclaration/okio/okio/-timeout/-timeout.html
+$dokka.location:okio/Timeout/awaitSignal/#java.util.concurrent.locks.Condition/PointingToDeclaration/okio/okio/-timeout/await-signal.html
+$dokka.location:okio/Timeout/clearDeadline/#/PointingToDeclaration/okio/okio/-timeout/clear-deadline.html
+$dokka.location:okio/Timeout/clearTimeout/#/PointingToDeclaration/okio/okio/-timeout/clear-timeout.html
+$dokka.location:okio/Timeout/deadline/#kotlin.Long#java.util.concurrent.TimeUnit/PointingToDeclaration/okio/okio/-timeout/deadline.html
+$dokka.location:okio/Timeout/deadlineNanoTime/#/PointingToDeclaration/okio/okio/-timeout/deadline-nano-time.html
+$dokka.location:okio/Timeout/deadlineNanoTime/#kotlin.Long/PointingToDeclaration/okio/okio/-timeout/deadline-nano-time.html
+$dokka.location:okio/Timeout/hasDeadline/#/PointingToDeclaration/okio/okio/-timeout/has-deadline.html
+$dokka.location:okio/Timeout/intersectWith/#okio.Timeout#kotlin.Function0[TypeParam(bounds=[kotlin.Any?])]/PointingToDeclaration/okio/okio/-timeout/intersect-with.html
+$dokka.location:okio/Timeout/throwIfReached/#/PointingToDeclaration/okio/okio/-timeout/throw-if-reached.html
+$dokka.location:okio/Timeout/timeout/#kotlin.Long#java.util.concurrent.TimeUnit/PointingToDeclaration/okio/okio/-timeout/timeout.html
+$dokka.location:okio/Timeout/timeoutNanos/#/PointingToDeclaration/okio/okio/-timeout/timeout-nanos.html
+$dokka.location:okio/Timeout/waitUntilNotified/#kotlin.Any/PointingToDeclaration/okio/okio/-timeout/wait-until-notified.html
+okio
+
diff --git a/formats/json-tests/build.gradle.kts b/formats/json-tests/build.gradle.kts
new file mode 100644
index 0000000..6be0a3a
--- /dev/null
+++ b/formats/json-tests/build.gradle.kts
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import Java9Modularity.configureJava9ModuleInfo
+import org.jetbrains.kotlin.gradle.targets.js.testing.*
+
+plugins {
+ kotlin("multiplatform")
+ kotlin("plugin.serialization")
+}
+
+apply(from = rootProject.file("gradle/native-targets.gradle"))
+apply(from = rootProject.file("gradle/configure-source-sets.gradle"))
+
+// disable kover tasks because there are no non-test classes in the project
+tasks.named("koverHtmlReport") {
+ enabled = false
+}
+tasks.named("koverXmlReport") {
+ enabled = false
+}
+tasks.named("koverVerify") {
+ enabled = false
+}
+
+kotlin {
+ sourceSets {
+ configureEach {
+ languageSettings {
+ optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
+ }
+ }
+ val commonTest by getting {
+ dependencies {
+ api(project(":kotlinx-serialization-json"))
+ api(project(":kotlinx-serialization-json-okio"))
+ implementation("com.squareup.okio:okio:${property("okio_version")}")
+ }
+ }
+
+ val jvmTest by getting {
+ dependencies {
+ implementation("com.google.code.gson:gson:2.8.5")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${property("coroutines_version")}")
+ }
+ }
+ }
+}
+
+project.configureJava9ModuleInfo()
+
+// TODO: Remove this after okio will be updated to the version with 1.9.20 stdlib dependency
+configurations.all {
+ resolutionStrategy.eachDependency {
+ if (requested.name == "kotlin-stdlib-wasm") {
+ useTarget("org.jetbrains.kotlin:kotlin-stdlib-wasm-js:${requested.version}")
+ }
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
similarity index 96%
rename from formats/json/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
index e8a0c48..73d3319 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
@@ -10,7 +10,6 @@
@Serializable(with = BoxSerializer::class)
class Box(val i: Int)
- @Serializer(forClass = Box::class)
object BoxSerializer : KSerializer<Box> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Box") {
element<Int>("i")
diff --git a/formats/json/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
similarity index 97%
rename from formats/json/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
index c7350ce..d361bbb 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
@@ -45,7 +45,6 @@
TWO
}
- @Serializer(WithCustom::class)
private class CustomEnumSerializer : KSerializer<WithCustom> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("WithCustom", SerialKind.ENUM) {
element("1", buildSerialDescriptor("WithCustom.1", StructureKind.OBJECT))
@@ -127,7 +126,7 @@
fun testStructurallyEqualDescriptors() {
val libraryGenerated = Wrapper.serializer().descriptor.getElementDescriptor(0)
val codeGenerated = MyEnum2.serializer().descriptor
- assertNotEquals(libraryGenerated::class, codeGenerated::class)
+ assertEquals(libraryGenerated::class, codeGenerated::class)
libraryGenerated.assertDescriptorEqualsTo(codeGenerated)
}
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/JsonPathTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/JsonPathTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
similarity index 96%
rename from formats/json/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
index b44a37f..e090347 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
@@ -53,7 +53,6 @@
@Serializable
data class Holder(val nullable: Int?, val nonNullable: Int)
- @Serializer(forClass = Int::class)
object NonNullableIntSerializer : KSerializer<Int> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("NotNullIntSerializer", PrimitiveKind.INT)
@@ -86,7 +85,11 @@
val json = Json { serializersModule = module }
assertEquals("""{"nullable":null,"nonNullable":"foo"}""", json.encodeToString(FileContextualHolder(null, FileContextualType("foo"))))
- assertEquals("""{"nullable":"foo","nonNullable":"bar"}""", json.encodeToString(FileContextualHolder(FileContextualType("foo"), FileContextualType("bar"))))
+ assertEquals("""{"nullable":"foo","nonNullable":"bar"}""", json.encodeToString(
+ FileContextualHolder(
+ FileContextualType("foo"), FileContextualType("bar")
+ )
+ ))
assertEquals(FileContextualHolder(null, FileContextualType("foo")), json.decodeFromString("""{"nullable":null,"nonNullable":"foo"}"""))
assertEquals(FileContextualHolder(FileContextualType("foo"), FileContextualType("bar")), json.decodeFromString("""{"nullable":"foo","nonNullable":"bar"}"""))
diff --git a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
similarity index 98%
rename from formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 6d4d42f..a185ccb 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -37,7 +37,6 @@
@Serializable
data class PolyDerived(val s: String) : PolyBase(1)
-@SharedImmutable
val BaseAndDerivedModule = SerializersModule {
polymorphic(PolyBase::class, PolyBase.serializer()) {
subclass(PolyDerived.serializer())
diff --git a/formats/json/commonTest/src/kotlinx/serialization/SerializableClasses.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializableClasses.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/SerializableClasses.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/SerializableClasses.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/SerializableOnPropertyTypeAndTypealiasTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializableOnPropertyTypeAndTypealiasTest.kt
new file mode 100644
index 0000000..7c7133c
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializableOnPropertyTypeAndTypealiasTest.kt
@@ -0,0 +1,93 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+@Serializable
+data class WithDefault(val s: String)
+
+@Serializable(SerializerA::class)
+data class WithoutDefault(val s: String)
+
+object SerializerA : KSerializer<WithoutDefault> {
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("Bruh", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: WithoutDefault) {
+ encoder.encodeString(value.s)
+ }
+
+ override fun deserialize(decoder: Decoder): WithoutDefault {
+ return WithoutDefault(decoder.decodeString())
+ }
+}
+
+object SerializerB : KSerializer<WithoutDefault> {
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("Bruh", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: WithoutDefault) {
+ encoder.encodeString(value.s + "#")
+ }
+
+ override fun deserialize(decoder: Decoder): WithoutDefault {
+ return WithoutDefault(decoder.decodeString().removeSuffix("#"))
+ }
+}
+
+object SerializerC : KSerializer<WithDefault> {
+ override val descriptor: SerialDescriptor
+ get() = PrimitiveSerialDescriptor("Bruh", PrimitiveKind.STRING)
+
+ override fun serialize(encoder: Encoder, value: WithDefault) {
+ encoder.encodeString(value.s + "#")
+ }
+
+ override fun deserialize(decoder: Decoder): WithDefault {
+ return WithDefault(decoder.decodeString().removeSuffix("#"))
+ }
+}
+
+typealias WithoutDefaultAlias = @Serializable(SerializerB::class) WithoutDefault
+typealias WithDefaultAlias = @Serializable(SerializerC::class) WithDefault
+
+@Serializable
+data class TesterWithoutDefault(
+ val b1: WithoutDefault,
+ @Serializable(SerializerB::class) val b2: WithoutDefault,
+ val b3: @Serializable(SerializerB::class) WithoutDefault,
+ val b4: WithoutDefaultAlias
+)
+
+@Serializable
+data class TesterWithDefault(
+ val b1: WithDefault,
+ @Serializable(SerializerC::class) val b2: WithDefault,
+ val b3: @Serializable(SerializerC::class) WithDefault,
+ val b4: WithDefaultAlias
+)
+
+class SerializableOnPropertyTypeAndTypealiasTest : JsonTestBase() {
+
+ @Test
+ fun testWithDefault() {
+ val t = TesterWithDefault(WithDefault("a"), WithDefault("b"), WithDefault("c"), WithDefault("d"))
+ assertJsonFormAndRestored(
+ TesterWithDefault.serializer(),
+ t,
+ """{"b1":{"s":"a"},"b2":"b#","b3":"c#","b4":"d#"}"""
+ )
+ }
+
+ @Test
+ fun testWithoutDefault() { // Ignored by #1895
+ val t = TesterWithoutDefault(WithoutDefault("a"), WithoutDefault("b"), WithoutDefault("c"), WithoutDefault("d"))
+ assertJsonFormAndRestored(
+ TesterWithoutDefault.serializer(),
+ t,
+ """{"b1":"a","b2":"b#","b3":"c#","b4":"d#"}"""
+ )
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
similarity index 96%
rename from formats/json/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
index 9f838b1..42f2a85 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
@@ -16,7 +16,6 @@
@Serializable
data class Holder(val nullable: Int?, val nonNullable: Int)
- @Serializer(forClass = Int::class)
object NullableIntSerializer : KSerializer<Int?> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("NullableIntSerializer", PrimitiveKind.INT).nullable
@@ -34,7 +33,6 @@
}
}
- @Serializer(forClass = Int::class)
object NonNullableIntSerializer : KSerializer<Int> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("NotNullIntSerializer", PrimitiveKind.INT)
diff --git a/formats/json/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
similarity index 96%
rename from formats/json/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
index 6446167..98f3f5e 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
@@ -16,7 +16,6 @@
@Serializable(with = StringHolderSerializer::class)
data class StringHolder(val s: String)
- @Serializer(forClass = StringHolder::class)
object StringHolderSerializer : KSerializer<StringHolder?> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SHS", PrimitiveKind.STRING).nullable
@@ -113,7 +112,6 @@
@Test
fun testGenericBoxNullable() {
- if (isJsLegacy()) return
val data = GenericBox<StringHolder?>(null)
val json = Json.encodeToString(data)
assertEquals("""{"value":"nullable"}""", Json.encodeToString(data))
@@ -122,13 +120,11 @@
@Test
fun testGenericNullableBoxFromNull() {
- if (isJsLegacy()) return
assertEquals(GenericBox(StringHolder("nullable")), Json.decodeFromString("""{"value":null}"""))
}
@Test
fun testGenericNullableBoxNullable() {
- if (isJsLegacy()) return
val data = GenericNullableBox<StringHolder>(null)
val json = Json.encodeToString(data)
assertEquals("""{"value":"nullable"}""", Json.encodeToString(data))
@@ -137,7 +133,6 @@
@Test
fun testGenericBoxNullableFromNull() {
- if (isJsLegacy()) return
assertEquals(GenericNullableBox(StringHolder("nullable")), Json.decodeFromString("""{"value":null}"""))
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt
similarity index 73%
rename from formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt
index 18a6940..4b4aebf 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt
@@ -14,6 +14,7 @@
import kotlinx.serialization.test.*
import kotlin.reflect.*
import kotlin.test.*
+import kotlin.time.Duration
@Suppress("RemoveExplicitTypeArguments") // This is exactly what's being tested
class SerializersLookupTest : JsonTestBase() {
@@ -51,6 +52,22 @@
assertSerializedWithType("""["a","b","c"]""", myArr)
}
+ @Test
+ fun testUnsigned() {
+ assertSame(UByte.serializer(), serializer<UByte>())
+ assertSame(UShort.serializer(), serializer<UShort>())
+ assertSame(UInt.serializer(), serializer<UInt>())
+ assertSame(ULong.serializer(), serializer<ULong>())
+ }
+
+ @Test
+ @OptIn(ExperimentalUnsignedTypes::class)
+ fun testUnsignedArrays() {
+ assertSame(UByteArraySerializer(), serializer<UByteArray>())
+ assertSame(UShortArraySerializer(), serializer<UShortArray>())
+ assertSame(UIntArraySerializer(), serializer<UIntArray>())
+ assertSame(ULongArraySerializer(), serializer<ULongArray>())
+ }
@Test
fun testPrimitiveSet() {
@@ -86,6 +103,20 @@
}
@Test
+ fun testStarProjectionsAreProhibited() {
+ val expectedMessage = "Star projections in type arguments are not allowed"
+ assertFailsWithMessage<IllegalArgumentException>(expectedMessage) {
+ serializer<Box<*>>()
+ }
+ assertFailsWithMessage<IllegalArgumentException>(expectedMessage) {
+ serializer(typeOf<Box<*>>())
+ }
+ assertFailsWithMessage<IllegalArgumentException>(expectedMessage) {
+ serializerOrNull(typeOf<Box<*>>())
+ }
+ }
+
+ @Test
fun testNullableTypes() {
val myList: List<Int?> = listOf(1, null, 3)
assertSerializedWithType("[1,null,3]", myList)
@@ -99,13 +130,19 @@
}
@Test
- fun testTriple() = noLegacyJs { // because of Box
+ fun testTriple() {
val myTriple = Triple("1", 2, Box(42))
assertSerializedWithType("""{"first":"1","second":2,"third":{"boxed":42}}""", myTriple)
}
@Test
- fun testCustomGeneric() = noLegacyJs {
+ fun testLookupDuration() {
+ assertNotNull(serializerOrNull(typeOf<Duration>()))
+ assertSame(Duration.serializer(), serializer<Duration>())
+ }
+
+ @Test
+ fun testCustomGeneric() {
val intBox = Box(42)
val intBoxSerializer = serializer<Box<Int>>()
assertEquals(Box.serializer(Int.serializer()).descriptor, intBoxSerializer.descriptor)
@@ -115,13 +152,13 @@
}
@Test
- fun testRecursiveGeneric() = noLegacyJs {
+ fun testRecursiveGeneric() {
val boxBox = Box(Box(Box(IntData(42))))
assertSerializedWithType("""{"boxed":{"boxed":{"boxed":{"intV":42}}}}""", boxBox)
}
@Test
- fun testMixedGeneric() = noLegacyJs {
+ fun testMixedGeneric() {
val listOfBoxes = listOf(Box("foo"), Box("bar"))
assertSerializedWithType("""[{"boxed":"foo"},{"boxed":"bar"}]""", listOfBoxes)
val boxedList = Box(listOf("foo", "bar"))
@@ -133,10 +170,8 @@
assertSerializedWithType("[1,2,3]", Array<Int>(3) { it + 1 }, default)
assertSerializedWithType("""["1","2","3"]""", Array<String>(3) { (it + 1).toString() }, default)
assertSerializedWithType("[[0],[1],[2]]", Array<Array<Int>>(3) { cnt -> Array(1) { cnt } }, default)
- noLegacyJs {
- assertSerializedWithType("""[{"boxed":"foo"}]""", Array(1) { Box("foo") }, default)
- assertSerializedWithType("""[[{"boxed":"foo"}]]""", Array(1) { Array(1) { Box("foo") } }, default)
- }
+ assertSerializedWithType("""[{"boxed":"foo"}]""", Array(1) { Box("foo") }, default)
+ assertSerializedWithType("""[[{"boxed":"foo"}]]""", Array(1) { Array(1) { Box("foo") } }, default)
}
@Test
@@ -150,7 +185,7 @@
}
@Test
- fun testSerializableObject() = noLegacyJs {
+ fun testSerializableObject() {
assertSerializedWithType("{}", SampleObject)
}
@@ -174,6 +209,24 @@
}
}
+ class GenericHolder<T>(value: T)
+
+ class GenericSerializer<T>(typeSerializer: KSerializer<T>) : KSerializer<GenericHolder<T>> {
+ override val descriptor: SerialDescriptor =
+ PrimitiveSerialDescriptor(
+ "Generic Serializer parametrized by ${typeSerializer.descriptor}",
+ PrimitiveKind.STRING
+ )
+
+ override fun deserialize(decoder: Decoder): GenericHolder<T> {
+ TODO()
+ }
+
+ override fun serialize(encoder: Encoder, value: GenericHolder<T>) {
+ TODO()
+ }
+ }
+
@Test
fun testContextualLookup() {
val module = SerializersModule { contextual(CustomIntSerializer(false).cast<IntBox>()) }
@@ -183,6 +236,25 @@
}
@Test
+ fun testGenericOfContextual() {
+ val module = SerializersModule {
+ contextual(CustomIntSerializer(false).cast<IntBox>())
+ contextual(GenericHolder::class) { args -> GenericSerializer(args[0]) }
+ }
+
+ val listSerializer = module.serializerOrNull(typeOf<List<IntBox>>())
+ assertNotNull(listSerializer)
+ assertEquals("kotlin.collections.ArrayList(PrimitiveDescriptor(CIS))", listSerializer.descriptor.toString())
+
+ val genericSerializer = module.serializerOrNull(typeOf<GenericHolder<IntBox>>())
+ assertNotNull(genericSerializer)
+ assertEquals(
+ "PrimitiveDescriptor(Generic Serializer parametrized by PrimitiveDescriptor(CIS))",
+ genericSerializer.descriptor.toString()
+ )
+ }
+
+ @Test
fun testContextualLookupNullable() {
val module = SerializersModule { contextual(CustomIntSerializer(true).cast<IntBox>()) }
val serializer = module.serializer<List<List<IntBox?>>>()
diff --git a/formats/json/commonTest/src/kotlinx/serialization/TuplesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/TuplesTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/TuplesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/TuplesTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
similarity index 76%
rename from formats/json/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
index 5e6432e..f878c63 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
@@ -64,7 +64,6 @@
arrIntData.contentEquals(other.arrIntData)
}
-@SharedImmutable
val umbrellaInstance = TypesUmbrella(
Unit, true, 10, 20, 30, 40, 50.1f, 60.1, 'A', "Str0", Attitude.POSITIVE, IntData(70),
null, null, 11, 21, 31, 41, 51.1f, 61.1, 'B', "Str1", Attitude.NEUTRAL, null,
@@ -85,3 +84,24 @@
arrayOf(IntData(1), IntData(2))
)
)
+
+val umbrellaInstance2 = TypesUmbrella(
+ Unit, true, 10, 20, 30, 40, 50.5f, 60.5, 'A', "Str0", Attitude.POSITIVE, IntData(70),
+ null, null, 11, 21, 31, 41, 51.5f, 61.5, 'B', "Str1", Attitude.NEUTRAL, null,
+ listOf(1, 2, 3),
+ listOf(4, 5, null),
+ setOf(6, 7, 8),
+ mutableSetOf(null, 9, 10),
+ listOf(listOf(Attitude.NEGATIVE, null)),
+ listOf(IntData(1), IntData(2), IntData(3)),
+ mutableListOf(IntData(1), null, IntData(3)),
+ Tree("root", Tree("left"), Tree("right", Tree("right.left"), Tree("right.right"))),
+ mapOf("one" to 1, "two" to 2, "three" to 3),
+ mapOf(0 to null, 1 to "first", 2 to "second"),
+ ArraysUmbrella(
+ arrayOf(1, 2, 3),
+ arrayOf(100, 200, 300),
+ arrayOf(null, -1, -2),
+ arrayOf(IntData(1), IntData(2))
+ )
+)
diff --git a/formats/json/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
similarity index 98%
rename from formats/json/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
index 81a44ef..3fb4bc0 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
@@ -15,7 +15,7 @@
data class Holder(val c: Choices)
class MalformedReader : AbstractDecoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
return UNKNOWN_NAME
diff --git a/formats/json/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt
similarity index 96%
rename from formats/json/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt
index c1a9873..047f1a3 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt
@@ -14,9 +14,8 @@
import kotlin.test.assertEquals
class BinaryPayloadExampleTest {
- @Serializable
+ @Serializable(BinaryPayload.Companion::class)
class BinaryPayload(val req: ByteArray, val res: ByteArray) {
- @Serializer(forClass = BinaryPayload::class)
companion object : KSerializer<BinaryPayload> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("BinaryPayload") {
element("req", ByteArraySerializer().descriptor)
@@ -73,6 +72,7 @@
fun payloadEquivalence() {
val payload1 = BinaryPayload(byteArrayOf(0, 0, 0), byteArrayOf(127, 127))
val s = Json.encodeToString(BinaryPayload.serializer(), payload1)
+ assertEquals("""{"req":"000000","res":"7F7F"}""", s)
val payload2 = Json.decodeFromString(BinaryPayload.serializer(), s)
assertEquals(payload1, payload2)
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt
similarity index 93%
rename from formats/json/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt
index ca8116a..022ef0e 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt
@@ -5,9 +5,7 @@
package kotlinx.serialization.features
import kotlinx.serialization.*
-import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.Json
-import kotlinx.serialization.test.*
import kotlin.test.*
class CollectionSerializerTest {
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt
similarity index 98%
rename from formats/json/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt
index 8f00ad9..ac24cf0 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt
@@ -69,7 +69,8 @@
@Test
fun testReadCustom() {
- val s = json.decodeFromString(EnhancedData.serializer(),
+ val s = json.decodeFromString(
+ EnhancedData.serializer(),
"""{"data":{"a":100500,"b":42},"stringPayload":{"s":"string"},"binaryPayload":"62696E617279"}""")
assertEquals(value, s)
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/DefaultPolymorphicSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/DefaultPolymorphicSerializerTest.kt
new file mode 100644
index 0000000..9d35a29
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/DefaultPolymorphicSerializerTest.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import kotlin.test.*
+
+class DefaultPolymorphicSerializerTest : JsonTestBase() {
+
+ @Serializable
+ abstract class Project {
+ abstract val name: String
+ }
+
+ @Serializable
+ data class DefaultProject(override val name: String, val type: String): Project()
+
+ val module = SerializersModule {
+ polymorphic(Project::class) {
+ defaultDeserializer { DefaultProject.serializer() }
+ }
+ }
+
+ private val json = Json { serializersModule = module }
+
+ @Test
+ fun test() = parametrizedTest {
+ assertEquals(
+ DefaultProject("example", "unknown"),
+ json.decodeFromString<Project>(""" {"type":"unknown","name":"example"}""", it))
+ }
+
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/DurationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/DurationTest.kt
new file mode 100644
index 0000000..0dbb3f9
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/DurationTest.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.JsonTestBase
+import kotlin.test.Test
+import kotlin.time.Duration
+import kotlin.time.DurationUnit
+import kotlin.time.toDuration
+
+class DurationTest : JsonTestBase() {
+ @Serializable
+ data class DurationHolder(val duration: Duration)
+ @Test
+ fun testDuration() {
+ assertJsonFormAndRestored(
+ DurationHolder.serializer(),
+ DurationHolder(1000.toDuration(DurationUnit.SECONDS)),
+ """{"duration":"PT16M40S"}"""
+ )
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/EmojiTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/EmojiTest.kt
new file mode 100644
index 0000000..1e3904a
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/EmojiTest.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.json.JsonTestBase
+import kotlin.test.Test
+
+
+class EmojiTest : JsonTestBase() {
+
+ @Test
+ fun testEmojiString() {
+ assertJsonFormAndRestored(
+ String.serializer(),
+ "\uD83C\uDF34",
+ "\"\uD83C\uDF34\""
+ )
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt
similarity index 67%
rename from formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt
index 804dca6..9c62391 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt
@@ -33,7 +33,6 @@
}
}
-@Serializer(forClass = CheckedData::class)
class CheckedDataSerializer<T : Any>(private val dataSerializer: KSerializer<T>) : KSerializer<CheckedData<T>> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("CheckedDataSerializer") {
val dataDescriptor = dataSerializer.descriptor
@@ -75,6 +74,49 @@
data class DataWithStringContext(@Contextual val data: CheckedData<String>)
+@Serializable
+data class OptionalHolder(val optionalInt: Optional<Int>)
+
+@Serializable(OptionalSerializer::class)
+sealed class Optional<out T : Any?> {
+ object NotPresent : Optional<Nothing>()
+ data class Value<T : Any?>(val value: T?) : Optional<T>()
+
+ fun get(): T? {
+ return when (this) {
+ NotPresent -> null
+ is Value -> this.value
+ }
+ }
+}
+
+class OptionalSerializer<T>(
+ private val valueSerializer: KSerializer<T>
+) : KSerializer<Optional<T>> {
+ override val descriptor: SerialDescriptor = valueSerializer.descriptor
+
+ override fun deserialize(decoder: Decoder): Optional<T> {
+ return try {
+ Optional.Value(valueSerializer.deserialize(decoder))
+ } catch (exception: Exception) {
+ Optional.NotPresent
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: Optional<T>) {
+ val msg = "Tried to serialize an optional property that had no value present. Is encodeDefaults false?"
+ when (value) {
+ Optional.NotPresent -> throw SerializationException(msg)
+ is Optional.Value ->
+ when (val optional = value.value) {
+ null -> encoder.encodeNull()
+ else -> valueSerializer.serialize(encoder, optional)
+ }
+ }
+ }
+}
+
+
class GenericCustomSerializerTest {
@Test
fun testStringData() {
@@ -99,7 +141,7 @@
fun testContextualGeneric() {
val module = SerializersModule {
@Suppress("UNCHECKED_CAST")
- contextual(CheckedData::class) { args -> CheckedDataSerializer(args[0] as KSerializer<Any>)}
+ contextual(CheckedData::class) { args -> CheckedDataSerializer(args[0] as KSerializer<Any>) }
}
assertStringFormAndRestored(
"""{"data":{"data":"my data","checkSum":"2A20"}}""",
@@ -108,4 +150,18 @@
Json { serializersModule = module }
)
}
+
+ @Test
+ fun testOnSealedClass() {
+ /*
+ Test on custom serializer for sealed class with generic parameter.
+ Related issues:
+ https://github.com/Kotlin/kotlinx.serialization/issues/1705
+ https://youtrack.jetbrains.com/issue/KT-50764
+ https://youtrack.jetbrains.com/issue/KT-50718
+ https://github.com/Kotlin/kotlinx.serialization/issues/1843
+ */
+ val encoded = Json.encodeToString(OptionalHolder(Optional.Value(42)))
+ assertEquals("""{"optionalInt":42}""", encoded)
+ }
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt
similarity index 90%
rename from formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt
index 93719c3..5eebe21 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt
@@ -8,7 +8,6 @@
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
-import kotlinx.serialization.test.noLegacyJs
import kotlin.test.*
class JsonClassDiscriminatorTest : JsonTestBase() {
@@ -38,7 +37,7 @@
@Test
- fun testSealedClassesHaveCustomDiscriminator() = noLegacyJs {
+ fun testSealedClassesHaveCustomDiscriminator() {
val messages = listOf(
SealedMessage.StringMessage("string message", "foo"),
SealedMessage.EOF
@@ -53,7 +52,7 @@
}
@Test
- fun testAbstractClassesHaveCustomDiscriminator() = noLegacyJs {
+ fun testAbstractClassesHaveCustomDiscriminator() {
val messages = listOf(
AbstractMessage.StringMessage("string message", "foo"),
AbstractMessage.IntMessage("int message", 42),
@@ -67,7 +66,11 @@
val json = Json { serializersModule = module }
val expected =
"""[{"abstractType":"Message.StringMessage","description":"string message","message":"foo"},{"abstractType":"Message.IntMessage","description":"int message","message":42}]"""
- assertJsonFormAndRestored(ListSerializer(AbstractMessage.serializer()), messages, expected, json)
+ assertJsonFormAndRestored(
+ ListSerializer(
+ AbstractMessage.serializer()
+ ), messages, expected, json
+ )
}
@Serializable
@@ -90,7 +93,7 @@
@Test
- fun testDocumentationInheritanceSample() = noLegacyJs {
+ fun testDocumentationInheritanceSample() {
val module = SerializersModule {
polymorphic(Base::class) {
subclass(BaseMessage.serializer())
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt
new file mode 100644
index 0000000..0e802c1
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt
@@ -0,0 +1,170 @@
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+@Suppress("EnumEntryName")
+class JsonEnumsCaseInsensitiveTest: JsonTestBase() {
+ @Serializable
+ data class Foo(
+ val one: Bar = Bar.BAZ,
+ val two: Bar = Bar.QUX,
+ val three: Bar = Bar.QUX
+ )
+
+ enum class Bar { BAZ, QUX }
+
+ // It seems that we no longer report a warning that @Serializable is required for enums with @SerialName.
+ // It is still required for them to work at top-level.
+ @Serializable
+ enum class Cases {
+ ALL_CAPS,
+ MiXed,
+ all_lower,
+
+ @JsonNames("AltName")
+ hasAltNames,
+
+ @SerialName("SERIAL_NAME")
+ hasSerialName
+ }
+
+ @Serializable
+ data class EnumCases(val cases: List<Cases>)
+
+ val json = Json(default) { decodeEnumsCaseInsensitive = true }
+
+ @Test
+ fun testCases() = parametrizedTest { mode ->
+ val input =
+ """{"cases":["ALL_CAPS","all_caps","mixed","MIXED","miXed","all_lower","ALL_LOWER","all_Lower","hasAltNames","HASALTNAMES","altname","ALTNAME","AltName","SERIAL_NAME","serial_name"]}"""
+ val target = listOf(
+ Cases.ALL_CAPS,
+ Cases.ALL_CAPS,
+ Cases.MiXed,
+ Cases.MiXed,
+ Cases.MiXed,
+ Cases.all_lower,
+ Cases.all_lower,
+ Cases.all_lower,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasAltNames,
+ Cases.hasSerialName,
+ Cases.hasSerialName
+ )
+ val decoded = json.decodeFromString<EnumCases>(input, mode)
+ assertEquals(EnumCases(target), decoded)
+ val encoded = json.encodeToString(decoded, mode)
+ assertEquals(
+ """{"cases":["ALL_CAPS","ALL_CAPS","MiXed","MiXed","MiXed","all_lower","all_lower","all_lower","hasAltNames","hasAltNames","hasAltNames","hasAltNames","hasAltNames","SERIAL_NAME","SERIAL_NAME"]}""",
+ encoded
+ )
+ }
+
+ @Test
+ fun testTopLevelList() = parametrizedTest { mode ->
+ val input = """["all_caps","serial_name"]"""
+ val decoded = json.decodeFromString<List<Cases>>(input, mode)
+ assertEquals(listOf(Cases.ALL_CAPS, Cases.hasSerialName), decoded)
+ assertEquals("""["ALL_CAPS","SERIAL_NAME"]""", json.encodeToString(decoded, mode))
+ }
+
+ @Test
+ fun testTopLevelEnum() = parametrizedTest { mode ->
+ val input = """"altName""""
+ val decoded = json.decodeFromString<Cases>(input, mode)
+ assertEquals(Cases.hasAltNames, decoded)
+ assertEquals(""""hasAltNames"""", json.encodeToString(decoded, mode))
+ }
+
+ @Test
+ fun testSimpleCase() = parametrizedTest { mode ->
+ val input = """{"one":"baz","two":"Qux","three":"QUX"}"""
+ val decoded = json.decodeFromString<Foo>(input, mode)
+ assertEquals(Foo(), decoded)
+ assertEquals("""{"one":"BAZ","two":"QUX","three":"QUX"}""", json.encodeToString(decoded, mode))
+ }
+
+ enum class E { VALUE_A, @JsonNames("ALTERNATIVE") VALUE_B }
+
+ @Test
+ fun testDocSample() {
+
+ val j = Json { decodeEnumsCaseInsensitive = true }
+ @Serializable
+ data class Outer(val enums: List<E>)
+
+ println(j.decodeFromString<Outer>("""{"enums":["value_A", "alternative"]}""").enums)
+ }
+
+ @Test
+ fun testCoercingStillWorks() = parametrizedTest { mode ->
+ val withCoercing = Json(json) { coerceInputValues = true }
+ val input = """{"one":"baz","two":"unknown","three":"Que"}"""
+ assertEquals(Foo(), withCoercing.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testCaseInsensitivePriorityOverCoercing() = parametrizedTest { mode ->
+ val withCoercing = Json(json) { coerceInputValues = true }
+ val input = """{"one":"QuX","two":"Baz","three":"Que"}"""
+ assertEquals(Foo(Bar.QUX, Bar.BAZ, Bar.QUX), withCoercing.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testCoercingStillWorksWithNulls() = parametrizedTest { mode ->
+ val withCoercing = Json(json) { coerceInputValues = true }
+ val input = """{"one":"baz","two":"null","three":null}"""
+ assertEquals(Foo(), withCoercing.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testFeatureDisablesProperly() = parametrizedTest { mode ->
+ val disabled = Json(json) {
+ coerceInputValues = true
+ decodeEnumsCaseInsensitive = false
+ }
+ val input = """{"one":"BAZ","two":"BAz","three":"baz"}""" // two and three should be coerced to QUX
+ assertEquals(Foo(), disabled.decodeFromString<Foo>(input, mode))
+ }
+
+ @Test
+ fun testFeatureDisabledThrowsWithoutCoercing() = parametrizedTest { mode ->
+ val disabled = Json(json) {
+ coerceInputValues = false
+ decodeEnumsCaseInsensitive = false
+ }
+ val input = """{"one":"BAZ","two":"BAz","three":"baz"}"""
+ assertFailsWithMessage<SerializationException>("does not contain element with name 'BAz'") {
+ disabled.decodeFromString<Foo>(input, mode)
+ }
+ }
+
+ @Serializable enum class BadEnum { Bad, BAD }
+
+ @Serializable data class ListBadEnum(val l: List<BadEnum>)
+
+ @Test
+ fun testLowercaseClashThrowsException() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("""The suggested name 'bad' for enum value BAD is already one of the names for enum value Bad""") {
+ json.decodeFromString<Box<BadEnum>>("""{"boxed":"bad"}""", mode)
+ }
+ assertFailsWithMessage<SerializationException>("""The suggested name 'bad' for enum value BAD is already one of the names for enum value Bad""") {
+ json.decodeFromString<Box<BadEnum>>("""{"boxed":"unrelated"}""", mode)
+ }
+ }
+
+ @Test
+ fun testLowercaseClashHandledWithoutFeature() = parametrizedTest { mode ->
+ val disabled = Json(json) {
+ coerceInputValues = false
+ decodeEnumsCaseInsensitive = false
+ }
+ assertEquals(ListBadEnum(listOf(BadEnum.Bad, BadEnum.BAD)), disabled.decodeFromString("""{"l":["Bad","BAD"]}""", mode))
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt
similarity index 93%
rename from formats/json/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt
index 6a4e33a..3404419 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt
@@ -54,7 +54,7 @@
}
@Test
- fun testEnumSupportsAlternativeNames() = noLegacyJs {
+ fun testEnumSupportsAlternativeNames() {
val input = """{"enumList":["VALUE_A", "someValue", "some_value", "VALUE_B"], "checkCoercion":"someValue"}"""
val expected = WithEnumNames(
listOf(
@@ -70,14 +70,14 @@
}
@Test
- fun topLevelEnumSupportAlternativeNames() = noLegacyJs {
+ fun topLevelEnumSupportAlternativeNames() {
parameterizedCoercingTest { json, streaming, msg ->
assertEquals(AlternateEnumNames.VALUE_A, json.decodeFromString("\"someValue\"", streaming), msg)
}
}
@Test
- fun testParsesAllAlternativeNames() = noLegacyJs {
+ fun testParsesAllAlternativeNames() {
for (input in listOf(inputString1, inputString2)) {
parameterizedCoercingTest { json, streaming, _ ->
val data = json.decodeFromString(WithNames.serializer(), input, jsonTestingMode = streaming)
@@ -87,7 +87,7 @@
}
@Test
- fun testThrowsAnErrorOnDuplicateNames() = noLegacyJs {
+ fun testThrowsAnErrorOnDuplicateNames() {
val serializer = CollisionWithAlternate.serializer()
parameterizedCoercingTest { json, streaming, _ ->
assertFailsWithMessage<SerializationException>(
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt
new file mode 100644
index 0000000..b997454
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt
@@ -0,0 +1,60 @@
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+class JsonNamingStrategyExclusionTest : JsonTestBase() {
+ @SerialInfo
+ @Target(AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
+ annotation class OriginalSerialName
+
+ private fun List<Annotation>.hasOriginal() = filterIsInstance<OriginalSerialName>().isNotEmpty()
+
+ private val myStrategy = JsonNamingStrategy { descriptor, index, serialName ->
+ if (descriptor.annotations.hasOriginal() || descriptor.getElementAnnotations(index).hasOriginal()) serialName
+ else JsonNamingStrategy.SnakeCase.serialNameForJson(descriptor, index, serialName)
+ }
+
+ @Serializable
+ @OriginalSerialName
+ data class Foo(val firstArg: String = "a", val secondArg: String = "b")
+
+ enum class E {
+ @OriginalSerialName
+ FIRST_E,
+ SECOND_E
+ }
+
+ @Serializable
+ data class Bar(
+ val firstBar: String = "a",
+ @OriginalSerialName val secondBar: String = "b",
+ val fooBar: Foo = Foo(),
+ val enumBarOne: E = E.FIRST_E,
+ val enumBarTwo: E = E.SECOND_E
+ )
+
+ private fun doTest(json: Json) {
+ val j = Json(json) {
+ namingStrategy = myStrategy
+ }
+ val bar = Bar()
+ assertJsonFormAndRestored(
+ Bar.serializer(),
+ bar,
+ """{"first_bar":"a","secondBar":"b","foo_bar":{"firstArg":"a","secondArg":"b"},"enum_bar_one":"FIRST_E","enum_bar_two":"SECOND_E"}""",
+ j
+ )
+ }
+
+ @Test
+ fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(default) {
+ useAlternativeNames = true
+ })
+
+ @Test
+ fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(default) {
+ useAlternativeNames = false
+ })
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
new file mode 100644
index 0000000..9e2cf1d
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt
@@ -0,0 +1,242 @@
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+
+class JsonNamingStrategyTest : JsonTestBase() {
+ @Serializable
+ data class Foo(
+ val simple: String = "a",
+ val oneWord: String = "b",
+ val already_in_snake: String = "c",
+ val aLotOfWords: String = "d",
+ val FirstCapitalized: String = "e",
+ val hasAcronymURL: Bar = Bar.BAZ,
+ val hasDigit123AndPostfix: Bar = Bar.QUX,
+ val coercionTest: Bar = Bar.QUX
+ )
+
+ enum class Bar { BAZ, QUX }
+
+ val jsonWithNaming = Json(default) {
+ namingStrategy = JsonNamingStrategy.SnakeCase
+ decodeEnumsCaseInsensitive = true // check that related feature does not break anything
+ }
+
+ @Test
+ fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(jsonWithNaming) {
+ useAlternativeNames = true
+ })
+
+ @Test
+ fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(jsonWithNaming) {
+ useAlternativeNames = false
+ })
+
+ private fun doTest(json: Json) {
+ val foo = Foo()
+ assertJsonFormAndRestored(
+ Foo.serializer(),
+ foo,
+ """{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"QUX"}""",
+ json
+ )
+ }
+
+ @Test
+ fun testNamingStrategyWorksWithCoercing() {
+ val j = Json(jsonWithNaming) {
+ coerceInputValues = true
+ useAlternativeNames = false
+ }
+ assertEquals(
+ Foo(),
+ j.decodeFromString("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"baz","has_digit123_and_postfix":"qux","coercion_test":"invalid"}""")
+ )
+ }
+
+ @Test
+ fun testSnakeCaseStrategy() {
+ fun apply(name: String) =
+ JsonNamingStrategy.SnakeCase.serialNameForJson(String.serializer().descriptor, 0, name)
+
+ val cases = mapOf<String, String>(
+ "" 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_Underscores" to "camel_case_underscores",
+ "_many____underscores__" to "_many____underscores__",
+ "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))
+ }
+ }
+
+ @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)
+
+ @Test
+ fun testNamingStrategyOverridesOriginal() {
+ val json = Json(jsonWithNaming) {
+ ignoreUnknownKeys = true
+ }
+ parametrizedTest { mode ->
+ assertEquals(DontUseOriginal("a"), json.decodeFromString("""{"test_case":"a","testCase":"b"}""", mode))
+ }
+
+ val jsonThrows = Json(jsonWithNaming) {
+ ignoreUnknownKeys = false
+ }
+ parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Encountered an unknown key 'testCase'") {
+ jsonThrows.decodeFromString<DontUseOriginal>("""{"test_case":"a","testCase":"b"}""", mode)
+ }
+ }
+ }
+
+ @Serializable
+ data class CollisionCheckPrimary(val testCase: String, val test_case: String)
+
+ @Serializable
+ data class CollisionCheckAlternate(val testCase: String, @JsonNames("test_case") val testCase2: String)
+
+ @Test
+ fun testNamingStrategyPrioritizesOverAlternative() {
+ val json = Json(jsonWithNaming) {
+ ignoreUnknownKeys = true
+ }
+ parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("The suggested name 'test_case' for property test_case is already one of the names for property testCase") {
+ json.decodeFromString<CollisionCheckPrimary>("""{"test_case":"a"}""", mode)
+ }
+ }
+ parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("The suggested name 'test_case' for property testCase2 is already one of the names for property testCase") {
+ json.decodeFromString<CollisionCheckAlternate>("""{"test_case":"a"}""", mode)
+ }
+ }
+ }
+
+
+ @Serializable
+ data class OriginalAsFallback(@JsonNames("testCase") val testCase: String)
+
+ @Test
+ fun testCanUseOriginalNameAsAlternative() {
+ val json = Json(jsonWithNaming) {
+ ignoreUnknownKeys = true
+ }
+ parametrizedTest { mode ->
+ assertEquals(OriginalAsFallback("b"), json.decodeFromString("""{"testCase":"b"}""", mode))
+ }
+ }
+
+ @Serializable
+ sealed interface SealedBase {
+ @Serializable
+ @JsonClassDiscriminator("typeSub")
+ sealed class SealedMid : SealedBase {
+ @Serializable
+ @SerialName("SealedSub1")
+ object SealedSub1 : SealedMid()
+ }
+
+ @Serializable
+ @SerialName("SealedSub2")
+ data class SealedSub2(val testCase: Int = 0) : SealedBase
+ }
+
+ @Serializable
+ data class Holder(val testBase: SealedBase, val testMid: SealedBase.SealedMid)
+
+ @Test
+ fun testNamingStrategyDoesNotAffectPolymorphism() {
+ val json = Json(jsonWithNaming) {
+ classDiscriminator = "typeBase"
+ }
+ val holder = Holder(SealedBase.SealedSub2(), SealedBase.SealedMid.SealedSub1)
+ assertJsonFormAndRestored(
+ Holder.serializer(),
+ holder,
+ """{"test_base":{"typeBase":"SealedSub2","test_case":0},"test_mid":{"typeSub":"SealedSub1"}}""",
+ json
+ )
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt
similarity index 70%
rename from formats/json/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt
index b9ba6ff..82a5ca6 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt
@@ -1,16 +1,11 @@
package kotlinx.serialization.features
import kotlinx.serialization.*
-import kotlinx.serialization.descriptors.PrimitiveKind
-import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.descriptors.buildSerialDescriptor
-import kotlinx.serialization.encoding.Decoder
-import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.json.Json
-import kotlinx.serialization.test.jvmOnly
-import kotlinx.serialization.test.noLegacyJs
-import kotlin.test.Test
-import kotlin.test.assertEquals
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
class LocalClassesTest {
object ObjectCustomSerializer: KSerializer<Any?> {
@@ -42,10 +37,8 @@
val origin = Local(42)
- noLegacyJs {
- val decoded: Local = Json.decodeFromString(Json.encodeToString(origin))
- assertEquals(origin, decoded)
- }
+ val decoded: Local = Json.decodeFromString(Json.encodeToString(origin))
+ assertEquals(origin, decoded)
}
@Test
@@ -56,13 +49,12 @@
val origin = Local(it)
- noLegacyJs {
- val decoded: Local = Json.decodeFromString(Json.encodeToString(origin))
- assertEquals(origin, decoded)
- }
+ val decoded: Local = Json.decodeFromString(Json.encodeToString(origin))
+ assertEquals(origin, decoded)
}
}
+ @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Test
fun testObjectCustomSerializer() {
@Serializable(with = ObjectCustomSerializer::class)
@@ -70,12 +62,11 @@
val origin: Local? = null
- noLegacyJs {
- val decoded: Local? = Json.decodeFromString(Json.encodeToString(origin))
- assertEquals(origin, decoded)
- }
+ val decoded: Local? = Json.decodeFromString(Json.encodeToString(origin))
+ assertEquals(origin, decoded)
}
+ @Suppress("SERIALIZER_TYPE_INCOMPATIBLE")
@Test
fun testClassCustomSerializer() {
@Serializable(with = ClassCustomSerializer::class)
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt
new file mode 100644
index 0000000..af9ef6c
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt
@@ -0,0 +1,72 @@
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+@MetaSerializable
+@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
+annotation class JsonComment(val comment: String)
+
+@JsonComment("class_comment")
+data class IntDataCommented(val i: Int)
+
+class MetaSerializableJsonTest : JsonTestBase() {
+
+ @Serializable
+ data class Carrier(
+ val plain: String,
+ @JsonComment("string_comment") val commented: StringData,
+ val intData: IntDataCommented
+ )
+
+ class CarrierSerializer : JsonTransformingSerializer<Carrier>(serializer()) {
+
+ private val desc = Carrier.serializer().descriptor
+ private fun List<Annotation>.comment(): String? = filterIsInstance<JsonComment>().firstOrNull()?.comment
+
+ private val commentMap = (0 until desc.elementsCount).associateBy({ desc.getElementName(it) },
+ { desc.getElementAnnotations(it).comment() ?: desc.getElementDescriptor(it).annotations.comment() })
+
+ // NB: we may want to add this to public API
+ private fun JsonElement.editObject(action: (MutableMap<String, JsonElement>) -> Unit): JsonElement {
+ val mutable = this.jsonObject.toMutableMap()
+ action(mutable)
+ return JsonObject(mutable)
+ }
+
+ override fun transformDeserialize(element: JsonElement): JsonElement {
+ return element.editObject { result ->
+ for ((key, value) in result) {
+ commentMap[key]?.let {
+ result[key] = value.editObject {
+ it.remove("comment")
+ }
+ }
+ }
+ }
+ }
+
+ override fun transformSerialize(element: JsonElement): JsonElement {
+ return element.editObject { result ->
+ for ((key, value) in result) {
+ commentMap[key]?.let { comment ->
+ result[key] = value.editObject {
+ it["comment"] = JsonPrimitive(comment)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testMyJsonComment() {
+ assertJsonFormAndRestored(
+ CarrierSerializer(),
+ Carrier("plain", StringData("string1"), IntDataCommented(42)),
+ """{"plain":"plain","commented":{"data":"string1","comment":"string_comment"},"intData":{"i":42,"comment":"class_comment"}}"""
+ )
+ }
+
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt
similarity index 96%
rename from formats/json/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt
index 71379d2..9ad4afe 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt
@@ -10,7 +10,7 @@
import kotlin.test.Test
import kotlin.test.assertEquals
-@Serializable
+@Serializable(WithNull.Companion::class)
data class WithNull(@SerialName("value") val nullable: String? = null) {
@Serializer(forClass = WithNull::class)
companion object : KSerializer<WithNull> {
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt
new file mode 100644
index 0000000..2b2f1f7
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+class PolymorphicDeserializationErrorMessagesTest : JsonTestBase() {
+ @Serializable
+ class DummyData(@Polymorphic val a: Any)
+
+ @Serializable
+ class Holder(val d: DummyData)
+
+ // TODO: remove this after #2480 is merged
+ private fun checkSerializationException(action: () -> Unit, assertions: SerializationException.(String) -> Unit) {
+ val e = assertFailsWith(SerializationException::class, action)
+ assertNotNull(e.message)
+ e.assertions(e.message!!)
+ }
+
+ @Test
+ fun testNotRegisteredMessage() = parametrizedTest { mode ->
+ val input = """{"d":{"a":{"type":"my.Class", "value":42}}}"""
+ checkSerializationException({
+ default.decodeFromString<Holder>(input, mode)
+ }, { message ->
+ // ReaderJsonLexer.peekLeadingMatchingValue is not implemented, so first-key optimization is not working for streaming yet.
+ if (mode == JsonTestingMode.STREAMING)
+ assertContains(message, "Unexpected JSON token at offset 10: Serializer for subclass 'my.Class' is not found in the polymorphic scope of 'Any' at path: \$.d.a")
+ else
+ assertContains(message, "Serializer for subclass 'my.Class' is not found in the polymorphic scope of 'Any'")
+ })
+ }
+
+ @Test
+ fun testDiscriminatorMissingNoDefaultMessage() = parametrizedTest { mode ->
+ val input = """{"d":{"a":{"value":42}}}"""
+ checkSerializationException({
+ default.decodeFromString<Holder>(input, mode)
+ }, { message ->
+ // Always slow path when discriminator is missing, so no position and path
+ assertContains(message, "Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'Any'")
+ })
+ }
+
+ @Test
+ fun testClassDiscriminatorIsNull() = parametrizedTest { mode ->
+ val input = """{"d":{"a":{"type":null, "value":42}}}"""
+ checkSerializationException({
+ default.decodeFromString<Holder>(input, mode)
+ }, { message ->
+ // Always slow path when discriminator is missing, so no position and path
+ assertContains(message, "Class discriminator was missing and no default serializers were registered in the polymorphic scope of 'Any'")
+ })
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
similarity index 98%
rename from formats/json/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
index 8e859ee..77004db 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt
@@ -136,7 +136,7 @@
fun testSerializerLookupForInterface() {
// On JVM and JS IR it can be supported via reflection/runtime hacks
// on Native, unfortunately, only with intrinsics.
- if (currentPlatform == Platform.NATIVE || currentPlatform == Platform.JS_LEGACY) return
+ if (isNative() || isWasm()) return
val msgSer = serializer<IMessage>()
assertEquals(IMessage::class, (msgSer as AbstractPolymorphicSerializer).baseClass)
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt
similarity index 78%
rename from formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt
index d05403b..c493887 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt
@@ -143,4 +143,42 @@
val s = json.encodeToString(Wrapper.serializer(), obj, jsonTestingMode)
assertEquals("""{"polyBase1":{"type":"even","parity":"even"},"polyBase2":{"type":"odd","parity":"odd"}}""", s)
}
+
+ @Serializable
+ sealed class Conf {
+ @Serializable
+ @SerialName("empty")
+ object Empty : Conf() // default
+
+ @Serializable
+ @SerialName("simple")
+ data class Simple(val value: String) : Conf()
+ }
+
+ private val jsonForConf = Json {
+ isLenient = false
+ ignoreUnknownKeys = true
+ serializersModule = SerializersModule {
+ polymorphicDefaultDeserializer(Conf::class) { Conf.Empty.serializer() }
+ }
+ }
+
+ @Test
+ fun defaultSerializerWithEmptyBodyTest() = parametrizedTest { mode ->
+ assertEquals(Conf.Simple("123"), jsonForConf.decodeFromString<Conf>("""{"type": "simple", "value": "123"}""", mode))
+ assertEquals(Conf.Empty, jsonForConf.decodeFromString<Conf>("""{"type": "default"}""", mode))
+ assertEquals(Conf.Empty, jsonForConf.decodeFromString<Conf>("""{"unknown": "Meow"}""", mode))
+ assertEquals(Conf.Empty, jsonForConf.decodeFromString<Conf>("""{}""", mode))
+ }
+
+ @Test
+ fun testTypeKeysInLenientMode() = parametrizedTest { mode ->
+ val json = Json(jsonForConf) { isLenient = true }
+
+ assertEquals(Conf.Simple("123"), json.decodeFromString<Conf>("""{type: simple, value: 123}""", mode))
+ assertEquals(Conf.Empty, json.decodeFromString<Conf>("""{type: default}""", mode))
+ assertEquals(Conf.Empty, json.decodeFromString<Conf>("""{unknown: Meow}""", mode))
+ assertEquals(Conf.Empty, json.decodeFromString<Conf>("""{}""", mode))
+
+ }
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
similarity index 71%
rename from formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
index 9ce0ea9..07b6e31 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt
@@ -5,14 +5,13 @@
package kotlinx.serialization.features
import kotlinx.serialization.*
-import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlinx.serialization.modules.plus
import kotlinx.serialization.test.assertStringFormAndRestored
-import kotlinx.serialization.test.isJs
import kotlin.test.*
-class PolymorphismWithAnyTest {
+class PolymorphismWithAnyTest: JsonTestBase() {
@Serializable
data class MyPolyData(val data: Map<String, @Polymorphic Any>)
@@ -29,19 +28,20 @@
val className = className.substringAfterLast('.')
val scopeName = scopeName.substringAfterLast('.')
val expectedText =
- "Class '$className' is not registered for polymorphic serialization in the scope of '$scopeName'"
+ "Serializer for subclass '$className' is not found in the polymorphic scope of '$scopeName'"
assertTrue(exception.message!!.startsWith(expectedText),
"Found $exception, but expected to start with: $expectedText")
}
@Test
- fun testFailWithoutModulesWithCustomClass() {
+ fun testFailWithoutModulesWithCustomClass() = parametrizedTest { mode ->
checkNotRegisteredMessage(
"kotlinx.serialization.IntData", "kotlin.Any",
assertFailsWith<SerializationException>("not registered") {
Json.encodeToString(
MyPolyData.serializer(),
- MyPolyData(mapOf("a" to IntData(42)))
+ MyPolyData(mapOf("a" to IntData(42))),
+ mode
)
}
)
@@ -52,11 +52,11 @@
val json = Json {
serializersModule = SerializersModule { polymorphic(Any::class) { subclass(IntData.serializer()) } }
}
- assertStringFormAndRestored(
+ assertJsonFormAndRestored(
expected = """{"data":{"a":{"type":"kotlinx.serialization.IntData","intV":42}}}""",
- original = MyPolyData(mapOf("a" to IntData(42))),
+ data = MyPolyData(mapOf("a" to IntData(42))),
serializer = MyPolyData.serializer(),
- format = json
+ json = json
)
}
@@ -64,14 +64,15 @@
* This test should fail because PolyDerived registered in the scope of PolyBase, not kotlin.Any
*/
@Test
- fun testFailWithModulesNotInAnyScope() {
+ fun testFailWithModulesNotInAnyScope() = parametrizedTest { mode ->
val json = Json { serializersModule = BaseAndDerivedModule }
checkNotRegisteredMessage(
"kotlinx.serialization.PolyDerived", "kotlin.Any",
assertFailsWith<SerializationException> {
json.encodeToString(
MyPolyData.serializer(),
- MyPolyData(mapOf("a" to PolyDerived("foo")))
+ MyPolyData(mapOf("a" to PolyDerived("foo"))),
+ mode
)
}
)
@@ -87,11 +88,11 @@
@Test
fun testRebindModules() {
val json = Json { serializersModule = baseAndDerivedModuleAtAny }
- assertStringFormAndRestored(
+ assertJsonFormAndRestored(
expected = """{"data":{"a":{"type":"kotlinx.serialization.PolyDerived","id":1,"s":"foo"}}}""",
- original = MyPolyData(mapOf("a" to PolyDerived("foo"))),
+ data = MyPolyData(mapOf("a" to PolyDerived("foo"))),
serializer = MyPolyData.serializer(),
- format = json
+ json = json
)
}
@@ -99,7 +100,7 @@
* This test should fail because PolyDerived registered in the scope of kotlin.Any, not PolyBase
*/
@Test
- fun testFailWithModulesNotInParticularScope() {
+ fun testFailWithModulesNotInParticularScope() = parametrizedTest { mode ->
val json = Json { serializersModule = baseAndDerivedModuleAtAny }
checkNotRegisteredMessage(
"kotlinx.serialization.PolyDerived", "kotlinx.serialization.PolyBase",
@@ -109,7 +110,8 @@
MyPolyDataWithPolyBase(
mapOf("a" to PolyDerived("foo")),
PolyDerived("foo")
- )
+ ),
+ mode
)
}
)
@@ -118,17 +120,30 @@
@Test
fun testBindModules() {
val json = Json { serializersModule = (baseAndDerivedModuleAtAny + BaseAndDerivedModule) }
- assertStringFormAndRestored(
+ assertJsonFormAndRestored(
expected = """{"data":{"a":{"type":"kotlinx.serialization.PolyDerived","id":1,"s":"foo"}},
|"polyBase":{"type":"kotlinx.serialization.PolyDerived","id":1,"s":"foo"}}""".trimMargin().lines().joinToString(
""
),
- original = MyPolyDataWithPolyBase(
+ data = MyPolyDataWithPolyBase(
mapOf("a" to PolyDerived("foo")),
PolyDerived("foo")
),
serializer = MyPolyDataWithPolyBase.serializer(),
- format = json
+ json = json
)
}
+
+ @Test
+ fun testTypeKeyLastInInput() = parametrizedTest { mode ->
+ val json = Json { serializersModule = (baseAndDerivedModuleAtAny + BaseAndDerivedModule) }
+ val input = """{"data":{"a":{"id":1,"s":"foo","type":"kotlinx.serialization.PolyDerived"}},
+ |"polyBase":{"id":1,"s":"foo","type":"kotlinx.serialization.PolyDerived"}}""".trimMargin().lines().joinToString(
+ "")
+ val data = MyPolyDataWithPolyBase(
+ mapOf("a" to PolyDerived("foo")),
+ PolyDerived("foo")
+ )
+ assertEquals(data, json.decodeFromString(MyPolyDataWithPolyBase.serializer(), input, mode))
+ }
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt
similarity index 99%
rename from formats/json/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt
index 8af9055..a99c2a1 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt
@@ -11,7 +11,6 @@
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
-import kotlinx.serialization.test.*
import kotlin.test.*
class SealedClassesSerializationTest : JsonTestBase() {
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt
similarity index 94%
rename from formats/json/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt
index ee37b4b..a7fea75 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt
@@ -7,7 +7,6 @@
import kotlinx.serialization.*
import kotlinx.serialization.EncodeDefault.Mode.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.test.noLegacyJs
import kotlin.test.*
class SkipDefaultsTest {
@@ -59,7 +58,7 @@
}
@Test
- fun encodeDefaultsAnnotationWithFlag() = noLegacyJs {
+ fun encodeDefaultsAnnotationWithFlag() {
val data = DifferentModes()
assertEquals("""{"a":"a","b":"b","c":"c"}""", jsonEncodeDefaults.encodeToString(data))
assertEquals("""{"b":"b","c":"c"}""", jsonDropDefaults.encodeToString(data))
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt
similarity index 78%
rename from formats/json/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt
index 037d785..aa4866f 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt
@@ -1,20 +1,15 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-@file:OptIn(ExperimentalUnsignedTypes::class)
-/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.features.inline
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
-import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
import kotlinx.serialization.test.*
-import kotlin.test.Test
+import kotlin.test.*
@Serializable(WithUnsignedSerializer::class)
data class WithUnsigned(val u: UInt)
@@ -46,8 +41,8 @@
class EncodeInlineElementTest {
@Test
- fun wrapper() = noLegacyJs {
+ fun wrapper() {
val w = WithUnsigned(Int.MAX_VALUE.toUInt() + 1.toUInt())
- assertStringFormAndRestored("""{"u":2147483648}""", w, WithUnsignedSerializer, printResult = true)
+ assertStringFormAndRestored<WithUnsigned>("""{"u":2147483648}""", w, WithUnsignedSerializer, printResult = true)
}
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt
similarity index 90%
rename from formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt
index 363bdaf..96972f9 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt
@@ -61,7 +61,7 @@
class InlineClassesCompleteTest {
@Test
- fun testAllVariantsWithoutNull() = noLegacyJs {
+ fun testAllVariantsWithoutNull() {
val withAll = WithAll(
MyInt(1),
MyInt(2),
@@ -72,15 +72,15 @@
OverSerializableNullable(IntData(7)),
OverSerializableNullable(IntData(8)),
WithT(Box(9)),
- WithT<Int>(Box(10)),
- WithT<Int?>(Box(11)),
- WithTNullable<Int?>(Box(12))
+ WithT(Box(10)),
+ WithT(Box(11)),
+ WithTNullable(Box(12))
)
assertSerializedAndRestored(withAll, WithAll.serializer())
}
@Test
- fun testAllVariantsWithNull() = noLegacyJs {
+ fun testAllVariantsWithNull() {
assertSerializedAndRestored(
WithAll(
MyInt(1),
@@ -93,14 +93,14 @@
null,
WithT(Box(9)),
null,
- WithT<Int?>(Box(null)),
- WithTNullable<Int?>(Box(null))
+ WithT(Box(null)),
+ WithTNullable(Box(null))
), WithAll.serializer()
)
}
@Test
- fun testAllGenericVariantsWithoutNull() = noLegacyJs {
+ fun testAllGenericVariantsWithoutNull() {
assertSerializedAndRestored(
WithGenerics(
Box(MyInt(1)),
@@ -117,7 +117,7 @@
}
@Test
- fun testAllGenericVariantsWithNull() = noLegacyJs {
+ fun testAllGenericVariantsWithNull() {
assertSerializedAndRestored(
WithGenerics(
Box(MyInt(1)),
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt
similarity index 82%
rename from formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt
index 1eedafa..f3eb951 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt
@@ -3,11 +3,6 @@
*/
@file:Suppress("INLINE_CLASSES_NOT_SUPPORTED", "SERIALIZER_NOT_FOUND")
-@file:OptIn(ExperimentalUnsignedTypes::class)
-
-/*
- * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
package kotlinx.serialization.features.inline
@@ -27,8 +22,7 @@
@JvmInline
value class MyUInt(val m: Int)
-@Serializer(forClass = MyUInt::class)
-object MyUIntSerializer {
+object MyUIntSerializer : KSerializer<MyUInt> {
override val descriptor = UInt.serializer().descriptor
override fun serialize(encoder: Encoder, value: MyUInt) {
encoder.encodeInline(descriptor).encodeInt(value.m)
@@ -79,14 +73,47 @@
@Serializable
data class ResourceIdentifier(val id: ResourceId, val type: ResourceType, val type2: ValueWrapper)
-@Serializable @JvmInline
+@Serializable
+@JvmInline
value class ValueWrapper(val wrapped: ResourceType)
+@Serializable
+@JvmInline
+value class Outer(val inner: Inner)
+
+@Serializable
+data class Inner(val n: Int)
+
+@Serializable
+data class OuterOuter(val outer: Outer)
+
+@Serializable
+@JvmInline
+value class WithList(val value: List<Int>)
+
class InlineClassesTest : JsonTestBase() {
private val precedent: UInt = Int.MAX_VALUE.toUInt() + 10.toUInt()
@Test
- fun testTopLevel() = noLegacyJs {
+ fun withList() {
+ val withList = WithList(listOf(1, 2, 3))
+ assertJsonFormAndRestored(WithList.serializer(), withList, """[1,2,3]""")
+ }
+
+ @Test
+ fun testOuterInner() {
+ val o = Outer(Inner(10))
+ assertJsonFormAndRestored(Outer.serializer(), o, """{"n":10}""")
+ }
+
+ @Test
+ fun testOuterOuterInner() {
+ val o = OuterOuter(Outer(Inner(10)))
+ assertJsonFormAndRestored(OuterOuter.serializer(), o, """{"outer":{"n":10}}""")
+ }
+
+ @Test
+ fun testTopLevel() {
assertJsonFormAndRestored(
ResourceType.serializer(),
ResourceType("foo"),
@@ -95,7 +122,7 @@
}
@Test
- fun testTopLevelOverEnum() = noLegacyJs {
+ fun testTopLevelOverEnum() {
assertJsonFormAndRestored(
ResourceKind.serializer(),
ResourceKind(SampleEnum.OptionC),
@@ -104,7 +131,7 @@
}
@Test
- fun testTopLevelWrapper() = noLegacyJs {
+ fun testTopLevelWrapper() {
assertJsonFormAndRestored(
ValueWrapper.serializer(),
ValueWrapper(ResourceType("foo")),
@@ -113,7 +140,7 @@
}
@Test
- fun testTopLevelContextual() = noLegacyJs {
+ fun testTopLevelContextual() {
val module = SerializersModule {
contextual<ResourceType>(ResourceType.serializer())
}
@@ -128,7 +155,7 @@
@Test
- fun testSimpleContainer() = noLegacyJs {
+ fun testSimpleContainer() {
assertJsonFormAndRestored(
SimpleContainerForUInt.serializer(),
SimpleContainerForUInt(precedent),
@@ -144,7 +171,7 @@
)
@Test
- fun testSimpleContainerForList() = noLegacyJs {
+ fun testSimpleContainerForList() {
assertJsonFormAndRestored(
ContainerForList.serializer(UInt.serializer()),
ContainerForList(MyList(listOf(precedent))),
@@ -153,7 +180,7 @@
}
@Test
- fun testInlineClassesWithStrings() = noLegacyJs {
+ fun testInlineClassesWithStrings() {
assertJsonFormAndRestored(
ResourceIdentifier.serializer(),
ResourceIdentifier(ResourceId("resId"), ResourceType("resType"), ValueWrapper(ResourceType("wrappedType"))),
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineMapQuotedTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineMapQuotedTest.kt
new file mode 100644
index 0000000..63157d1
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineMapQuotedTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features.inline
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.jvm.*
+import kotlin.test.*
+
+class InlineMapQuotedTest : JsonTestBase() {
+ @Serializable(with = CustomULong.Serializer::class)
+ data class CustomULong(val value: ULong) {
+ @OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
+ internal object Serializer : KSerializer<CustomULong> {
+ override val descriptor: SerialDescriptor =
+ @OptIn(ExperimentalUnsignedTypes::class) ULong.serializer().descriptor
+
+ override fun deserialize(decoder: Decoder): CustomULong =
+ CustomULong(decoder.decodeInline(descriptor).decodeSerializableValue(ULong.serializer()))
+
+ override fun serialize(encoder: Encoder, value: CustomULong) {
+ encoder.encodeInline(descriptor).encodeSerializableValue(ULong.serializer(), value.value)
+ }
+ }
+ }
+
+ @JvmInline
+ @Serializable
+ value class WrappedLong(val value: Long)
+
+ @JvmInline
+ @Serializable
+ value class WrappedULong(val value: ULong)
+
+ @Serializable
+ data class Carrier(
+ val mapLong: Map<Long, Long>,
+ val mapULong: Map<ULong, Long>,
+ val wrappedLong: Map<WrappedLong, Long>,
+ val mapWrappedU: Map<WrappedULong, Long>,
+ val mapCustom: Map<CustomULong, Long>
+ )
+
+ @Test
+ fun testInlineClassAsMapKey() {
+ println(Long.MAX_VALUE.toULong() + 2UL)
+ val c = Carrier(
+ mapOf(1L to 1L),
+ mapOf(Long.MAX_VALUE.toULong() + 2UL to 2L),
+ mapOf(WrappedLong(3L) to 3L),
+ mapOf(WrappedULong(Long.MAX_VALUE.toULong() + 4UL) to 4L),
+ mapOf(CustomULong(Long.MAX_VALUE.toULong() + 5UL) to 5L)
+ )
+ assertJsonFormAndRestored(
+ serializer<Carrier>(),
+ c,
+ """{"mapLong":{"1":1},"mapULong":{"9223372036854775809":2},"wrappedLong":{"3":3},"mapWrappedU":{"9223372036854775811":4},"mapCustom":{"9223372036854775812":5}}"""
+ )
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt
new file mode 100644
index 0000000..5e24c7f
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features.inline
+
+import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+class UnsignedIntegersTest : JsonTestBase() {
+ @Serializable
+ data class AllUnsigned(
+ val uInt: UInt,
+ val uLong: ULong,
+ val uByte: UByte,
+ val uShort: UShort,
+ val signedInt: Int,
+ val signedLong: Long,
+ val double: Double
+ )
+
+ @ExperimentalUnsignedTypes
+ @Serializable
+ data class UnsignedArrays(
+ val uByte: UByteArray,
+ val uShort: UShortArray,
+ val uInt: UIntArray,
+ val uLong: ULongArray
+ ) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other == null || this::class != other::class) return false
+
+ other as UnsignedArrays
+
+ if (!uByte.contentEquals(other.uByte)) return false
+ if (!uShort.contentEquals(other.uShort)) return false
+ if (!uInt.contentEquals(other.uInt)) return false
+ if (!uLong.contentEquals(other.uLong)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = uByte.contentHashCode()
+ result = 31 * result + uShort.contentHashCode()
+ result = 31 * result + uInt.contentHashCode()
+ result = 31 * result + uLong.contentHashCode()
+ return result
+ }
+ }
+
+ @Serializable
+ data class UnsignedWithoutLong(val uInt: UInt, val uByte: UByte, val uShort: UShort)
+
+ @Test
+ fun testUnsignedIntegersJson() {
+ val data = AllUnsigned(
+ Int.MAX_VALUE.toUInt() + 10.toUInt(),
+ Long.MAX_VALUE.toULong() + 10.toULong(),
+ 239.toUByte(),
+ 65000.toUShort(),
+ -42,
+ Long.MIN_VALUE,
+ 1.1
+ )
+ assertJsonFormAndRestored(
+ AllUnsigned.serializer(),
+ data,
+ """{"uInt":2147483657,"uLong":9223372036854775817,"uByte":239,"uShort":65000,"signedInt":-42,"signedLong":-9223372036854775808,"double":1.1}""",
+ )
+ }
+
+ @Test
+ fun testUnsignedIntegersWithoutLongJson() {
+ val data = UnsignedWithoutLong(
+ Int.MAX_VALUE.toUInt() + 10.toUInt(),
+ 239.toUByte(),
+ 65000.toUShort(),
+ )
+ assertJsonFormAndRestored(
+ UnsignedWithoutLong.serializer(),
+ data,
+ """{"uInt":2147483657,"uByte":239,"uShort":65000}""",
+ )
+ }
+
+ @Test
+ fun testRoot() {
+ assertJsonFormAndRestored(UByte.serializer(), 220U, "220")
+ assertJsonFormAndRestored(UShort.serializer(), 65000U, "65000")
+ assertJsonFormAndRestored(UInt.serializer(), 2147483657U, "2147483657")
+ assertJsonFormAndRestored(ULong.serializer(), 9223372036854775817U, "9223372036854775817")
+ }
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ @Test
+ fun testRootArrays() = parametrizedTest {
+ assertJsonFormAndRestoredCustom(
+ UByteArraySerializer(),
+ ubyteArrayOf(1U, 220U),
+ "[1,220]"
+ ) { l, r -> l.contentEquals(r) }
+
+ assertJsonFormAndRestoredCustom(
+ UShortArraySerializer(),
+ ushortArrayOf(1U, 65000U),
+ "[1,65000]"
+ ) { l, r -> l.contentEquals(r) }
+
+ assertJsonFormAndRestoredCustom(
+ UIntArraySerializer(),
+ uintArrayOf(1U, 2147483657U),
+ "[1,2147483657]"
+ ) { l, r -> l.contentEquals(r) }
+
+ assertJsonFormAndRestoredCustom(
+ ULongArraySerializer(),
+ ulongArrayOf(1U, 9223372036854775817U),
+ "[1,9223372036854775817]"
+ ) { l, r -> l.contentEquals(r) }
+ }
+
+ @OptIn(ExperimentalUnsignedTypes::class)
+ @Test
+ fun testArrays() {
+ val data = UnsignedArrays(
+ ubyteArrayOf(1U, 220U),
+ ushortArrayOf(1U, 65000U),
+ uintArrayOf(1U, 2147483657U),
+ ulongArrayOf(1U, 9223372036854775817U)
+ )
+ val json = """{"uByte":[1,220],"uShort":[1,65000],"uInt":[1,2147483657],"uLong":[1,9223372036854775817]}"""
+
+ assertJsonFormAndRestored(UnsignedArrays.serializer(), data, json)
+ }
+
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/ValueClassesInSealedHierarchyTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/ValueClassesInSealedHierarchyTest.kt
new file mode 100644
index 0000000..ed96829
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/ValueClassesInSealedHierarchyTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features.inline
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.jvm.*
+import kotlin.test.*
+
+class ValueClassesInSealedHierarchyTest : JsonTestBase() {
+ @Test
+ fun testSingle() {
+ val single = "foo"
+ assertJsonFormAndRestored(
+ AnyValue.serializer(),
+ AnyValue.Single(single),
+ "\"$single\""
+ )
+ }
+
+ @Test
+ fun testComplex() {
+ val complexJson = """{"id":"1","name":"object"}"""
+ assertJsonFormAndRestored(
+ AnyValue.serializer(),
+ AnyValue.Complex(mapOf("id" to "1", "name" to "object")),
+ complexJson
+ )
+ }
+
+ @Test
+ fun testMulti() {
+ val multiJson = """["list","of","strings"]"""
+ assertJsonFormAndRestored(
+ AnyValue.serializer(),
+ AnyValue.Multi(listOf("list", "of", "strings")),
+ multiJson
+ )
+ }
+}
+
+
+// From https://github.com/Kotlin/kotlinx.serialization/issues/2159
+@Serializable(with = AnyValue.Companion.Serializer::class)
+sealed interface AnyValue {
+
+ @JvmInline
+ @Serializable
+ value class Single(val value: String) : AnyValue
+
+ @JvmInline
+ @Serializable
+ value class Multi(val values: List<String>) : AnyValue
+
+ @JvmInline
+ @Serializable
+ value class Complex(val values: Map<String, String>) : AnyValue
+
+ @JvmInline
+ @Serializable
+ value class Unknown(val value: JsonElement) : AnyValue
+
+ companion object {
+ object Serializer : JsonContentPolymorphicSerializer<AnyValue>(AnyValue::class) {
+
+ override fun selectDeserializer(element: JsonElement): DeserializationStrategy<AnyValue> =
+ when {
+ element is JsonArray && element.all { it is JsonPrimitive && it.isString } -> Multi.serializer()
+ element is JsonObject && element.values.all { it is JsonPrimitive && it.isString } -> Complex.serializer()
+ element is JsonPrimitive && element.isString -> Single.serializer()
+ else -> Unknown.serializer()
+ }
+ }
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedDiamondTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedDiamondTest.kt
new file mode 100644
index 0000000..6ba4713
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedDiamondTest.kt
@@ -0,0 +1,49 @@
+package kotlinx.serialization.features.sealed
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+class SealedDiamondTest : JsonTestBase() {
+
+ @Serializable
+ sealed interface A {}
+
+ @Serializable
+ sealed interface B : A {}
+
+ @Serializable
+ sealed interface C : A {}
+
+ @Serializable
+ @SerialName("X")
+ data class X(val i: Int) : B, C
+
+ @Serializable
+ @SerialName("Y")
+ object Y : B, C
+
+ @SerialName("E")
+ enum class E : B, C {
+ Q, W
+ }
+
+ @Test
+ fun testMultipleSuperSealedInterfacesDescriptor() {
+ val subclasses = A.serializer().descriptor.getElementDescriptor(1).elementDescriptors.map { it.serialName }
+ assertEquals(listOf("E", "X", "Y"), subclasses)
+ }
+
+ @Test
+ fun testMultipleSuperSealedInterfaces() {
+ @Serializable
+ data class Carrier(val a: A, val b: B, val c: C)
+ assertJsonFormAndRestored(
+ Carrier.serializer(),
+ Carrier(X(1), X(2), Y),
+ """{"a":{"type":"X","i":1},"b":{"type":"X","i":2},"c":{"type":"Y"}}"""
+ )
+ }
+
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedInterfacesJsonSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedInterfacesJsonSerializationTest.kt
new file mode 100644
index 0000000..a2e6bb6
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedInterfacesJsonSerializationTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.features.sealed
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class SealedInterfacesJsonSerializationTest : JsonTestBase() {
+ @Serializable
+ sealed interface I
+
+ @Serializable
+ sealed class Response: I {
+ @Serializable
+ @SerialName("ResponseInt")
+ data class ResponseInt(val i: Int): Response()
+
+ @Serializable
+ @SerialName("ResponseString")
+ data class ResponseString(val s: String): Response()
+ }
+
+ @Serializable
+ @SerialName("NoResponse")
+ object NoResponse: I
+
+ @Test
+ fun testSealedInterfaceJson() {
+ val messages = listOf(Response.ResponseInt(10), NoResponse, Response.ResponseString("foo"))
+ assertJsonFormAndRestored(
+ serializer(),
+ messages,
+ """[{"type":"ResponseInt","i":10},{"type":"NoResponse"},{"type":"ResponseString","s":"foo"}]"""
+ )
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
new file mode 100644
index 0000000..4959b7e
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.test.*
+import kotlin.test.*
+
+class BasicTypesSerializationTest : JsonTestBase() {
+
+ val goldenValue = """
+ {"unit":{},"boolean":true,"byte":10,"short":20,"int":30,"long":40,"float":50.1,"double":60.1,"char":"A","string":"Str0","enum":"POSITIVE","intData":{"intV":70},"unitN":null,"booleanN":null,"byteN":11,"shortN":21,"intN":31,"longN":41,"floatN":51.1,"doubleN":61.1,"charN":"B","stringN":"Str1","enumN":"NEUTRAL","intDataN":null,"listInt":[1,2,3],"listIntN":[4,5,null],"listNInt":[6,7,8],"listNIntN":[null,9,10],"listListEnumN":[["NEGATIVE",null]],"listIntData":[{"intV":1},{"intV":2},{"intV":3}],"listIntDataN":[{"intV":1},null,{"intV":3}],"tree":{"name":"root","left":{"name":"left","left":null,"right":null},"right":{"name":"right","left":{"name":"right.left","left":null,"right":null},"right":{"name":"right.right","left":null,"right":null}}},"mapStringInt":{"one":1,"two":2,"three":3},"mapIntStringN":{"0":null,"1":"first","2":"second"},"arrays":{"arrByte":[1,2,3],"arrInt":[100,200,300],"arrIntN":[null,-1,-2],"arrIntData":[{"intV":1},{"intV":2}]}}
+ """.trimIndent()
+
+ val goldenValue2 = """
+ {"unit":{},"boolean":true,"byte":10,"short":20,"int":30,"long":40,"float":50.5,"double":60.5,"char":"A","string":"Str0","enum":"POSITIVE","intData":{"intV":70},"unitN":null,"booleanN":null,"byteN":11,"shortN":21,"intN":31,"longN":41,"floatN":51.5,"doubleN":61.5,"charN":"B","stringN":"Str1","enumN":"NEUTRAL","intDataN":null,"listInt":[1,2,3],"listIntN":[4,5,null],"listNInt":[6,7,8],"listNIntN":[null,9,10],"listListEnumN":[["NEGATIVE",null]],"listIntData":[{"intV":1},{"intV":2},{"intV":3}],"listIntDataN":[{"intV":1},null,{"intV":3}],"tree":{"name":"root","left":{"name":"left","left":null,"right":null},"right":{"name":"right","left":{"name":"right.left","left":null,"right":null},"right":{"name":"right.right","left":null,"right":null}}},"mapStringInt":{"one":1,"two":2,"three":3},"mapIntStringN":{"0":null,"1":"first","2":"second"},"arrays":{"arrByte":[1,2,3],"arrInt":[100,200,300],"arrIntN":[null,-1,-2],"arrIntData":[{"intV":1},{"intV":2}]}}
+ """.trimIndent()
+
+ private fun testSerializationImpl(typesUmbrella: TypesUmbrella, goldenValue: String) = parametrizedTest { jsonTestingMode ->
+ val json = default.encodeToString(TypesUmbrella.serializer(), typesUmbrella)
+ assertEquals(goldenValue, json)
+ val instance = default.decodeFromString(TypesUmbrella.serializer(), json, jsonTestingMode)
+ assertEquals(typesUmbrella, instance)
+ assertNotSame(typesUmbrella, instance)
+ }
+
+ @Test
+ fun testSerialization() {
+ if (isWasm()) return //https://youtrack.jetbrains.com/issue/KT-59118/WASM-floating-point-toString-inconsistencies
+ testSerializationImpl(umbrellaInstance, goldenValue)
+ }
+
+ @Test
+ fun testSerialization2() = testSerializationImpl(umbrellaInstance2, goldenValue2)
+
+ @Test
+ fun testTopLevelPrimitive() = parametrizedTest { jsonTestingMode ->
+ testPrimitive(Unit, "{}", jsonTestingMode)
+ testPrimitive(false, "false", jsonTestingMode)
+ testPrimitive(1.toByte(), "1", jsonTestingMode)
+ testPrimitive(2.toShort(), "2", jsonTestingMode)
+ testPrimitive(3, "3", jsonTestingMode)
+ testPrimitive(4L, "4", jsonTestingMode)
+ testPrimitive(2.5f, "2.5", jsonTestingMode)
+ testPrimitive(3.5, "3.5", jsonTestingMode)
+ testPrimitive('c', "\"c\"", jsonTestingMode)
+ testPrimitive("string", "\"string\"", jsonTestingMode)
+ }
+
+ private inline fun <reified T : Any> testPrimitive(primitive: T, expectedJson: String, jsonTestingMode: JsonTestingMode) {
+ val json = default.encodeToString(primitive, jsonTestingMode)
+ assertEquals(expectedJson, json)
+ val instance = default.decodeFromString<T>(json, jsonTestingMode)
+ assertEquals(primitive, instance)
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt
new file mode 100644
index 0000000..c2dab08
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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 kotlin.test.*
+
+class JsonBuildersTest {
+
+ @Test
+ fun testBuildJson() {
+ val json = buildJsonObject {
+ putJsonObject("object") {
+ put("k", JsonPrimitive("v"))
+ }
+
+ putJsonArray("array") {
+ addJsonObject { put("nestedLiteral", true) }
+ }
+
+ val number: Number? = null
+ put("null", number)
+ put("primitive", JsonPrimitive(42))
+ put("boolean", true)
+ put("literal", "foo")
+ put("null2", null)
+ }
+ assertEquals("""{"object":{"k":"v"},"array":[{"nestedLiteral":true}],"null":null,"primitive":42,"boolean":true,"literal":"foo","null2":null}""", json.toString())
+ }
+
+ @Test
+ fun testBuildJsonArray() {
+ val json = buildJsonArray {
+ add(true)
+ addJsonArray {
+ for (i in 1..10) add(i)
+ }
+ add(null)
+ addJsonObject {
+ put("stringKey", "stringValue")
+ }
+ }
+ assertEquals("""[true,[1,2,3,4,5,6,7,8,9,10],null,{"stringKey":"stringValue"}]""", json.toString())
+ }
+
+ @Test
+ fun testBuildJsonArrayAddAll() {
+ assertEquals(
+ """[1,2,3,4,5,null]""",
+ buildJsonArray {
+ assertTrue { addAll(listOf(1, 2, 3, 4, 5, null)) }
+ }.toString()
+ )
+
+ assertEquals(
+ """["a","b","c",null]""",
+ buildJsonArray {
+ assertTrue { addAll(listOf("a", "b", "c", null)) }
+ }.toString()
+ )
+
+ assertEquals(
+ """[true,false,null]""",
+ buildJsonArray {
+ assertTrue { addAll(listOf(true, false, null)) }
+ }.toString()
+ )
+
+ assertEquals(
+ """[2,"b",true,null]""",
+ buildJsonArray {
+ assertTrue {
+ addAll(
+ listOf(
+ JsonPrimitive(2),
+ JsonPrimitive("b"),
+ JsonPrimitive(true),
+ JsonNull,
+ )
+ )
+ }
+ }.toString()
+ )
+
+ assertEquals(
+ """[{},{},{},null]""",
+ buildJsonArray {
+ assertTrue {
+ addAll(
+ listOf(
+ JsonObject(emptyMap()),
+ JsonObject(emptyMap()),
+ JsonObject(emptyMap()),
+ JsonNull
+ )
+ )
+ }
+ }.toString()
+ )
+
+ assertEquals(
+ """[[],[],[],null]""",
+ buildJsonArray {
+ assertTrue {
+ addAll(
+ listOf(
+ JsonArray(emptyList()),
+ JsonArray(emptyList()),
+ JsonArray(emptyList()),
+ JsonNull
+ )
+ )
+ }
+ }.toString()
+ )
+
+ assertEquals(
+ """[null,null]""",
+ buildJsonArray {
+ assertTrue {
+ addAll(listOf(JsonNull, JsonNull))
+ }
+ }.toString()
+ )
+ }
+
+ @Test
+ fun testBuildJsonArrayAddAllNotModified() {
+ assertEquals(
+ """[]""",
+ buildJsonArray {
+ // add collections
+ assertFalse { addAll(listOf<Number>()) }
+ assertFalse { addAll(listOf<String>()) }
+ assertFalse { addAll(listOf<Boolean>()) }
+
+ // add json elements
+ assertFalse { addAll(listOf()) }
+ assertFalse { addAll(listOf<JsonNull>()) }
+ assertFalse { addAll(listOf<JsonObject>()) }
+ assertFalse { addAll(listOf<JsonArray>()) }
+ assertFalse { addAll(listOf<JsonPrimitive>()) }
+ }.toString()
+ )
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonChunkedStringDecoderTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonChunkedStringDecoderTest.kt
new file mode 100644
index 0000000..fca258f
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonChunkedStringDecoderTest.kt
@@ -0,0 +1,74 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.test.assertFailsWithMessage
+import kotlin.test.*
+
+
+@Serializable(with = LargeStringSerializer::class)
+data class LargeStringData(val largeString: String)
+
+@Serializable
+data class ClassWithLargeStringDataField(val largeStringField: LargeStringData)
+
+
+object LargeStringSerializer : KSerializer<LargeStringData> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LargeStringContent", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): LargeStringData {
+ require(decoder is ChunkedDecoder) { "Only chunked decoder supported" }
+
+ val outStringBuilder = StringBuilder()
+
+ decoder.decodeStringChunked { chunk ->
+ outStringBuilder.append(chunk)
+ }
+ return LargeStringData(outStringBuilder.toString())
+ }
+
+ override fun serialize(encoder: Encoder, value: LargeStringData) {
+ encoder.encodeString(value.largeString)
+ }
+}
+
+open class JsonChunkedStringDecoderTest : JsonTestBase() {
+
+ @Test
+ fun decodePlainLenientString() {
+ val longString = "abcd".repeat(8192) // Make string more than 16k
+ val sourceObject = ClassWithLargeStringDataField(LargeStringData(longString))
+ val serializedObject = "{\"largeStringField\": $longString }"
+ val jsonWithLenientMode = Json { isLenient = true }
+ testDecodeInAllModes(jsonWithLenientMode, serializedObject, sourceObject)
+ }
+
+ @Test
+ fun decodePlainString() {
+ val longStringWithEscape = "${"abcd".repeat(4096)}\"${"abcd".repeat(4096)}" // Make string more than 16k
+ val sourceObject = ClassWithLargeStringDataField(LargeStringData(longStringWithEscape))
+ val serializedObject = Json.encodeToString(sourceObject)
+ testDecodeInAllModes(Json, serializedObject, sourceObject)
+ }
+
+ private fun testDecodeInAllModes(
+ seralizer: Json, serializedObject: String, sourceObject: ClassWithLargeStringDataField
+ ) {
+ /* Filter out Java Streams mode in common tests. Java streams tested separately in java tests */
+ JsonTestingMode.values().filterNot { it == JsonTestingMode.JAVA_STREAMS }.forEach { mode ->
+ if (mode == JsonTestingMode.TREE) {
+ assertFailsWithMessage<IllegalArgumentException>(
+ "Only chunked decoder supported", "Shouldn't decode JSON in TREE mode"
+ ) {
+ seralizer.decodeFromString<ClassWithLargeStringDataField>(serializedObject, mode)
+ }
+ } else {
+ val deserializedObject =
+ seralizer.decodeFromString<ClassWithLargeStringDataField>(serializedObject, mode)
+ assertEquals(sourceObject.largeStringField, deserializedObject.largeStringField)
+ }
+ }
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
similarity index 62%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
index 1e6b20d..3d7c332 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt
@@ -5,7 +5,7 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
-import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.test.assertFailsWithSerial
import kotlin.test.*
class JsonCoerceInputValuesTest : JsonTestBase() {
@@ -24,6 +24,21 @@
val foo: String
)
+ @Serializable
+ data class NullableEnumHolder(
+ val enum: SampleEnum?
+ )
+
+ @Serializable
+ class Uncoercable(
+ val s: String
+ )
+
+ @Serializable
+ class UncoercableEnum(
+ val e: SampleEnum
+ )
+
val json = Json {
coerceInputValues = true
isLenient = true
@@ -60,10 +75,10 @@
WithEnum(),
WithEnum.serializer()
)
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
json.decodeFromString(WithEnum.serializer(), """{"e":{"x":"definitely not a valid enum value"}}""")
}
- assertFailsWith<JsonDecodingException> { // test user still sees exception on missing quotes
+ assertFailsWithSerial("JsonDecodingException") { // test user still sees exception on missing quotes
Json(json) { isLenient = false }.decodeFromString(WithEnum.serializer(), """{"e":unknown_value}""")
}
}
@@ -98,4 +113,33 @@
assertEquals(expected, json.decodeFromString(MultipleValues.serializer(), input), "Failed on input: $input")
}
}
+
+ @Test
+ fun testNullSupportForEnums() = parametrizedTest(json) {
+ var decoded = decodeFromString<NullableEnumHolder>("""{"enum": null}""")
+ assertNull(decoded.enum)
+
+ 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/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt
similarity index 97%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt
index 91b65d9..351866d 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt
@@ -34,7 +34,7 @@
@Serializable
data class BList(@Id(1) val bs: List<B>)
- @Serializable
+ @Serializable(C.Companion::class)
data class C(@Id(1) val a: Int = 31, @Id(2) val b: Int = 42) {
@Serializer(forClass = C::class)
companion object : KSerializer<C> {
@@ -50,7 +50,7 @@
@Serializable
data class CList1(@Id(1) val c: List<C>)
- @Serializable
+ @Serializable(CList2.Companion::class)
data class CList2(@Id(1) val d: Int = 5, @Id(2) val c: List<C>) {
@Serializer(forClass = CList2::class)
companion object : KSerializer<CList2> {
@@ -63,7 +63,7 @@
}
}
- @Serializable
+ @Serializable(CList3.Companion::class)
data class CList3(@Id(1) val e: List<C> = emptyList(), @Id(2) val f: Int) {
@Serializer(forClass = CList3::class)
companion object : KSerializer<CList3> {
@@ -76,7 +76,7 @@
}
}
- @Serializable
+ @Serializable(CList4.Companion::class)
data class CList4(@Id(1) val g: List<C> = emptyList(), @Id(2) val h: Int) {
@Serializer(forClass = CList4::class)
companion object : KSerializer<CList4> {
@@ -89,7 +89,7 @@
}
}
- @Serializable
+ @Serializable(CList5.Companion::class)
data class CList5(@Id(1) val g: List<Int> = emptyList(), @Id(2) val h: Int) {
@Serializer(forClass = CList5::class)
companion object : KSerializer<CList5> {
@@ -199,7 +199,7 @@
fun testReadListOfOptional() = parametrizedTest { jsonTestingMode ->
val obj = listOf(C(a = 1), C(b = 2), C(3, 4))
val j = """[{"b":42,"a":1},{"b":2},{"b":4,"a":3}]"""
- val s = jsonNoAltNames.decodeFromString(ListSerializer<kotlinx.serialization.json.JsonCustomSerializersTest.C>(C), j, jsonTestingMode)
+ val s = jsonNoAltNames.decodeFromString(ListSerializer<C>(C), j, jsonTestingMode)
assertEquals(obj, s)
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
new file mode 100644
index 0000000..3cdfa08
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
@@ -0,0 +1,110 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlin.test.*
+
+class JsonElementDecodingTest : JsonTestBase() {
+
+ @Serializable
+ data class A(val a: Int = 42)
+
+ @Test
+ fun testTopLevelClass() = assertSerializedForm(A(), """{}""".trimMargin())
+
+ @Test
+ fun testTopLevelNullableClass() {
+ assertSerializedForm<A?>(A(), """{}""")
+ assertSerializedForm<A?>(null, "null")
+ }
+
+ @Test
+ fun testTopLevelPrimitive() = assertSerializedForm(42, """42""")
+
+ @Test
+ fun testTopLevelNullablePrimitive() {
+ assertSerializedForm<Int?>(42, """42""")
+ assertSerializedForm<Int?>(null, """null""")
+ }
+
+ @Test
+ fun testTopLevelList() = assertSerializedForm(listOf(42), """[42]""")
+
+ @Test
+ fun testTopLevelNullableList() {
+ assertSerializedForm<List<Int>?>(listOf(42), """[42]""")
+ assertSerializedForm<List<Int>?>(null, """null""")
+ }
+
+ private inline fun <reified T> assertSerializedForm(value: T, expectedString: String) {
+ val element = Json.encodeToJsonElement(value)
+ assertEquals(expectedString, element.toString())
+ assertEquals(value, Json.decodeFromJsonElement(element))
+ }
+
+ @Test
+ fun testDeepRecursion() {
+ // Reported as https://github.com/Kotlin/kotlinx.serialization/issues/1594
+ var json = """{ "a": %}"""
+ for (i in 0..12) {
+ json = json.replace("%", json)
+ }
+ json = json.replace("%", "0")
+ Json.parseToJsonElement(json)
+ }
+
+ private open class NullAsElementSerializer<T : Any>(private val serializer: KSerializer<T>, val nullElement: T) : KSerializer<T?> {
+ final override val descriptor: SerialDescriptor = serializer.descriptor.nullable
+
+ final override fun serialize(encoder: Encoder, value: T?) {
+ serializer.serialize(encoder, value ?: nullElement)
+ }
+
+ final override fun deserialize(decoder: Decoder): T = serializer.deserialize(decoder)
+ }
+
+ private object NullAsJsonNullJsonElementSerializer : NullAsElementSerializer<JsonElement>(JsonElement.serializer(), JsonNull)
+ private object NullAsJsonNullJsonPrimitiveSerializer : NullAsElementSerializer<JsonPrimitive>(JsonPrimitive.serializer(), JsonNull)
+ private object NullAsJsonNullJsonNullSerializer : NullAsElementSerializer<JsonNull>(JsonNull.serializer(), JsonNull)
+ private val noExplicitNullsOrDefaultsJson = Json {
+ explicitNulls = false
+ encodeDefaults = false
+ }
+
+ @Test
+ fun testNullableJsonElementDecoding() {
+ @Serializable
+ data class Wrapper(
+ @Serializable(NullAsJsonNullJsonElementSerializer::class)
+ val value: JsonElement? = null,
+ )
+
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
+ }
+
+ @Test
+ fun testNullableJsonPrimitiveDecoding() {
+ @Serializable
+ data class Wrapper(
+ @Serializable(NullAsJsonNullJsonPrimitiveSerializer::class)
+ val value: JsonPrimitive? = null,
+ )
+
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
+ }
+
+ @Test
+ fun testNullableJsonNullDecoding() {
+ @Serializable
+ data class Wrapper(
+ @Serializable(NullAsJsonNullJsonNullSerializer::class)
+ val value: JsonNull? = null,
+ )
+
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = JsonNull), """{"value":null}""", noExplicitNullsOrDefaultsJson)
+ assertJsonFormAndRestored(Wrapper.serializer(), Wrapper(value = null), """{}""", noExplicitNullsOrDefaultsJson)
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt
new file mode 100644
index 0000000..08d1eef
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt
@@ -0,0 +1,159 @@
+/*
+ * 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 kotlinx.serialization.test.*
+import kotlin.test.*
+
+
+class JsonErrorMessagesTest : JsonTestBase() {
+
+ @Test
+ fun testJsonTokensAreProperlyReported() = parametrizedTest { mode ->
+ val input1 = """{"boxed":4}"""
+ val input2 = """{"boxed":"str"}"""
+
+ val serString = serializer<Box<String>>()
+ val serInt = serializer<Box<Int>>()
+
+ checkSerializationException({
+ default.decodeFromString(serString, input1, mode)
+ }, { message ->
+ if (mode == JsonTestingMode.TREE)
+ assertContains(message, "String literal for key 'boxed' should be quoted.")
+ else
+ assertContains(
+ message,
+ "Unexpected JSON token at offset 9: Expected quotation mark '\"', but had '4' instead at path: \$.boxed"
+ )
+ })
+
+ checkSerializationException({
+ default.decodeFromString(serInt, input2, mode)
+ }, { message ->
+ if (mode != JsonTestingMode.TREE)
+ // we allow number values to be quoted, so the message pointing to 's' is correct
+ assertContains(
+ message,
+ "Unexpected JSON token at offset 9: Unexpected symbol 's' in numeric literal at path: \$.boxed"
+ )
+ else
+ assertContains(message, "Failed to parse literal as 'int' value")
+ })
+ }
+
+ @Test
+ fun testMissingClosingQuote() = parametrizedTest { mode ->
+ val input1 = """{"boxed:4}"""
+ val input2 = """{"boxed":"str}"""
+ val input3 = """{"boxed:"str"}"""
+ val serString = serializer<Box<String>>()
+ val serInt = serializer<Box<Int>>()
+
+ checkSerializationException({
+ default.decodeFromString(serInt, input1, mode)
+ }, { message ->
+ // For discussion:
+ // Technically, both of these messages are correct despite them being completely different.
+ // A `:` instead of `"` is a good guess, but `:`/`}` is a perfectly valid token inside Json string — for example,
+ // it can be some kind of path `{"foo:bar:baz":"my:resource:locator:{123}"}` or even URI used as a string key/value.
+ // So if the closing quote is missing, there's really no way to correctly tell where the key or value is supposed to end.
+ // Although we may try to unify these messages for consistency.
+ if (mode in setOf(JsonTestingMode.STREAMING, JsonTestingMode.TREE))
+ assertContains(
+ message,
+ "Unexpected JSON token at offset 7: Expected quotation mark '\"', but had ':' instead at path: \$"
+ )
+ else
+ assertContains(
+ message, "Unexpected EOF at path: \$"
+ )
+ })
+
+ checkSerializationException({
+ default.decodeFromString(serString, input2, mode)
+ }, { message ->
+ if (mode in setOf(JsonTestingMode.STREAMING, JsonTestingMode.TREE))
+ assertContains(
+ message,
+ "Unexpected JSON token at offset 13: Expected quotation mark '\"', but had '}' instead at path: \$"
+ )
+ else
+ assertContains(message, "Unexpected EOF at path: \$.boxed")
+ })
+
+ checkSerializationException({
+ default.decodeFromString(serString, input3, mode)
+ }, { message ->
+ assertContains(
+ message,
+ "Unexpected JSON token at offset 9: Expected colon ':', but had 's' instead at path: \$"
+ )
+ })
+ }
+
+ @Test
+ fun testUnquoted() = parametrizedTest { mode ->
+ val input1 = """{boxed:str}"""
+ val input2 = """{"boxed":str}"""
+ val ser = serializer<Box<String>>()
+
+ checkSerializationException({
+ default.decodeFromString(ser, input1, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 1: Expected quotation mark '"', but had 'b' instead at path: ${'$'}"""
+ )
+ })
+
+ checkSerializationException({
+ default.decodeFromString(ser, input2, mode)
+ }, { message ->
+ if (mode == JsonTestingMode.TREE) assertContains(
+ message,
+ """String literal for key 'boxed' should be quoted."""
+ )
+ else assertContains(
+ message,
+ """Unexpected JSON token at offset 9: Expected quotation mark '"', but had 's' instead at path: ${'$'}.boxed"""
+ )
+ })
+ }
+
+ @Test
+ fun testNullLiteralForNotNull() = parametrizedTest { mode ->
+ val input = """{"boxed":null}"""
+ val ser = serializer<Box<String>>()
+ checkSerializationException({
+ default.decodeFromString(ser, input, mode)
+ }, { message ->
+ if (mode == JsonTestingMode.TREE)
+ assertContains(message, "Unexpected 'null' literal when non-nullable string was expected")
+ else
+ assertContains(
+ message,
+ "Unexpected JSON token at offset 9: Expected string literal but 'null' literal was found at path: \$.boxed"
+ )
+ })
+ }
+
+ @Test
+ fun testEof() = parametrizedTest { mode ->
+ val input = """{"boxed":"""
+ checkSerializationException({
+ default.decodeFromString<Box<String>>(input, mode)
+ }, { message ->
+ if (mode == JsonTestingMode.TREE)
+ assertContains(message, "Cannot read Json element because of unexpected end of the input at path: $")
+ else
+ assertContains(message, "Expected quotation mark '\"', but had 'EOF' instead at path: \$.boxed")
+
+ })
+
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonExponentTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonExponentTest.kt
new file mode 100644
index 0000000..0f31ac5
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonExponentTest.kt
@@ -0,0 +1,79 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.test.*
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class JsonExponentTest : JsonTestBase() {
+ @Serializable
+ data class SomeData(val count: Long)
+ @Serializable
+ data class SomeDataDouble(val count: Double)
+
+ @Test
+ fun testExponentDecodingPositive() = parametrizedTest {
+ val decoded = Json.decodeFromString<SomeData>("""{ "count": 23e11 }""", it)
+ assertEquals(2300000000000, decoded.count)
+ }
+
+ @Test
+ fun testExponentDecodingNegative() = parametrizedTest {
+ val decoded = Json.decodeFromString<SomeData>("""{ "count": -10E1 }""", it)
+ assertEquals(-100, decoded.count)
+ }
+
+ @Test
+ fun testExponentDecodingPositiveDouble() = parametrizedTest {
+ val decoded = Json.decodeFromString<SomeDataDouble>("""{ "count": 1.5E1 }""", it)
+ assertEquals(15.0, decoded.count)
+ }
+
+ @Test
+ fun testExponentDecodingNegativeDouble() = parametrizedTest {
+ val decoded = Json.decodeFromString<SomeDataDouble>("""{ "count": -1e-1 }""", it)
+ assertEquals(-0.1, decoded.count)
+ }
+
+ @Test
+ fun testExponentDecodingErrorTruncatedDecimal() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeData>("""{ "count": -1E-1 }""", it) }
+ }
+
+ @Test
+ fun testExponentDecodingErrorExponent() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeData>("""{ "count": 1e-1e-1 }""", it) }
+ }
+
+ @Test
+ fun testExponentDecodingErrorExponentDouble() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeDataDouble>("""{ "count": 1e-1e-1 }""", it) }
+ }
+
+ @Test
+ fun testExponentOverflowDouble() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeDataDouble>("""{ "count": 10000e10000 }""", it) }
+ }
+
+ @Test
+ fun testExponentUnderflowDouble() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeDataDouble>("""{ "count": -100e2222 }""", it) }
+ }
+
+ @Test
+ fun testExponentOverflow() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeData>("""{ "count": 10000e10000 }""", it) }
+ }
+
+ @Test
+ fun testExponentUnderflow() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException")
+ { Json.decodeFromString<SomeData>("""{ "count": -10000e10000 }""", it) }
+ }
+}
\ No newline at end of file
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonHugeDataSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonHugeDataSerializationTest.kt
new file mode 100644
index 0000000..0a63326
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonHugeDataSerializationTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json
+
+import kotlinx.serialization.Serializable
+import kotlin.test.Test
+
+class JsonHugeDataSerializationTest : JsonTestBase() {
+
+ @Serializable
+ private data class Node(
+ val children: List<Node>
+ )
+
+ private fun createNodes(count: Int, depth: Int): List<Node> {
+ val ret = mutableListOf<Node>()
+ if (depth == 0) return ret
+ for (i in 0 until count) {
+ ret.add(Node(createNodes(1, depth - 1)))
+ }
+ return ret
+ }
+
+ @Test
+ fun test() {
+ // create some huge instance
+ val rootNode = Node(createNodes(1000, 10))
+
+ val expectedJson = Json.encodeToString(Node.serializer(), rootNode)
+
+ /*
+ The assertJsonFormAndRestored function, when checking the encoding, will call Json.encodeToString(...) for `JsonTestingMode.STREAMING`
+ since the string `expectedJson` was generated by the same function, the test will always consider
+ the encoding to the `STREAMING` mode is correct, even if there was actually an error there. So only TREE, JAVA_STREAMS and OKIO are actually being tested here
+ */
+ assertJsonFormAndRestored(Node.serializer(), rootNode, expectedJson)
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
similarity index 70%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
index e958cca..560e51f 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt
@@ -5,13 +5,12 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
+import kotlinx.serialization.builtins.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
-import kotlinx.serialization.descriptors.buildSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.json.internal.*
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.test.*
import kotlin.jvm.*
@@ -26,7 +25,6 @@
value class PrimitiveCarrier(val c: String)
data class ContextualValue(val c: String) {
- @Serializer(forClass = ContextualValue::class)
companion object: KSerializer<ContextualValue> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ContextualValue", PrimitiveKind.STRING)
@@ -45,6 +43,9 @@
private data class WithMap(val map: Map<Long, Long>)
@Serializable
+ private data class WithBooleanMap(val map: Map<Boolean, Boolean>)
+
+ @Serializable
private data class WithValueKeyMap(val map: Map<PrimitiveCarrier, Long>)
@Serializable
@@ -63,28 +64,54 @@
private data class WithContextualKey(val map: Map<@Contextual ContextualValue, Long>)
@Test
- fun testMapKeysShouldBeStrings() = parametrizedTest(default) {
+ fun testMapKeysSupportNumbers() = parametrizedTest {
assertStringFormAndRestored(
"""{"map":{"10":10,"20":20}}""",
WithMap(mapOf(10L to 10L, 20L to 20L)),
WithMap.serializer(),
- this
+ default
)
}
@Test
- fun testStructuredMapKeysShouldBeProhibitedByDefault() = parametrizedTest { streaming ->
- noLegacyJs {
- verifyProhibition(WithComplexKey(mapOf(IntData(42) to "42")), streaming)
- verifyProhibition(WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")), streaming)
+ fun testMapKeysSupportBooleans() = parametrizedTest {
+ assertStringFormAndRestored(
+ """{"map":{"true":false,"false":true}}""",
+ WithBooleanMap(mapOf(true to false, false to true)),
+ WithBooleanMap.serializer(),
+ default
+ )
+ }
+
+ // As a result of quoting ignorance when parsing primitives, it is possible to parse unquoted maps if Kotlin keys are non-string primitives.
+ // This is not spec-compliant, but I do not see any problems with it.
+ @Test
+ fun testMapDeserializesUnquotedKeys() = parametrizedTest {
+ assertEquals(WithMap(mapOf(10L to 10L, 20L to 20L)), default.decodeFromString("""{"map":{10:10,20:20}}"""))
+ assertEquals(
+ WithBooleanMap(mapOf(true to false, false to true)),
+ default.decodeFromString("""{"map":{true:false,false:true}}""")
+ )
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ MapSerializer(
+ String.serializer(),
+ Boolean.serializer()
+ ),"""{"map":{true:false,false:true}}"""
+ )
}
}
+ @Test
+ fun testStructuredMapKeysShouldBeProhibitedByDefault() = parametrizedTest { streaming ->
+ verifyProhibition(WithComplexKey(mapOf(IntData(42) to "42")), streaming)
+ verifyProhibition(WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")), streaming)
+ }
+
private inline fun <reified T: Any> verifyProhibition(value: T, streaming: JsonTestingMode) {
- val e = assertFailsWith<JsonException> {
+ assertFailsWithSerialMessage("JsonEncodingException", "can't be used in JSON as a key in the map") {
Json.encodeToString(value, streaming)
}
- assertTrue(e.message?.contains("can't be used in JSON as a key in the map") == true)
}
@Test
@@ -96,7 +123,7 @@
)
@Test
- fun testStructuredValueMapKeysAllowedWithFlag() = noLegacyJs {
+ fun testStructuredValueMapKeysAllowedWithFlag() {
assertJsonFormAndRestored(
WithComplexValueKey.serializer(),
WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")),
@@ -114,7 +141,7 @@
)
@Test
- fun testPrimitivesAreAllowedAsValueMapKeys() = noLegacyJs {
+ fun testPrimitivesAreAllowedAsValueMapKeys() {
assertJsonFormAndRestored(
WithValueKeyMap.serializer(),
WithValueKeyMap(mapOf(PrimitiveCarrier("fooKey") to 1)),
@@ -124,7 +151,7 @@
}
@Test
- fun testContextualValuePrimitivesAreAllowedAsValueMapKeys() = noLegacyJs {
+ fun testContextualValuePrimitivesAreAllowedAsValueMapKeys() {
assertJsonFormAndRestored(
WithContextualValueKey.serializer(),
WithContextualValueKey(mapOf(PrimitiveCarrier("fooKey") to 1)),
@@ -143,7 +170,7 @@
WithContextualKey(mapOf(ContextualValue("fooKey") to 1)),
"""{"map":{"fooKey":1}}""",
Json {
- serializersModule = SerializersModule { contextual(ContextualValue::class, ContextualValue.Companion) }
+ serializersModule = SerializersModule { contextual(ContextualValue::class, ContextualValue) }
}
)
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt
similarity index 90%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt
index 9799380..e7f107c 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt
@@ -120,11 +120,9 @@
@Test
fun testIgnoreUnknownKeysObject() = parametrizedTest { jsonTestingMode ->
- noLegacyJs {
- assertEquals(Holder(Object), lenient.decodeFromString("""{"o":{}}""", jsonTestingMode))
- assertEquals(Holder(Object), lenient.decodeFromString("""{"o":{"unknown":{"b":"c"}}}""", jsonTestingMode))
- assertEquals(Object, lenient.decodeFromString("""{}""", jsonTestingMode))
- assertEquals(Object, lenient.decodeFromString("""{"o":{"unknown":{"b":"c"}}}""", jsonTestingMode))
- }
+ assertEquals(Holder(Object), lenient.decodeFromString("""{"o":{}}""", jsonTestingMode))
+ assertEquals(Holder(Object), lenient.decodeFromString("""{"o":{"unknown":{"b":"c"}}}""", jsonTestingMode))
+ assertEquals(Object, lenient.decodeFromString("""{}""", jsonTestingMode))
+ assertEquals(Object, lenient.decodeFromString("""{"o":{"unknown":{"b":"c"}}}""", jsonTestingMode))
}
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
new file mode 100644
index 0000000..892696b
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.test.*
+import kotlin.test.*
+
+class JsonParserFailureModesTest : JsonTestBase() {
+
+ @Serializable
+ data class Holder(
+ val id: Long
+ )
+
+ @Test
+ fun testFailureModes() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id": "}""",
+ it
+ )
+ }
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id": ""}""",
+ it
+ )
+ }
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id":a}""",
+ it
+ )
+ }
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id":2.0}""",
+ it
+ )
+ }
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id2":2}""",
+ it
+ )
+ }
+ // 9223372036854775807 is Long.MAX_VALUE
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id":${Long.MAX_VALUE}""" + "00" + "}",
+ it
+ )
+ }
+ // -9223372036854775808 is Long.MIN_VALUE
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ Holder.serializer(),
+ """{"id":9223372036854775808}""",
+ it
+ )
+ }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """{"id"}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """{"id}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """{"i}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """{"}""", it) }
+ assertFailsWithMissingField { default.decodeFromString(Holder.serializer(), """{}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """{""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), """{""", it) }
+ }
+
+ @Serializable
+ class BooleanHolder(val b: Boolean)
+
+ @Test
+ fun testBoolean() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ BooleanHolder.serializer(),
+ """{"b": fals}""",
+ it
+ )
+ }
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString(
+ BooleanHolder.serializer(),
+ """{"b": 123}""",
+ it
+ )
+ }
+ }
+
+ @Serializable
+ class PrimitiveHolder(
+ val b: Byte = 0, val s: Short = 0, val i: Int = 0
+ )
+
+ @Test
+ fun testOverflow() = parametrizedTest {
+ // Byte overflow
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<PrimitiveHolder>("""{"b": 128}""", it) }
+ // Short overflow
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<PrimitiveHolder>("""{"s": 32768}""", it) }
+ // Int overflow
+ assertFailsWithSerial("JsonDecodingException") {
+ default.decodeFromString<PrimitiveHolder>(
+ """{"i": 2147483648}""",
+ it
+ )
+ }
+ }
+
+ @Test
+ fun testNoOverflow() = parametrizedTest {
+ default.decodeFromString<PrimitiveHolder>("""{"b": ${Byte.MAX_VALUE}}""", it)
+ default.decodeFromString<PrimitiveHolder>("""{"b": ${Byte.MIN_VALUE}}""", it)
+ default.decodeFromString<PrimitiveHolder>("""{"s": ${Short.MAX_VALUE}}""", it)
+ default.decodeFromString<PrimitiveHolder>("""{"s": ${Short.MIN_VALUE}}""", it)
+ default.decodeFromString<PrimitiveHolder>("""{"i": ${Int.MAX_VALUE}}""", it)
+ default.decodeFromString<PrimitiveHolder>("""{"i": ${Int.MIN_VALUE}}""", it)
+ default.decodeFromString<Holder>("""{"id": ${Long.MIN_VALUE.toString()}}""", it)
+ default.decodeFromString<Holder>("""{"id": ${Long.MAX_VALUE.toString()}}""", it)
+ }
+
+ @Test
+ fun testInvalidNumber() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":-}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":+}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":--}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":1-1}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":0-1}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":0-}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":a}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<Holder>("""{"id":-a}""", it) }
+ }
+
+
+ @Serializable
+ data class BooleanWrapper(val b: Boolean)
+
+ @Serializable
+ data class StringWrapper(val s: String)
+
+ @Test
+ fun testUnexpectedNull() = parametrizedTest {
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<BooleanWrapper>("""{"b":{"b":"b"}}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<BooleanWrapper>("""{"b":null}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<StringWrapper>("""{"s":{"s":"s"}}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<StringWrapper>("""{"s":null}""", it) }
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
similarity index 85%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
index 123214e..94f7052 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt
@@ -16,7 +16,7 @@
fun testQuotedBrace() {
val tree = parse("""{"x": "{"}""")
assertTrue("x" in tree)
- assertEquals("{", (tree.getValue("x") as JsonLiteral).content)
+ assertEquals("{", (tree.getValue("x") as JsonPrimitive).content)
}
private fun parse(input: String) = default.parseToJsonElement(input).jsonObject
@@ -25,19 +25,19 @@
fun testEmptyKey() {
val tree = parse("""{"":"","":""}""")
assertTrue("" in tree)
- assertEquals("", (tree.getValue("") as JsonLiteral).content)
+ assertEquals("", (tree.getValue("") as JsonPrimitive).content)
}
@Test
fun testEmptyValue() {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
parse("""{"X": "foo", "Y"}""")
}
}
@Test
fun testIncorrectUnicodeEscape() {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
parse("""{"X": "\uDD1H"}""")
}
}
@@ -83,18 +83,16 @@
}
private fun testTrailingComma(content: String) {
- val e = assertFailsWith<JsonDecodingException> { Json.parseToJsonElement(content) }
- val msg = e.message!!
- assertTrue(msg.contains("Unexpected trailing"))
+ assertFailsWithSerialMessage("JsonDecodingException", "Trailing comma before the end of JSON object") { Json.parseToJsonElement(content) }
}
@Test
fun testUnclosedStringLiteral() {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
parse("\"")
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
parse("""{"id":"""")
}
}
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/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
similarity index 71%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
index 4b39308..6f3b132 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt
@@ -1,19 +1,25 @@
/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 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.json.okio.decodeFromBufferedSource
+import kotlinx.serialization.json.okio.encodeToBufferedSink
import kotlinx.serialization.modules.EmptySerializersModule
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.test.*
import kotlin.test.assertEquals
+import okio.*
+import kotlin.test.assertTrue
+
enum class JsonTestingMode {
STREAMING,
TREE,
+ OKIO_STREAMS,
JAVA_STREAMS;
companion object {
@@ -43,9 +49,14 @@
encodeViaStream(serializer, value)
}
JsonTestingMode.TREE -> {
- val tree = writeJson(value, serializer)
+ val tree = writeJson(this, value, serializer)
encodeToString(tree)
}
+ JsonTestingMode.OKIO_STREAMS -> {
+ val buffer = Buffer()
+ encodeToBufferedSink(serializer, value, buffer)
+ buffer.readUtf8()
+ }
}
internal inline fun <reified T : Any> Json.decodeFromString(source: String, jsonTestingMode: JsonTestingMode): T {
@@ -66,11 +77,13 @@
decodeViaStream(deserializer, source)
}
JsonTestingMode.TREE -> {
- val lexer = StringJsonLexer(source)
- val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor)
- val tree = input.decodeJsonElement()
- lexer.expectEof()
- readJson(tree, deserializer)
+ val tree = decodeStringToJsonTree(this, deserializer, source)
+ readJson(this, tree, deserializer)
+ }
+ JsonTestingMode.OKIO_STREAMS -> {
+ val buffer = Buffer()
+ buffer.writeUtf8(source)
+ decodeFromBufferedSource(deserializer, buffer)
}
}
@@ -78,6 +91,8 @@
processResults(buildList {
add(runCatching { test(JsonTestingMode.STREAMING) })
add(runCatching { test(JsonTestingMode.TREE) })
+ add(runCatching { test(JsonTestingMode.OKIO_STREAMS) })
+
if (isJvm()) {
add(runCatching { test(JsonTestingMode.JAVA_STREAMS) })
}
@@ -87,7 +102,7 @@
private inner class SwitchableJson(
val json: Json,
val jsonTestingMode: JsonTestingMode,
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
) : StringFormat {
override fun <T> encodeToString(serializer: SerializationStrategy<T>, value: T): String {
return json.encodeToString(serializer, value, jsonTestingMode)
@@ -101,7 +116,8 @@
protected fun parametrizedTest(json: Json, test: StringFormat.() -> Unit) {
val streamingResult = runCatching { SwitchableJson(json, JsonTestingMode.STREAMING).test() }
val treeResult = runCatching { SwitchableJson(json, JsonTestingMode.TREE).test() }
- processResults(listOf(streamingResult, treeResult))
+ val okioResult = runCatching { SwitchableJson(json, JsonTestingMode.OKIO_STREAMS).test() }
+ processResults(listOf(streamingResult, treeResult, okioResult))
}
protected fun processResults(results: List<Result<*>>) {
@@ -140,4 +156,21 @@
assertEquals(data, deserialized, "Failed with streaming = $jsonTestingMode")
}
}
+ /**
+ * Same as [assertStringFormAndRestored], but tests both json converters (streaming and tree)
+ * via [parametrizedTest]. Use custom checker for deserialized value.
+ */
+ internal fun <T> assertJsonFormAndRestoredCustom(
+ serializer: KSerializer<T>,
+ data: T,
+ expected: String,
+ check: (T, T) -> Boolean
+ ) {
+ parametrizedTest { jsonTestingMode ->
+ val serialized = Json.encodeToString(serializer, data, jsonTestingMode)
+ assertEquals(expected, serialized, "Failed with streaming = $jsonTestingMode")
+ val deserialized: T = Json.decodeFromString(serializer, serialized, jsonTestingMode)
+ assertTrue("Failed with streaming = $jsonTestingMode\n\tsource value =$data\n\tdeserialized value=$deserialized") { check(data, deserialized) }
+ }
+ }
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt
similarity index 93%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt
index c46a11d..ebe0631 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt
@@ -8,6 +8,7 @@
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.test.assertFailsWithSerial
import kotlin.test.*
class JsonTransientTest : JsonTestBase() {
@@ -51,7 +52,7 @@
@Test
fun testThrowTransient() = parametrizedTest { jsonTestingMode ->
- assertFailsWith(JsonDecodingException::class) {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(Data.serializer(), """{"a":0,"b":100500,"c":"Hello"}""", jsonTestingMode)
}
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt
similarity index 99%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt
index 3506e1f..6e60038 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt
@@ -7,7 +7,6 @@
import kotlinx.serialization.*
import kotlinx.serialization.test.*
import kotlin.test.*
-import kotlin.test.assertTrue
class JsonTreeTest : JsonTestBase() {
@Serializable
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt
similarity index 85%
rename from formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt
index b89e853..d24c7d7 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt
@@ -7,6 +7,7 @@
import kotlinx.serialization.*
import kotlinx.serialization.builtins.*
import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.test.assertFailsWithSerial
import kotlin.test.*
class LenientTest : JsonTestBase() {
@@ -36,28 +37,28 @@
@Test
fun testQuotedBoolean() = parametrizedTest {
val json = """{"i":1, "l":2, "b":"true", "s":"string"}"""
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), json, it) }
+ assertEquals(value, default.decodeFromString(Holder.serializer(), json, it))
assertEquals(value, lenient.decodeFromString(Holder.serializer(), json, it))
}
@Test
fun testUnquotedStringValue() = parametrizedTest {
val json = """{"i":1, "l":2, "b":true, "s":string}"""
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), json, it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), json, it) }
assertEquals(value, lenient.decodeFromString(Holder.serializer(), json, it))
}
@Test
fun testUnquotedKey() = parametrizedTest {
val json = """{"i":1, "l":2, b:true, "s":"string"}"""
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), json, it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(Holder.serializer(), json, it) }
assertEquals(value, lenient.decodeFromString(Holder.serializer(), json, it))
}
@Test
fun testUnquotedStringInArray() = parametrizedTest {
val json = """{"l":[1, 2, ss]}"""
- assertFailsWith<JsonDecodingException> { default.decodeFromString(ListHolder.serializer(), json, it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(ListHolder.serializer(), json, it) }
assertEquals(listValue, lenient.decodeFromString(ListHolder.serializer(), json, it))
}
@@ -68,7 +69,7 @@
fun testNullsProhibited() = parametrizedTest {
assertEquals(StringWrapper("nul"), lenient.decodeFromString("""{"s":nul}""", it))
assertEquals(StringWrapper("null1"), lenient.decodeFromString("""{"s":null1}""", it))
- assertFailsWith<JsonException> { lenient.decodeFromString<StringWrapper>("""{"s":null}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString<StringWrapper>("""{"s":null}""", it) }
}
@Serializable
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt
similarity index 98%
rename from formats/json/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt
index 37abd95..bd8f104 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt
@@ -15,7 +15,6 @@
@Serializable
data class StringPair(val a: String, val b: String)
- @Serializer(forClass = StringPair::class)
object StringPairSerializer : KSerializer<StringPair> {
override val descriptor: SerialDescriptor = buildSerialDescriptor("package.StringPair", StructureKind.MAP) {
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt
similarity index 81%
rename from formats/json/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt
index 745b074..fad07e6 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt
@@ -47,11 +47,9 @@
}
private fun test(box: Box, expected: String, jsonTestingMode: JsonTestingMode) {
- val e1 = assertFailsWith<JsonException> { default.encodeToString(Box.serializer(), box, jsonTestingMode) }
- assertTrue { e1.message!!.contains("Unexpected special floating-point value") }
+ assertFailsWithSerialMessage("JsonEncodingException", "Unexpected special floating-point value") { default.encodeToString(Box.serializer(), box, jsonTestingMode) }
assertEquals(expected, json.encodeToString(Box.serializer(), box, jsonTestingMode))
assertEquals(box, json.decodeFromString(Box.serializer(), expected, jsonTestingMode))
- val e2 = assertFailsWith<JsonException> { default.decodeFromString(Box.serializer(), expected, jsonTestingMode) }
- assertTrue { e2.message!!.contains("Unexpected special floating-point value") }
+ assertFailsWithSerialMessage("JsonDecodingException", "Unexpected special floating-point value") { default.decodeFromString(Box.serializer(), expected, jsonTestingMode) }
}
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt
new file mode 100644
index 0000000..0916de5
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt
@@ -0,0 +1,128 @@
+/*
+ * 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 kotlinx.serialization.test.*
+import kotlin.test.*
+
+class TrailingCommaTest : JsonTestBase() {
+ val tj = Json { allowTrailingComma = true }
+
+ @Serializable
+ data class Optional(val data: String = "")
+
+ @Serializable
+ data class MultipleFields(val a: String, val b: String, val c: String)
+
+ private val multipleFields = MultipleFields("1", "2", "3")
+
+ @Serializable
+ data class WithMap(val m: Map<String, String>)
+
+ private val withMap = WithMap(mapOf("a" to "1", "b" to "2", "c" to "3"))
+
+ @Serializable
+ data class WithList(val l: List<Int>)
+
+ private val withList = WithList(listOf(1, 2, 3))
+
+ @Test
+ fun basic() = parametrizedTest { mode ->
+ val sd = """{"data":"str",}"""
+ assertEquals(Optional("str"), tj.decodeFromString<Optional>(sd, mode))
+ }
+
+ @Test
+ fun trailingCommaNotAllowedByDefaultForObjects() = parametrizedTest { mode ->
+ val sd = """{"data":"str",}"""
+ checkSerializationException({
+ default.decodeFromString<Optional>(sd, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 13: Trailing comma before the end of JSON object"""
+ )
+ })
+ }
+
+ @Test
+ fun trailingCommaNotAllowedByDefaultForLists() = parametrizedTest { mode ->
+ val sd = """{"l":[1,]}"""
+ checkSerializationException({
+ default.decodeFromString<WithList>(sd, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 7: Trailing comma before the end of JSON array"""
+ )
+ })
+ }
+
+ @Test
+ fun trailingCommaNotAllowedByDefaultForMaps() = parametrizedTest { mode ->
+ val sd = """{"m":{"a": "b",}}"""
+ checkSerializationException({
+ default.decodeFromString<WithMap>(sd, mode)
+ }, { message ->
+ assertContains(
+ message,
+ """Unexpected JSON token at offset 14: Trailing comma before the end of JSON object"""
+ )
+ })
+ }
+
+ @Test
+ fun emptyObjectNotAllowed() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Unexpected leading comma") {
+ tj.decodeFromString<Optional>("""{,}""", mode)
+ }
+ }
+
+ @Test
+ fun emptyListNotAllowed() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Unexpected leading comma") {
+ tj.decodeFromString<WithList>("""{"l":[,]}""", mode)
+ }
+ }
+
+ @Test
+ fun emptyMapNotAllowed() = parametrizedTest { mode ->
+ assertFailsWithMessage<SerializationException>("Unexpected leading comma") {
+ tj.decodeFromString<WithMap>("""{"m":{,}}""", mode)
+ }
+ }
+
+ @Test
+ fun testMultipleFields() = parametrizedTest { mode ->
+ val input = """{"a":"1","b":"2","c":"3", }"""
+ assertEquals(multipleFields, tj.decodeFromString(input, mode))
+ }
+
+ @Test
+ fun testWithMap() = parametrizedTest { mode ->
+ val input = """{"m":{"a":"1","b":"2","c":"3", }}"""
+
+ assertEquals(withMap, tj.decodeFromString(input, mode))
+ }
+
+ @Test
+ fun testWithList() = parametrizedTest { mode ->
+ val input = """{"l":[1, 2, 3, ]}"""
+ assertEquals(withList, tj.decodeFromString(input, mode))
+ }
+
+ @Serializable
+ data class Mixed(val mf: MultipleFields, val wm: WithMap, val wl: WithList)
+
+ @Test
+ fun testMixed() = parametrizedTest { mode ->
+ //language=JSON5
+ val input = """{"mf":{"a":"1","b":"2","c":"3",},
+ "wm":{"m":{"a":"1","b":"2","c":"3",},},
+ "wl":{"l":[1, 2, 3,],},}"""
+ assertEquals(Mixed(multipleFields, withMap, withList), tj.decodeFromString(input, 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/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt
similarity index 88%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt
index 13e9231..b7d4f12 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt
@@ -8,6 +8,7 @@
import kotlinx.serialization.json.*
import kotlinx.serialization.json.internal.*
import kotlinx.serialization.modules.*
+import kotlinx.serialization.test.assertFailsWithSerial
import kotlin.test.*
class JsonPolymorphismExceptionTest : JsonTestBase() {
@@ -30,7 +31,7 @@
}
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
Json { serializersModule = serialModule }.decodeFromString(Base.serializer(), """{"type":"derived","nested":null}""", jsonTestingMode)
}
}
@@ -43,7 +44,7 @@
}
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
Json { serializersModule = serialModule }.decodeFromString(Base.serializer(), """{"nested":{}}""", jsonTestingMode)
}
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonTreeDecoderPolymorphicTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonTreeDecoderPolymorphicTest.kt
new file mode 100644
index 0000000..9d8e861
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonTreeDecoderPolymorphicTest.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.Serializable
+import kotlinx.serialization.json.*
+import kotlin.test.*
+
+class JsonTreeDecoderPolymorphicTest : JsonTestBase() {
+
+ @Serializable
+ sealed class Sealed
+
+ @Serializable
+ data class ClassContainingItself(
+ val a: String,
+ val b: String,
+ val c: ClassContainingItself? = null,
+ val d: String?
+ ) : Sealed()
+
+ val inner = ClassContainingItself(
+ "InnerA",
+ "InnerB",
+ null,
+ "InnerC"
+ )
+ val outer = ClassContainingItself(
+ "OuterA",
+ "OuterB",
+ inner,
+ "OuterC"
+ )
+
+ @Test
+ fun testDecodingWhenClassContainsItself() = parametrizedTest { jsonTestingMode ->
+ val encoded = default.encodeToString(outer as Sealed, jsonTestingMode)
+ val decoded: Sealed = Json.decodeFromString(encoded, jsonTestingMode)
+ assertEquals(outer, decoded)
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
similarity index 97%
rename from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
index e46de17..e70d89c 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt
@@ -38,7 +38,6 @@
@Serializable
internal data class OuterNullableBox(@Polymorphic val outerBase: OuterBase?, @Polymorphic val innerBase: InnerBase?)
-@SharedImmutable
internal val polymorphicTestModule = SerializersModule {
polymorphic(InnerBase::class) {
subclass(InnerImpl.serializer())
@@ -51,13 +50,11 @@
}
}
-@SharedImmutable
internal val polymorphicJson = Json {
serializersModule = polymorphicTestModule
encodeDefaults = true
}
-@SharedImmutable
internal val polymorphicRelaxedJson = Json {
isLenient = true
serializersModule = polymorphicTestModule
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt
similarity index 83%
rename from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt
index ba4672a..c571ec9 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt
@@ -5,7 +5,6 @@
package kotlinx.serialization.json.serializers
import kotlinx.serialization.json.*
-import kotlinx.serialization.json.internal.*
import kotlinx.serialization.test.*
import kotlin.test.*
@@ -27,22 +26,22 @@
@Test
fun testTopLevelJsonObjectAsElement() = parametrizedTest(default) {
- assertStringFormAndRestored(expectedTopLevel, prebuiltJson(), JsonElementSerializer)
+ assertStringFormAndRestored(expectedTopLevel, prebuiltJson(), JsonElement.serializer())
}
@Test
fun testJsonArrayToString() {
val prebuiltJson = prebuiltJson()
- val string = lenient.encodeToString(JsonArraySerializer, prebuiltJson)
+ val string = lenient.encodeToString(JsonArray.serializer(), prebuiltJson)
assertEquals(string, prebuiltJson.toString())
}
@Test
fun testMixedLiterals() = parametrizedTest { jsonTestingMode ->
val json = """[1, "2", 3, "4"]"""
- val array = default.decodeFromString(JsonArraySerializer, json, jsonTestingMode)
+ val array = default.decodeFromString(JsonArray.serializer(), json, jsonTestingMode)
array.forEachIndexed { index, element ->
- require(element is JsonLiteral)
+ require(element is JsonPrimitive)
assertEquals(index % 2 == 1, element.isString)
}
}
@@ -58,17 +57,17 @@
@Test
fun testEmptyArray() = parametrizedTest { jsonTestingMode ->
- assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArraySerializer, "[]", jsonTestingMode))
- assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArraySerializer, "[ ]", jsonTestingMode))
- assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArraySerializer, "[\n\n]", jsonTestingMode))
- assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArraySerializer, "[ \t]", jsonTestingMode))
+ assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArray.serializer(), "[]", jsonTestingMode))
+ assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArray.serializer(), "[ ]", jsonTestingMode))
+ assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArray.serializer(), "[\n\n]", jsonTestingMode))
+ assertEquals(JsonArray(emptyList()), lenient.decodeFromString(JsonArray.serializer(), "[ \t]", jsonTestingMode))
}
@Test
fun testWhitespaces() = parametrizedTest { jsonTestingMode ->
assertEquals(
JsonArray(listOf(1, 2, 3, 4, 5).map(::JsonPrimitive)),
- lenient.decodeFromString(JsonArraySerializer, "[1, 2, 3, \n 4, 5]", jsonTestingMode)
+ lenient.decodeFromString(JsonArray.serializer(), "[1, 2, 3, \n 4, 5]", jsonTestingMode)
)
}
@@ -89,9 +88,9 @@
}
private fun testFails(input: String, errorMessage: String, jsonTestingMode: JsonTestingMode) {
- assertFailsWithMessage<JsonDecodingException>(errorMessage) {
+ assertFailsWithSerial("JsonDecodingException", errorMessage) {
lenient.decodeFromString(
- JsonArraySerializer,
+ JsonArray.serializer(),
input,
jsonTestingMode
)
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt
similarity index 69%
rename from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt
index 83de592..934afca 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt
@@ -5,7 +5,7 @@
package kotlinx.serialization.json.serializers
import kotlinx.serialization.json.*
-import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.test.assertFailsWithSerialMessage
import kotlinx.serialization.test.assertStringFormAndRestored
import kotlin.test.*
@@ -18,8 +18,7 @@
@Test
fun testJsonNullFailure() = parametrizedTest(default) {
- val t = assertFailsWith<JsonException> { default.decodeFromString(JsonNullWrapper.serializer(), "{\"element\":\"foo\"}", JsonTestingMode.STREAMING) }
- assertTrue { t.message!!.contains("'null' literal") }
+ assertFailsWithSerialMessage("JsonDecodingException", "'null' literal") { default.decodeFromString(JsonNullWrapper.serializer(), "{\"element\":\"foo\"}", JsonTestingMode.STREAMING) }
}
@Test
@@ -34,28 +33,28 @@
@Test
fun testTopLevelJsonNull() = parametrizedTest { jsonTestingMode ->
- val string = default.encodeToString(JsonNullSerializer, JsonNull, jsonTestingMode)
+ val string = default.encodeToString(JsonNull.serializer(), JsonNull, jsonTestingMode)
assertEquals("null", string)
- assertEquals(JsonNull, default.decodeFromString(JsonNullSerializer, string, jsonTestingMode))
+ assertEquals(JsonNull, default.decodeFromString(JsonNull.serializer(), string, jsonTestingMode))
}
@Test
fun testTopLevelJsonNullAsElement() = parametrizedTest { jsonTestingMode ->
- val string = default.encodeToString(JsonElementSerializer, JsonNull, jsonTestingMode)
+ val string = default.encodeToString(JsonElement.serializer(), JsonNull, jsonTestingMode)
assertEquals("null", string)
- assertEquals(JsonNull, default.decodeFromString(JsonElementSerializer, string, jsonTestingMode))
+ assertEquals(JsonNull, default.decodeFromString(JsonElement.serializer(), string, jsonTestingMode))
}
@Test
fun testTopLevelJsonNullAsPrimitive() = parametrizedTest { jsonTestingMode ->
- val string = default.encodeToString(JsonPrimitiveSerializer, JsonNull, jsonTestingMode)
+ val string = default.encodeToString(JsonPrimitive.serializer(), JsonNull, jsonTestingMode)
assertEquals("null", string)
- assertEquals(JsonNull, default.decodeFromString(JsonPrimitiveSerializer, string, jsonTestingMode))
+ assertEquals(JsonNull, default.decodeFromString(JsonPrimitive.serializer(), string, jsonTestingMode))
}
@Test
fun testJsonNullToString() {
- val string = default.encodeToString(JsonPrimitiveSerializer, JsonNull)
+ val string = default.encodeToString(JsonPrimitive.serializer(), JsonNull)
assertEquals(string, JsonNull.toString())
}
}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt
new file mode 100644
index 0000000..9a65eff
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.serializers
+
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.json.*
+import kotlinx.serialization.json.internal.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class JsonObjectSerializerTest : JsonTestBase() {
+
+ private val expected = """{"element":{"literal":1,"nullKey":null,"nested":{"another literal":"some value"},"\\. escaped":"\\. escaped","\n new line":"\n new line"}}"""
+ private val expectedTopLevel = """{"literal":1,"nullKey":null,"nested":{"another literal":"some value"},"\\. escaped":"\\. escaped","\n new line":"\n new line"}"""
+
+ @Test
+ fun testJsonObject() = parametrizedTest(default) {
+ assertStringFormAndRestored(expected, JsonObjectWrapper(prebuiltJson()), JsonObjectWrapper.serializer())
+ }
+
+ @Test
+ fun testJsonObjectAsElement() = parametrizedTest(default) {
+ assertStringFormAndRestored(expected, JsonElementWrapper(prebuiltJson()), JsonElementWrapper.serializer())
+ }
+
+ @Test
+ fun testTopLevelJsonObject() = parametrizedTest (default) {
+ assertStringFormAndRestored(expectedTopLevel, prebuiltJson(), JsonObject.serializer())
+ }
+
+ @Test
+ fun testTopLevelJsonObjectAsElement() = parametrizedTest (default) {
+ assertStringFormAndRestored(expectedTopLevel, prebuiltJson(), JsonElement.serializer())
+ }
+
+ @Test
+ fun testJsonObjectToString() {
+ val prebuiltJson = prebuiltJson()
+ val string = lenient.encodeToString(JsonElement.serializer(), prebuiltJson)
+ assertEquals(string, prebuiltJson.toString())
+ }
+
+ @Test
+ fun testDocumentationSample() {
+ val string = Json.encodeToString(JsonElement.serializer(), buildJsonObject { put("key", 1.0) })
+ val literal = Json.decodeFromString(JsonElement.serializer(), string)
+ assertEquals(JsonObject(mapOf("key" to JsonPrimitive(1.0))), literal)
+ }
+
+ @Test
+ fun testMissingCommas() = parametrizedTest { jsonTestingMode ->
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{ \"1\": \"2\" \"3\":\"4\"}", jsonTestingMode) }
+ }
+
+ @Test
+ fun testEmptyObject() = parametrizedTest { jsonTestingMode ->
+ assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObject.serializer(), "{}", jsonTestingMode))
+ assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObject.serializer(), "{}", jsonTestingMode))
+ assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObject.serializer(), "{\n\n}", jsonTestingMode))
+ assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObject.serializer(), "{ \t}", jsonTestingMode))
+ }
+
+ @Test
+ fun testInvalidObject() = parametrizedTest { jsonTestingMode ->
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(JsonObject.serializer(), "{\"a\":\"b\"]", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(JsonObject.serializer(), "{", jsonTestingMode) }
+ if (jsonTestingMode != JsonTestingMode.JAVA_STREAMS) // Streams support dangling characters
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(JsonObject.serializer(), "{}}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString(JsonObject.serializer(), "{]", jsonTestingMode) }
+ }
+
+ @Test
+ fun testWhitespaces() = parametrizedTest { jsonTestingMode ->
+ assertEquals(
+ JsonObject(mapOf("1" to JsonPrimitive(2), "3" to JsonPrimitive(4), "5" to JsonPrimitive(6))),
+ lenient.decodeFromString(JsonObject.serializer(), "{1: 2, 3: \n 4, 5:6}", jsonTestingMode)
+ )
+ }
+
+ @Test
+ fun testExcessiveCommas() = parametrizedTest { jsonTestingMode ->
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{\"a\":\"b\",}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{\"a\",}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{,\"1\":\"2\"}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{,\"1\":\"2\",}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{,,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{,,\"1\":\"2\"}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{\"1\":\"2\",,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{\"1\":\"2\",,\"2\":\"2\"}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{, ,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(JsonObject.serializer(), "{,\n,}", jsonTestingMode) }
+ }
+
+ @Serializable
+ data class Holder(val a: String)
+
+ @Test
+ fun testExcessiveCommasInObject() = parametrizedTest { jsonTestingMode ->
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{\"a\":\"b\",}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{\"a\",}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{,\"a\":\"b\"}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{,\"a\":\"b\",}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{,,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{,,\"a\":\"b\"}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{\"a\":\"b\",,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{, ,}", jsonTestingMode) }
+ assertFailsWithSerial("JsonDecodingException") { lenient.decodeFromString(Holder.serializer(), "{,\n,}", jsonTestingMode) }
+ }
+
+ private fun prebuiltJson(): JsonObject {
+ return buildJsonObject {
+ put("literal", 1)
+ put("nullKey", JsonNull)
+ putJsonObject("nested") {
+ put("another literal", "some value")
+ }
+ put("\\. escaped", "\\. escaped")
+ put("\n new line", "\n new line")
+ }
+ }
+}
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt
new file mode 100644
index 0000000..72f8a4f
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.serializers
+
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class JsonPrimitiveSerializerTest : JsonTestBase() {
+
+ @Test
+ fun testJsonPrimitiveDouble() = parametrizedTest { jsonTestingMode ->
+ if (isJs()) return@parametrizedTest // JS toString numbers
+
+
+ val wrapper = JsonPrimitiveWrapper(JsonPrimitive(1.0))
+ val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
+ assertEquals("{\"primitive\":1.0}", string)
+ assertEquals(JsonPrimitiveWrapper(JsonPrimitive(1.0)), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
+ }
+
+ @Test
+ fun testJsonPrimitiveInt() = parametrizedTest { jsonTestingMode ->
+ val wrapper = JsonPrimitiveWrapper(JsonPrimitive(1))
+ val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
+ assertEquals("{\"primitive\":1}", string)
+ assertEquals(JsonPrimitiveWrapper(JsonPrimitive(1)), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
+ }
+
+
+ @Test
+ fun testJsonPrimitiveString() = parametrizedTest { jsonTestingMode ->
+ val wrapper = JsonPrimitiveWrapper(JsonPrimitive("foo"))
+ val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
+ assertEquals("{\"primitive\":\"foo\"}", string)
+ assertEquals(JsonPrimitiveWrapper(JsonPrimitive("foo")), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
+ }
+
+ @Test
+ fun testJsonPrimitiveStringNumber() = parametrizedTest { jsonTestingMode ->
+ val wrapper = JsonPrimitiveWrapper(JsonPrimitive("239"))
+ val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
+ assertEquals("{\"primitive\":\"239\"}", string)
+ assertEquals(JsonPrimitiveWrapper(JsonPrimitive("239")), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
+ }
+
+ @Test
+ fun testJsonUnquotedLiteralNumbers() = parametrizedTest { jsonTestingMode ->
+ listOf(
+ "99999999999999999999999999999999999999999999999999999999999999999999999999",
+ "99999999999999999999999999999999999999.999999999999999999999999999999999999",
+ "-99999999999999999999999999999999999999999999999999999999999999999999999999",
+ "-99999999999999999999999999999999999999.999999999999999999999999999999999999",
+ "2.99792458e8",
+ "-2.99792458e8",
+ ).forEach { literalNum ->
+ val literalNumJson = JsonUnquotedLiteral(literalNum)
+ val wrapper = JsonPrimitiveWrapper(literalNumJson)
+ val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
+ assertEquals("{\"primitive\":$literalNum}", string, "mode:$jsonTestingMode")
+ assertEquals(
+ JsonPrimitiveWrapper(literalNumJson),
+ default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode),
+ "mode:$jsonTestingMode",
+ )
+ }
+ }
+
+ @Test
+ fun testTopLevelPrimitive() = parametrizedTest { jsonTestingMode ->
+ val string = default.encodeToString(JsonPrimitive.serializer(), JsonPrimitive(42), jsonTestingMode)
+ assertEquals("42", string)
+ assertEquals(JsonPrimitive(42), default.decodeFromString(JsonPrimitive.serializer(), string))
+ }
+
+ @Test
+ fun testTopLevelPrimitiveAsElement() = parametrizedTest { jsonTestingMode ->
+ if (isJs()) return@parametrizedTest // JS toString numbers
+ val string = default.encodeToString(JsonElement.serializer(), JsonPrimitive(1.3), jsonTestingMode)
+ assertEquals("1.3", string)
+ assertEquals(JsonPrimitive(1.3), default.decodeFromString(JsonElement.serializer(), string, jsonTestingMode))
+ }
+
+ @Test
+ fun testJsonLiteralStringToString() {
+ val literal = JsonPrimitive("some string literal")
+ val string = default.encodeToString(JsonPrimitive.serializer(), literal)
+ assertEquals(string, literal.toString())
+ }
+
+ @Test
+ fun testJsonLiteralIntToString() {
+ val literal = JsonPrimitive(0)
+ val string = default.encodeToString(JsonPrimitive.serializer(), literal)
+ assertEquals(string, literal.toString())
+ }
+
+ @Test
+ fun testJsonLiterals() {
+ testLiteral(0L, "0")
+ testLiteral(0, "0")
+ testLiteral(0.0, "0.0")
+ testLiteral(0.0f, "0.0")
+ testLiteral(Long.MAX_VALUE, "9223372036854775807")
+ testLiteral(Long.MIN_VALUE, "-9223372036854775808")
+ testLiteral(Float.MAX_VALUE, "3.4028235E38")
+ testLiteral(Float.MIN_VALUE, "1.4E-45")
+ testLiteral(Double.MAX_VALUE, "1.7976931348623157E308")
+ testLiteral(Double.MIN_VALUE, "4.9E-324")
+ testLiteral(Int.MAX_VALUE, "2147483647")
+ testLiteral(Int.MIN_VALUE, "-2147483648")
+ }
+
+ private fun testLiteral(number: Number, jvmExpectedString: String) {
+ val literal = JsonPrimitive(number)
+ val string = default.encodeToString(JsonPrimitive.serializer(), literal)
+ assertEquals(string, literal.toString())
+ if (isJvm()) { // We can rely on stable double/float format only on JVM
+ assertEquals(string, jvmExpectedString)
+ }
+ }
+
+ /**
+ * Helper function for [testJsonPrimitiveUnsignedNumbers]
+ *
+ * Asserts that an [unsigned number][actual] can be used to create a [JsonPrimitive][actualPrimitive],
+ * which can be decoded correctly.
+ *
+ * @param expected the expected string value of [actual]
+ * @param actual the unsigned number
+ * @param T should be an unsigned number
+ */
+ private inline fun <reified T> assertUnsignedNumberEncoding(
+ expected: String,
+ actual: T,
+ actualPrimitive: JsonPrimitive,
+ ) {
+ assertEquals(
+ expected,
+ actualPrimitive.toString(),
+ "expect ${T::class.simpleName} $actual can be used to create a JsonPrimitive"
+ )
+
+ parametrizedTest { mode ->
+ assertEquals(
+ expected,
+ default.encodeToString(JsonElement.serializer(), actualPrimitive, mode),
+ "expect ${T::class.simpleName} primitive can be decoded",
+ )
+ }
+ }
+
+ @Test
+ fun testJsonPrimitiveUnsignedNumbers() {
+
+ val expectedActualUBytes: Map<String, UByte> = mapOf(
+ "0" to 0u,
+ "1" to 1u,
+ "255" to UByte.MAX_VALUE,
+ )
+
+ expectedActualUBytes.forEach { (expected, actual) ->
+ assertUnsignedNumberEncoding(expected, actual, JsonPrimitive(actual))
+ }
+
+ val expectedActualUShorts: Map<String, UShort> = mapOf(
+ "0" to 0u,
+ "1" to 1u,
+ "255" to UByte.MAX_VALUE.toUShort(),
+ "65535" to UShort.MAX_VALUE,
+ )
+
+ expectedActualUShorts.forEach { (expected, actual) ->
+ assertUnsignedNumberEncoding(expected, actual, JsonPrimitive(actual))
+ }
+
+ val expectedActualUInts: Map<String, UInt> = mapOf(
+ "0" to 0u,
+ "1" to 1u,
+ "255" to UByte.MAX_VALUE.toUInt(),
+ "65535" to UShort.MAX_VALUE.toUInt(),
+ "4294967295" to UInt.MAX_VALUE,
+ )
+
+ expectedActualUInts.forEach { (expected, actual) ->
+ assertUnsignedNumberEncoding(expected, actual, JsonPrimitive(actual))
+ }
+
+ val expectedActualULongs: Map<String, ULong> = mapOf(
+ "0" to 0u,
+ "1" to 1u,
+ "255" to UByte.MAX_VALUE.toULong(),
+ "65535" to UShort.MAX_VALUE.toULong(),
+ "4294967295" to UInt.MAX_VALUE.toULong(),
+ "18446744073709551615" to ULong.MAX_VALUE,
+ )
+
+ expectedActualULongs.forEach { (expected, actual) ->
+ assertUnsignedNumberEncoding(expected, actual, JsonPrimitive(actual))
+ }
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt
similarity index 98%
rename from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt
index 00a78a2..dd4d51e 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt
@@ -9,7 +9,7 @@
class JsonTreeTest : JsonTestBase() {
- private fun parse(input: String): JsonElement = default.decodeFromString(JsonElementSerializer, input)
+ private fun parse(input: String): JsonElement = default.decodeFromString(JsonElement.serializer(), input)
@Test
fun testParseWithoutExceptions() {
diff --git a/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonUnquotedLiteralTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonUnquotedLiteralTest.kt
new file mode 100644
index 0000000..e809004
--- /dev/null
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonUnquotedLiteralTest.kt
@@ -0,0 +1,140 @@
+package kotlinx.serialization.json.serializers
+
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.json.*
+import kotlinx.serialization.test.assertFailsWithSerialMessage
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class JsonUnquotedLiteralTest : JsonTestBase() {
+
+ private fun assertUnquotedLiteralEncoded(inputValue: String) {
+ val unquotedElement = JsonUnquotedLiteral(inputValue)
+
+ assertEquals(
+ inputValue,
+ unquotedElement.toString(),
+ "expect JsonElement.toString() returns the unquoted input value"
+ )
+
+ parametrizedTest { mode ->
+ assertEquals(inputValue, default.encodeToString(JsonElement.serializer(), unquotedElement, mode))
+ }
+ }
+
+ @Test
+ fun testUnquotedJsonNumbers() {
+ assertUnquotedLiteralEncoded("1")
+ assertUnquotedLiteralEncoded("-1")
+ assertUnquotedLiteralEncoded("100.0")
+ assertUnquotedLiteralEncoded("-100.0")
+
+ assertUnquotedLiteralEncoded("9999999999999999999999999999999999999999999999999999999.9999999999999999999999999999999999999999999999999999999")
+ assertUnquotedLiteralEncoded("-9999999999999999999999999999999999999999999999999999999.9999999999999999999999999999999999999999999999999999999")
+
+ assertUnquotedLiteralEncoded("99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999")
+ assertUnquotedLiteralEncoded("-99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999")
+
+ assertUnquotedLiteralEncoded("2.99792458e8")
+ assertUnquotedLiteralEncoded("-2.99792458e8")
+
+ assertUnquotedLiteralEncoded("2.99792458E8")
+ assertUnquotedLiteralEncoded("-2.99792458E8")
+
+ assertUnquotedLiteralEncoded("11.399999999999")
+ assertUnquotedLiteralEncoded("0.30000000000000004")
+ assertUnquotedLiteralEncoded("0.1000000000000000055511151231257827021181583404541015625")
+ }
+
+ @Test
+ fun testUnquotedJsonWhitespaceStrings() {
+ assertUnquotedLiteralEncoded("")
+ assertUnquotedLiteralEncoded(" ")
+ assertUnquotedLiteralEncoded("\t")
+ assertUnquotedLiteralEncoded("\t\t\t")
+ assertUnquotedLiteralEncoded("\r\n")
+ assertUnquotedLiteralEncoded("\n")
+ assertUnquotedLiteralEncoded("\n\n\n")
+ }
+
+ @Test
+ fun testUnquotedJsonStrings() {
+ assertUnquotedLiteralEncoded("lorem")
+ assertUnquotedLiteralEncoded(""""lorem"""")
+ assertUnquotedLiteralEncoded(
+ """
+ Well, my name is Freddy Kreuger
+ I've got the Elm Street blues
+ I've got a hand like a knife rack
+ And I die in every film!
+ """.trimIndent()
+ )
+ }
+
+ @Test
+ fun testUnquotedJsonObjects() {
+ assertUnquotedLiteralEncoded("""{"some":"json"}""")
+ assertUnquotedLiteralEncoded("""{"some":"json","object":true,"count":1,"array":[1,2.0,-333,"4",boolean]}""")
+ }
+
+ @Test
+ fun testUnquotedJsonArrays() {
+ assertUnquotedLiteralEncoded("""[1,2,3]""")
+ assertUnquotedLiteralEncoded("""["a","b","c"]""")
+ assertUnquotedLiteralEncoded("""[true,false]""")
+ assertUnquotedLiteralEncoded("""[1,2.0,-333,"4",boolean]""")
+ assertUnquotedLiteralEncoded("""[{"some":"json","object":true,"count":1,"array":[1,2.0,-333,"4",boolean]}]""")
+ assertUnquotedLiteralEncoded("""[{"some":"json","object":true,"count":1,"array":[1,2.0,-333,"4",boolean]},{"some":"json","object":true,"count":1,"array":[1,2.0,-333,"4",boolean]}]""")
+ }
+
+ @Test
+ fun testUnquotedJsonNull() {
+ assertEquals(JsonNull, JsonUnquotedLiteral(null))
+ }
+
+ @Test
+ fun testUnquotedJsonNullString() {
+ fun test(block: () -> Unit) {
+ assertFailsWithSerialMessage(
+ exceptionName = "JsonEncodingException",
+ message = "Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive",
+ block = block,
+ )
+ }
+
+ test { JsonUnquotedLiteral("null") }
+ test { JsonUnquotedLiteral(JsonNull.content) }
+ test { buildJsonObject { put("key", JsonUnquotedLiteral("null")) } }
+ }
+
+ @Test
+ fun testUnquotedJsonInvalidMapKeyIsEscaped() {
+ val mapSerializer = MapSerializer(
+ JsonPrimitive.serializer(),
+ JsonPrimitive.serializer(),
+ )
+
+ fun test(expected: String, input: String) = parametrizedTest { mode ->
+ val data = mapOf(JsonUnquotedLiteral(input) to JsonPrimitive("invalid key"))
+
+ assertEquals(
+ """ {"$expected":"invalid key"} """.trim(),
+ default.encodeToString(mapSerializer, data, mode),
+ )
+ }
+
+ test(" ", " ")
+ test(
+ """ \\\"\\\" """.trim(),
+ """ \"\" """.trim(),
+ )
+ test(
+ """ \\\\\\\" """.trim(),
+ """ \\\" """.trim(),
+ )
+ test(
+ """ {\\\"I'm not a valid JSON object key\\\"} """.trim(),
+ """ {\"I'm not a valid JSON object key\"} """.trim(),
+ )
+ }
+}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/ContextualTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/ContextualTest.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/test/ContextualTest.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/ContextualTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
similarity index 62%
rename from formats/json/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
index c4a6b98..8c3633b 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
@@ -5,12 +5,12 @@
package kotlinx.serialization.test
enum class Platform {
- JVM, JS_LEGACY, JS_IR, NATIVE
+ JVM, JS, NATIVE, WASM
}
public expect val currentPlatform: Platform
-public fun isJs(): Boolean = currentPlatform == Platform.JS_LEGACY || currentPlatform == Platform.JS_IR
-public fun isJsLegacy(): Boolean = currentPlatform == Platform.JS_LEGACY
+public fun isJs(): Boolean = currentPlatform == Platform.JS
public fun isJvm(): Boolean = currentPlatform == Platform.JVM
public fun isNative(): Boolean = currentPlatform == Platform.NATIVE
+public fun isWasm(): Boolean = currentPlatform == Platform.WASM
\ No newline at end of file
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/TestHelpers.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
similarity index 93%
rename from formats/json/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
index d178c87..27ac19f 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
@@ -1,6 +1,9 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.serialization.test
import kotlinx.serialization.*
@@ -31,10 +34,6 @@
if (!isJs()) test()
}
-inline fun noLegacyJs(test: () -> Unit) {
- if (!isJsLegacy()) test()
-}
-
inline fun jvmOnly(test: () -> Unit) {
if (isJvm()) test()
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/TestId.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestId.kt
similarity index 100%
rename from formats/json/commonTest/src/kotlinx/serialization/test/TestId.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/TestId.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/test/TestingFramework.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
similarity index 61%
rename from formats/json/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
rename to formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
index 064828a..3ec0714 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt
@@ -52,6 +52,35 @@
assertEquals(original, restored)
}
+inline fun assertFailsWithSerial(
+ exceptionName: String,
+ assertionMessage: String? = null,
+ block: () -> Unit
+) {
+ val exception = assertFailsWith(SerializationException::class, assertionMessage, block)
+ assertEquals(
+ exceptionName,
+ exception::class.simpleName,
+ "Expected exception with type '${exceptionName}' but got '${exception::class.simpleName}'"
+ )
+}
+inline fun assertFailsWithSerialMessage(
+ exceptionName: String,
+ message: String,
+ assertionMessage: String? = null,
+ block: () -> Unit
+) {
+ val exception = assertFailsWith(SerializationException::class, assertionMessage, block)
+ assertEquals(
+ exceptionName,
+ exception::class.simpleName,
+ "Expected exception type '$exceptionName' but actual is '${exception::class.simpleName}'"
+ )
+ assertTrue(
+ exception.message!!.contains(message),
+ "expected:<$message> but was:<${exception.message}>"
+ )
+}
inline fun <reified T : Throwable> assertFailsWithMessage(
message: String,
assertionMessage: String? = null,
@@ -60,6 +89,12 @@
val exception = assertFailsWith(T::class, assertionMessage, block)
assertTrue(
exception.message!!.contains(message),
- "Expected message '${exception.message}' to contain substring '$message'"
+ "expected:<$message> but was:<${exception.message}>"
)
}
+
+inline fun checkSerializationException(action: () -> Unit, assertions: SerializationException.(String) -> Unit) {
+ val e = assertFailsWith(SerializationException::class, action)
+ assertNotNull(e.message)
+ e.assertions(e.message!!)
+}
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt
similarity index 100%
rename from formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt
similarity index 100%
rename from formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt
similarity index 98%
rename from formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt
index 0af00c6..3ff05ba 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt
@@ -8,7 +8,6 @@
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
-import kotlinx.serialization.test.noLegacyJs
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -85,7 +84,7 @@
}
@Test
- fun testCustomClassDiscriminator() = noLegacyJs {
+ fun testCustomClassDiscriminator() {
val value = SealedCustom.DataClassChild("custom-discriminator-test")
encodeAndDecode(SealedCustom.serializer(), value, objectJson) {
assertEquals("data_class", this["sealed_custom"])
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt
similarity index 90%
rename from formats/json/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt
index 9c5011a..2daf0bb 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt
@@ -4,10 +4,14 @@
package kotlinx.serialization.json
-import kotlinx.serialization.json.internal.*
import kotlinx.serialization.*
import kotlin.test.*
+/**
+ * [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER]
+ */
+internal const val MAX_SAFE_INTEGER: Double = 9007199254740991.toDouble() // 2^53 - 1
+
class DynamicToLongTest {
@Serializable
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt
similarity index 92%
rename from formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt
index ead1896..e419064 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt
@@ -4,13 +4,8 @@
package kotlinx.serialization.json
-import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.encoding.*
import kotlinx.serialization.*
-import kotlinx.serialization.modules.*
-import kotlinx.serialization.test.*
import kotlin.test.*
-import kotlin.test.assertFailsWith
class EncodeToDynamicSpecialCasesTest {
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt
similarity index 98%
rename from formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt
index 1c3c24c..74196b7 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt
@@ -92,10 +92,9 @@
WITH_SERIALNAME_red
}
- @Serializable
+ @Serializable(MyFancyClass.Companion::class)
data class MyFancyClass(val value: String) {
- @Serializer(forClass = MyFancyClass::class)
companion object : KSerializer<MyFancyClass> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("MyFancyClass", PrimitiveKind.STRING)
@@ -363,8 +362,8 @@
public inline fun <reified T : Any> assertDynamicForm(
data: T,
- serializer: KSerializer<T> = EmptySerializersModule.serializer(),
- skipEqualsCheck:Boolean = false,
+ serializer: KSerializer<T> = EmptySerializersModule().serializer(),
+ skipEqualsCheck: Boolean = false,
noinline assertions: ((T, dynamic) -> Unit)? = null
) {
val serialized = Json.encodeToDynamic(serializer, data)
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt
similarity index 100%
rename from formats/json/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt
similarity index 100%
rename from formats/json/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt
diff --git a/formats/json/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt
similarity index 90%
rename from formats/json/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt
index 09cf3d4..0c519fc 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt
@@ -29,7 +29,7 @@
}
@Test
- fun testParsesAllAlternativeNamesDynamic() = noLegacyJs {
+ fun testParsesAllAlternativeNamesDynamic() {
for (input in listOf(inputString1, inputString2)) {
parameterizedCoercingTest { json, msg ->
val data = json.decodeFromDynamic(JsonNamesTest.WithNames.serializer(), input)
@@ -39,7 +39,7 @@
}
@Test
- fun testEnumSupportsAlternativeNames() = noLegacyJs {
+ fun testEnumSupportsAlternativeNames() {
val input = js("""{"enumList":["VALUE_A", "someValue", "some_value", "VALUE_B"], "checkCoercion":"someValue"}""")
val expected = JsonNamesTest.WithEnumNames(
listOf(
@@ -55,14 +55,14 @@
}
@Test
- fun topLevelEnumSupportAlternativeNames() = noLegacyJs {
+ fun topLevelEnumSupportAlternativeNames() {
parameterizedCoercingTest { json, msg ->
assertEquals(JsonNamesTest.AlternateEnumNames.VALUE_A, json.decodeFromDynamic(js("\"someValue\"")), msg)
}
}
@Test
- fun testThrowsAnErrorOnDuplicateNames2() = noLegacyJs {
+ fun testThrowsAnErrorOnDuplicateNames2() {
val serializer = JsonNamesTest.CollisionWithAlternate.serializer()
parameterizedCoercingTest { json, _ ->
assertFailsWithMessage<SerializationException>(
diff --git a/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt
new file mode 100644
index 0000000..a1f7b0e
--- /dev/null
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt
@@ -0,0 +1,39 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.features.*
+import kotlin.test.*
+
+class JsonNamingStrategyDynamicTest: JsonTestBase() {
+ private val jsForm = js("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"QUX"}""")
+ private val jsFormNeedsCoercing = js("""{"simple":"a","one_word":"b","already_in_snake":"c","a_lot_of_words":"d","first_capitalized":"e","has_acronym_url":"BAZ","has_digit123_and_postfix":"QUX","coercion_test":"invalid"}""")
+
+ private fun doTest(json: Json) {
+ val j = Json(json) {
+ namingStrategy = JsonNamingStrategy.SnakeCase
+ }
+ val foo = JsonNamingStrategyTest.Foo()
+ assertDynamicForm(foo)
+ assertEquals(foo, j.decodeFromDynamic(jsForm))
+ }
+
+ @Test
+ fun testNamingStrategyWorksWithCoercing() {
+ val j = Json(default) {
+ coerceInputValues = true
+ useAlternativeNames = false
+ namingStrategy = JsonNamingStrategy.SnakeCase
+ }
+ assertEquals(JsonNamingStrategyTest.Foo(), j.decodeFromDynamic(jsFormNeedsCoercing))
+ }
+
+ @Test
+ fun testJsonNamingStrategyWithAlternativeNames() = doTest(Json(default) {
+ useAlternativeNames = true
+ })
+
+ @Test
+ fun testJsonNamingStrategyWithoutAlternativeNames() = doTest(Json(default) {
+ useAlternativeNames = false
+ })
+}
diff --git a/formats/json-tests/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..23627d1
--- /dev/null
+++ b/formats/json-tests/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.JS
diff --git a/formats/json/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt
similarity index 100%
rename from formats/json/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt
rename to formats/json-tests/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt
diff --git a/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$$serializer.class b/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$$serializer.class
new file mode 100644
index 0000000..3d27cb9
--- /dev/null
+++ b/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$$serializer.class
Binary files differ
diff --git a/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$Companion.class b/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$Companion.class
new file mode 100644
index 0000000..9d74dad
--- /dev/null
+++ b/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$Companion.class
Binary files differ
diff --git a/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo.class b/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo.class
new file mode 100644
index 0000000..e22f486
--- /dev/null
+++ b/formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo.class
Binary files differ
diff --git a/formats/json/jvmTest/resources/corner_cases/listing.txt b/formats/json-tests/jvmTest/resources/corner_cases/listing.txt
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/listing.txt
rename to formats/json-tests/jvmTest/resources/corner_cases/listing.txt
diff --git a/formats/json/jvmTest/resources/corner_cases/number_1.0.json b/formats/json-tests/jvmTest/resources/corner_cases/number_1.0.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/number_1.0.json
rename to formats/json-tests/jvmTest/resources/corner_cases/number_1.0.json
diff --git a/formats/json/jvmTest/resources/corner_cases/number_1.000000000000000005.json b/formats/json-tests/jvmTest/resources/corner_cases/number_1.000000000000000005.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/number_1.000000000000000005.json
rename to formats/json-tests/jvmTest/resources/corner_cases/number_1.000000000000000005.json
diff --git a/formats/json/jvmTest/resources/corner_cases/number_1000000000000000.json b/formats/json-tests/jvmTest/resources/corner_cases/number_1000000000000000.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/number_1000000000000000.json
rename to formats/json-tests/jvmTest/resources/corner_cases/number_1000000000000000.json
diff --git a/formats/json/jvmTest/resources/corner_cases/number_10000000000000000999.json b/formats/json-tests/jvmTest/resources/corner_cases/number_10000000000000000999.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/number_10000000000000000999.json
rename to formats/json-tests/jvmTest/resources/corner_cases/number_10000000000000000999.json
diff --git a/formats/json/jvmTest/resources/corner_cases/number_1e-999.json b/formats/json-tests/jvmTest/resources/corner_cases/number_1e-999.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/number_1e-999.json
rename to formats/json-tests/jvmTest/resources/corner_cases/number_1e-999.json
diff --git a/formats/json/jvmTest/resources/corner_cases/number_1e6.json b/formats/json-tests/jvmTest/resources/corner_cases/number_1e6.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/number_1e6.json
rename to formats/json-tests/jvmTest/resources/corner_cases/number_1e6.json
diff --git a/formats/json/jvmTest/resources/corner_cases/object_key_nfc_nfd.json b/formats/json-tests/jvmTest/resources/corner_cases/object_key_nfc_nfd.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/object_key_nfc_nfd.json
rename to formats/json-tests/jvmTest/resources/corner_cases/object_key_nfc_nfd.json
diff --git a/formats/json/jvmTest/resources/corner_cases/object_key_nfd_nfc.json b/formats/json-tests/jvmTest/resources/corner_cases/object_key_nfd_nfc.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/object_key_nfd_nfc.json
rename to formats/json-tests/jvmTest/resources/corner_cases/object_key_nfd_nfc.json
diff --git a/formats/json/jvmTest/resources/corner_cases/object_same_key_different_values.json b/formats/json-tests/jvmTest/resources/corner_cases/object_same_key_different_values.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/object_same_key_different_values.json
rename to formats/json-tests/jvmTest/resources/corner_cases/object_same_key_different_values.json
diff --git a/formats/json/jvmTest/resources/corner_cases/object_same_key_same_value.json b/formats/json-tests/jvmTest/resources/corner_cases/object_same_key_same_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/object_same_key_same_value.json
rename to formats/json-tests/jvmTest/resources/corner_cases/object_same_key_same_value.json
diff --git a/formats/json/jvmTest/resources/corner_cases/object_same_key_unclear_values.json b/formats/json-tests/jvmTest/resources/corner_cases/object_same_key_unclear_values.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/object_same_key_unclear_values.json
rename to formats/json-tests/jvmTest/resources/corner_cases/object_same_key_unclear_values.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_1_escaped_invalid_codepoint.json b/formats/json-tests/jvmTest/resources/corner_cases/string_1_escaped_invalid_codepoint.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_1_escaped_invalid_codepoint.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_1_escaped_invalid_codepoint.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_1_invalid_codepoint.json b/formats/json-tests/jvmTest/resources/corner_cases/string_1_invalid_codepoint.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_1_invalid_codepoint.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_1_invalid_codepoint.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_2_escaped_invalid_codepoints.json b/formats/json-tests/jvmTest/resources/corner_cases/string_2_escaped_invalid_codepoints.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_2_escaped_invalid_codepoints.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_2_escaped_invalid_codepoints.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_2_invalid_codepoints.json b/formats/json-tests/jvmTest/resources/corner_cases/string_2_invalid_codepoints.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_2_invalid_codepoints.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_2_invalid_codepoints.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_3_escaped_invalid_codepoints.json b/formats/json-tests/jvmTest/resources/corner_cases/string_3_escaped_invalid_codepoints.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_3_escaped_invalid_codepoints.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_3_escaped_invalid_codepoints.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_3_invalid_codepoints.json b/formats/json-tests/jvmTest/resources/corner_cases/string_3_invalid_codepoints.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_3_invalid_codepoints.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_3_invalid_codepoints.json
diff --git a/formats/json/jvmTest/resources/corner_cases/string_with_escaped_NULL.json b/formats/json-tests/jvmTest/resources/corner_cases/string_with_escaped_NULL.json
similarity index 100%
rename from formats/json/jvmTest/resources/corner_cases/string_with_escaped_NULL.json
rename to formats/json-tests/jvmTest/resources/corner_cases/string_with_escaped_NULL.json
diff --git a/formats/json/jvmTest/resources/corpus.zip b/formats/json-tests/jvmTest/resources/corpus.zip
similarity index 100%
rename from formats/json/jvmTest/resources/corpus.zip
rename to formats/json-tests/jvmTest/resources/corpus.zip
Binary files differ
diff --git a/formats/json/jvmTest/resources/spec_cases/listing.txt b/formats/json-tests/jvmTest/resources/spec_cases/listing.txt
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/listing.txt
rename to formats/json-tests/jvmTest/resources/spec_cases/listing.txt
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_1_true_without_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_1_true_without_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_1_true_without_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_1_true_without_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_a_invalid_utf8.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_a_invalid_utf8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_a_invalid_utf8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_a_invalid_utf8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_colon_instead_of_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_colon_instead_of_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_colon_instead_of_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_colon_instead_of_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_comma_after_close.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_comma_after_close.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_comma_after_close.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_comma_after_close.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_comma_and_number.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_comma_and_number.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_comma_and_number.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_comma_and_number.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_double_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_double_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_double_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_double_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_double_extra_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_double_extra_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_double_extra_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_double_extra_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_extra_close.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_extra_close.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_extra_close.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_extra_close.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_extra_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_extra_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_extra_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_extra_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_incomplete.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_incomplete.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_incomplete.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_incomplete.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_incomplete_invalid_value.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_incomplete_invalid_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_incomplete_invalid_value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_incomplete_invalid_value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_inner_array_no_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_inner_array_no_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_inner_array_no_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_inner_array_no_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_invalid_utf8.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_invalid_utf8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_invalid_utf8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_invalid_utf8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_items_separated_by_semicolon.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_items_separated_by_semicolon.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_items_separated_by_semicolon.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_items_separated_by_semicolon.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_just_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_just_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_just_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_just_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_just_minus.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_just_minus.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_just_minus.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_just_minus.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_missing_value.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_missing_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_missing_value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_missing_value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_newlines_unclosed.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_newlines_unclosed.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_newlines_unclosed.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_newlines_unclosed.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_number_and_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_number_and_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_number_and_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_number_and_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_number_and_several_commas.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_number_and_several_commas.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_number_and_several_commas.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_number_and_several_commas.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_spaces_vertical_tab_formfeed.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_spaces_vertical_tab_formfeed.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_spaces_vertical_tab_formfeed.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_spaces_vertical_tab_formfeed.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_star_inside.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_star_inside.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_star_inside.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_star_inside.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_unclosed.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_unclosed.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_unclosed_trailing_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_trailing_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_unclosed_trailing_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_trailing_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_unclosed_with_new_lines.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_with_new_lines.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_unclosed_with_new_lines.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_with_new_lines.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_array_unclosed_with_object_inside.json b/formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_with_object_inside.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_array_unclosed_with_object_inside.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_with_object_inside.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_incomplete_false.json b/formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_false.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_incomplete_false.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_false.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_incomplete_null.json b/formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_incomplete_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_incomplete_true.json b/formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_true.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_incomplete_true.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_true.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_multidigit_number_then_00.json b/formats/json-tests/jvmTest/resources/spec_cases/n_multidigit_number_then_00.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_multidigit_number_then_00.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_multidigit_number_then_00.json
Binary files differ
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_bad_value.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_bad_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_bad_value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_bad_value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_bracket_key.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_bracket_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_bracket_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_bracket_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_comma_instead_of_colon.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_comma_instead_of_colon.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_comma_instead_of_colon.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_comma_instead_of_colon.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_double_colon.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_double_colon.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_double_colon.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_double_colon.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_emoji.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_emoji.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_emoji.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_emoji.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_garbage_at_end.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_garbage_at_end.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_garbage_at_end.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_garbage_at_end.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_key_with_single_quotes.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_key_with_single_quotes.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_key_with_single_quotes.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_key_with_single_quotes.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_lone_continuation_byte_in_key_and_trailing_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_lone_continuation_byte_in_key_and_trailing_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_lone_continuation_byte_in_key_and_trailing_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_missing_colon.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_colon.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_missing_colon.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_colon.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_missing_key.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_missing_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_missing_semicolon.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_semicolon.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_missing_semicolon.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_semicolon.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_missing_value.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_missing_value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_no-colon.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_no-colon.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_no-colon.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_no-colon.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_non_string_key.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_non_string_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_non_string_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_non_string_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_non_string_key_but_huge_number_instead.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_non_string_key_but_huge_number_instead.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_non_string_key_but_huge_number_instead.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_non_string_key_but_huge_number_instead.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_repeated_null_null.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_repeated_null_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_repeated_null_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_repeated_null_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_several_trailing_commas.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_several_trailing_commas.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_several_trailing_commas.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_several_trailing_commas.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_single_quote.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_single_quote.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_single_quote.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_single_quote.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_trailing_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_open.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_open.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_open.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_open.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open_incomplete.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open_incomplete.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open_incomplete.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open_incomplete.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_two_commas_in_a_row.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_two_commas_in_a_row.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_two_commas_in_a_row.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_two_commas_in_a_row.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_unquoted_key.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_unquoted_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_unquoted_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_unquoted_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_unterminated-value.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_unterminated-value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_unterminated-value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_unterminated-value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_with_single_string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_with_single_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_with_single_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_with_single_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_object_with_trailing_garbage.json b/formats/json-tests/jvmTest/resources/spec_cases/n_object_with_trailing_garbage.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_object_with_trailing_garbage.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_object_with_trailing_garbage.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_single_space.json b/formats/json-tests/jvmTest/resources/spec_cases/n_single_space.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_single_space.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_single_space.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1x.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1x.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1x.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1x.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_accentuated_char_no_quotes.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_accentuated_char_no_quotes.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_accentuated_char_no_quotes.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_accentuated_char_no_quotes.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_backslash_00.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_backslash_00.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_backslash_00.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_backslash_00.json
Binary files differ
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_escape_x.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_escape_x.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_escape_x.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_escape_x.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_escaped_backslash_bad.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_backslash_bad.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_escaped_backslash_bad.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_backslash_bad.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_escaped_ctrl_char_tab.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_ctrl_char_tab.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_escaped_ctrl_char_tab.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_ctrl_char_tab.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_escaped_emoji.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_emoji.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_escaped_emoji.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_emoji.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_incomplete_escape.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_incomplete_escaped_character.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_escaped_character.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_escaped_character.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_escaped_character.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_incomplete_surrogate.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_surrogate.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_surrogate.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_surrogate.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_incomplete_surrogate_escape_invalid.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_surrogate_escape_invalid.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_surrogate_escape_invalid.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_surrogate_escape_invalid.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_invalid-utf-8-in-escape.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid-utf-8-in-escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_invalid-utf-8-in-escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid-utf-8-in-escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_invalid_backslash_esc.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_backslash_esc.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_invalid_backslash_esc.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_backslash_esc.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_invalid_unicode_escape.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_unicode_escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_invalid_unicode_escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_unicode_escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_invalid_utf8_after_escape.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_utf8_after_escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_invalid_utf8_after_escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_utf8_after_escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_leading_uescaped_thinspace.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_leading_uescaped_thinspace.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_leading_uescaped_thinspace.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_leading_uescaped_thinspace.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_no_quotes_with_bad_escape.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_no_quotes_with_bad_escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_no_quotes_with_bad_escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_no_quotes_with_bad_escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_single_doublequote.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_single_doublequote.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_single_doublequote.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_single_doublequote.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_single_quote.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_single_quote.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_single_quote.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_single_quote.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_single_string_no_double_quotes.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_single_string_no_double_quotes.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_single_string_no_double_quotes.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_single_string_no_double_quotes.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_start_escape_unclosed.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_start_escape_unclosed.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_start_escape_unclosed.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_start_escape_unclosed.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_unescaped_crtl_char.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_crtl_char.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_unescaped_crtl_char.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_crtl_char.json
Binary files differ
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_unescaped_newline.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_newline.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_unescaped_newline.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_newline.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_unescaped_tab.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_tab.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_unescaped_tab.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_tab.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_unicode_CapitalU.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_unicode_CapitalU.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_unicode_CapitalU.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_unicode_CapitalU.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_string_with_trailing_garbage.json b/formats/json-tests/jvmTest/resources/spec_cases/n_string_with_trailing_garbage.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_string_with_trailing_garbage.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_string_with_trailing_garbage.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_100000_opening_arrays.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_100000_opening_arrays.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_100000_opening_arrays.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_100000_opening_arrays.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_U+2060_word_joined.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_U+2060_word_joined.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_U+2060_word_joined.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_U+2060_word_joined.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_UTF8_BOM_no_data.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_UTF8_BOM_no_data.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_UTF8_BOM_no_data.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_UTF8_BOM_no_data.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_angle_bracket_..json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_angle_bracket_..json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_angle_bracket_..json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_angle_bracket_..json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_angle_bracket_null.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_angle_bracket_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_angle_bracket_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_angle_bracket_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_array_trailing_garbage.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_trailing_garbage.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_array_trailing_garbage.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_trailing_garbage.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_array_with_extra_array_close.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_with_extra_array_close.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_array_with_extra_array_close.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_with_extra_array_close.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_array_with_unclosed_string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_with_unclosed_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_array_with_unclosed_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_with_unclosed_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_ascii-unicode-identifier.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_ascii-unicode-identifier.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_ascii-unicode-identifier.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_ascii-unicode-identifier.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_capitalized_True.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_capitalized_True.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_capitalized_True.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_capitalized_True.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_close_unopened_array.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_close_unopened_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_close_unopened_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_close_unopened_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_comma_instead_of_closing_brace.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_comma_instead_of_closing_brace.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_comma_instead_of_closing_brace.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_comma_instead_of_closing_brace.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_double_array.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_double_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_double_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_double_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_end_array.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_end_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_end_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_end_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_incomplete_UTF8_BOM.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_incomplete_UTF8_BOM.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_incomplete_UTF8_BOM.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_incomplete_UTF8_BOM.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_lone-invalid-utf-8.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_lone-invalid-utf-8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_lone-invalid-utf-8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_lone-invalid-utf-8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_lone-open-bracket.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_lone-open-bracket.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_lone-open-bracket.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_lone-open-bracket.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_no_data.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_no_data.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_no_data.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_no_data.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_null-byte-outside-string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_null-byte-outside-string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_null-byte-outside-string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_null-byte-outside-string.json
Binary files differ
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_number_with_trailing_garbage.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_number_with_trailing_garbage.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_number_with_trailing_garbage.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_number_with_trailing_garbage.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_object_followed_by_closing_object.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_followed_by_closing_object.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_object_followed_by_closing_object.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_followed_by_closing_object.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_object_unclosed_no_value.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_unclosed_no_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_object_unclosed_no_value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_unclosed_no_value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_object_with_comment.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_with_comment.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_object_with_comment.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_with_comment.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_object_with_trailing_garbage.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_with_trailing_garbage.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_object_with_trailing_garbage.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_with_trailing_garbage.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_array_apostrophe.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_apostrophe.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_apostrophe.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_apostrophe.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_array_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_array_object.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_object.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_object.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_object.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_array_open_object.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_open_object.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_open_object.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_open_object.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_array_open_string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_open_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_open_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_open_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_array_string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_object.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_object.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_object_close_array.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_close_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_close_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_close_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_object_comma.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_comma.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_comma.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_comma.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_object_open_array.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_open_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_open_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_open_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_object_open_string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_open_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_open_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_open_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_object_string_with_apostrophes.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_string_with_apostrophes.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_string_with_apostrophes.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_string_with_apostrophes.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_open_open.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_open.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_open_open.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_open.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_single_star.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_single_star.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_single_star.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_single_star.json
diff --git "a/formats/json/jvmTest/resources/spec_cases/n_structure_trailing_\043.json" "b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_trailing_\043.json"
similarity index 100%
rename from "formats/json/jvmTest/resources/spec_cases/n_structure_trailing_\043.json"
rename to "formats/json-tests/jvmTest/resources/spec_cases/n_structure_trailing_\043.json"
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_uescaped_LF_before_string.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_uescaped_LF_before_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_uescaped_LF_before_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_uescaped_LF_before_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_partial_null.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_partial_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_partial_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_partial_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_false.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_false.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_false.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_false.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_true.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_true.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_true.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_true.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_object.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_object.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_object.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_object.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_unicode-identifier.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_unicode-identifier.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_unicode-identifier.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_unicode-identifier.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_whitespace_U+2060_word_joiner.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_whitespace_U+2060_word_joiner.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_whitespace_U+2060_word_joiner.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_whitespace_U+2060_word_joiner.json
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_whitespace_formfeed.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_whitespace_formfeed.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/n_structure_whitespace_formfeed.json
rename to formats/json-tests/jvmTest/resources/spec_cases/n_structure_whitespace_formfeed.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_empty-string.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_empty-string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_empty-string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_empty-string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_empty.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_empty.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_empty.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_empty.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_ending_with_newline.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_ending_with_newline.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_ending_with_newline.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_ending_with_newline.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_false.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_false.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_false.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_false.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_heterogeneous.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_heterogeneous.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_heterogeneous.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_heterogeneous.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_null.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_with_1_and_newline.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_with_1_and_newline.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_with_1_and_newline.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_with_1_and_newline.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_with_leading_space.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_with_leading_space.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_with_leading_space.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_with_leading_space.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_with_several_null.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_with_several_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_with_several_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_with_several_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_array_with_trailing_space.json b/formats/json-tests/jvmTest/resources/spec_cases/y_array_with_trailing_space.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_array_with_trailing_space.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_array_with_trailing_space.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_0e+1.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_0e+1.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_0e+1.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_0e+1.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_0e1.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_0e1.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_0e1.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_0e1.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_after_space.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_after_space.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_after_space.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_after_space.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_double_close_to_zero.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_double_close_to_zero.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_double_close_to_zero.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_double_close_to_zero.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_int_with_exp.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_int_with_exp.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_int_with_exp.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_int_with_exp.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_minus_zero.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_minus_zero.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_minus_zero.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_minus_zero.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_negative_int.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_int.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_negative_int.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_int.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_negative_one.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_one.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_negative_one.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_one.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_negative_zero.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_zero.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_negative_zero.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_zero.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e_neg_exp.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e_neg_exp.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e_neg_exp.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e_neg_exp.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e_pos_exp.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e_pos_exp.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e_pos_exp.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e_pos_exp.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_exponent.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_exponent.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_exponent.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_exponent.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_fraction_exponent.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_fraction_exponent.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_fraction_exponent.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_fraction_exponent.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_neg_exp.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_neg_exp.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_neg_exp.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_neg_exp.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_real_pos_exponent.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_real_pos_exponent.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_real_pos_exponent.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_real_pos_exponent.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_simple_int.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_simple_int.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_simple_int.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_simple_int.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number_simple_real.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number_simple_real.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_number_simple_real.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_number_simple_real.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_basic.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_basic.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_basic.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_basic.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_duplicated_key.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_duplicated_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_duplicated_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_duplicated_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_duplicated_key_and_value.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_duplicated_key_and_value.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_duplicated_key_and_value.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_duplicated_key_and_value.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_empty.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_empty.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_empty.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_empty.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_empty_key.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_empty_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_empty_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_empty_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_escaped_null_in_key.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_escaped_null_in_key.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_escaped_null_in_key.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_escaped_null_in_key.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_extreme_numbers.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_extreme_numbers.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_extreme_numbers.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_extreme_numbers.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_long_strings.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_long_strings.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_long_strings.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_long_strings.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_simple.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_simple.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_simple.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_simple.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_string_unicode.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_string_unicode.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_string_unicode.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_string_unicode.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object_with_newlines.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object_with_newlines.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_object_with_newlines.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_object_with_newlines.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_1_2_3_bytes_UTF-8_sequences.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_1_2_3_bytes_UTF-8_sequences.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_1_2_3_bytes_UTF-8_sequences.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_1_2_3_bytes_UTF-8_sequences.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pair.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pair.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pair.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pair.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pairs.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pairs.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pairs.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pairs.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_allowed_escapes.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_allowed_escapes.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_allowed_escapes.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_allowed_escapes.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_backslash_and_u_escaped_zero.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_backslash_and_u_escaped_zero.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_backslash_and_u_escaped_zero.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_backslash_and_u_escaped_zero.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_backslash_doublequotes.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_backslash_doublequotes.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_backslash_doublequotes.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_backslash_doublequotes.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_comments.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_comments.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_comments.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_comments.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_double_escape_a.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_double_escape_a.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_double_escape_a.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_double_escape_a.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_double_escape_n.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_double_escape_n.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_double_escape_n.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_double_escape_n.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_escaped_control_character.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_escaped_control_character.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_escaped_control_character.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_escaped_control_character.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_escaped_noncharacter.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_escaped_noncharacter.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_escaped_noncharacter.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_escaped_noncharacter.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_in_array.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_in_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_in_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_in_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_in_array_with_leading_space.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_in_array_with_leading_space.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_in_array_with_leading_space.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_in_array_with_leading_space.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_last_surrogates_1_and_2.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_last_surrogates_1_and_2.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_last_surrogates_1_and_2.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_last_surrogates_1_and_2.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_nbsp_uescaped.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_nbsp_uescaped.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_nbsp_uescaped.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_nbsp_uescaped.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+10FFFF.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+10FFFF.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+10FFFF.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+10FFFF.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+FFFF.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+FFFF.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+FFFF.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+FFFF.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_null_escape.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_null_escape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_null_escape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_null_escape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_one-byte-utf-8.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_one-byte-utf-8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_one-byte-utf-8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_one-byte-utf-8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_pi.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_pi.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_pi.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_pi.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_reservedCharacterInUTF-8_U+1BFFF.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_reservedCharacterInUTF-8_U+1BFFF.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_reservedCharacterInUTF-8_U+1BFFF.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_simple_ascii.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_simple_ascii.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_simple_ascii.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_simple_ascii.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_space.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_space.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_space.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_space.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_three-byte-utf-8.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_three-byte-utf-8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_three-byte-utf-8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_three-byte-utf-8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_two-byte-utf-8.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_two-byte-utf-8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_two-byte-utf-8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_two-byte-utf-8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_u+2028_line_sep.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_u+2028_line_sep.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_u+2028_line_sep.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_u+2028_line_sep.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_u+2029_par_sep.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_u+2029_par_sep.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_u+2029_par_sep.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_u+2029_par_sep.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_uEscape.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_uEscape.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_uEscape.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_uEscape.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_uescaped_newline.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_uescaped_newline.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_uescaped_newline.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_uescaped_newline.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unescaped_char_delete.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unescaped_char_delete.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unescaped_char_delete.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unescaped_char_delete.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_2.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_2.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_2.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_2.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+10FFFE_nonchar.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+10FFFE_nonchar.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+10FFFE_nonchar.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+10FFFE_nonchar.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+1FFFE_nonchar.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+1FFFE_nonchar.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+1FFFE_nonchar.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+1FFFE_nonchar.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+2064_invisible_plus.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+2064_invisible_plus.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+2064_invisible_plus.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+2064_invisible_plus.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+FDD0_nonchar.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+FDD0_nonchar.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+FDD0_nonchar.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+FDD0_nonchar.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+FFFE_nonchar.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+FFFE_nonchar.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+FFFE_nonchar.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+FFFE_nonchar.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_unicode_escaped_double_quote.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_escaped_double_quote.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_unicode_escaped_double_quote.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_escaped_double_quote.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_utf8.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_utf8.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_utf8.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_utf8.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_string_with_del_character.json b/formats/json-tests/jvmTest/resources/spec_cases/y_string_with_del_character.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_string_with_del_character.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_string_with_del_character.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_lonely_false.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_false.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_false.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_false.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_lonely_int.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_int.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_int.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_int.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_lonely_negative_real.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_negative_real.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_negative_real.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_negative_real.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_lonely_null.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_null.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_null.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_null.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_lonely_string.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_string.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_string.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_string.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_lonely_true.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_true.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_true.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_true.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_string_empty.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_string_empty.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_string_empty.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_string_empty.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_trailing_newline.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_trailing_newline.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_trailing_newline.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_trailing_newline.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_true_in_array.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_true_in_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_true_in_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_true_in_array.json
diff --git a/formats/json/jvmTest/resources/spec_cases/y_structure_whitespace_array.json b/formats/json-tests/jvmTest/resources/spec_cases/y_structure_whitespace_array.json
similarity index 100%
rename from formats/json/jvmTest/resources/spec_cases/y_structure_whitespace_array.json
rename to formats/json-tests/jvmTest/resources/spec_cases/y_structure_whitespace_array.json
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt
new file mode 100644
index 0000000..a0c4c73
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt
@@ -0,0 +1,193 @@
+package kotlinx.serialization
+
+import kotlinx.serialization.builtins.ListSerializer
+import kotlinx.serialization.builtins.MapSerializer
+import kotlinx.serialization.descriptors.PrimitiveKind
+import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
+import kotlinx.serialization.encoding.Decoder
+import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.*
+import org.junit.Test
+import java.math.BigDecimal
+
+private typealias BigDecimalKxs = @Serializable(with = BigDecimalNumericSerializer::class) BigDecimal
+
+class BigDecimalTest : JsonTestBase() {
+
+ private val json = Json {
+ prettyPrint = true
+ }
+
+ private inline fun <reified T> assertBigDecimalJsonFormAndRestored(
+ expected: String,
+ actual: T,
+ serializer: KSerializer<T> = serializer(),
+ ) = assertJsonFormAndRestored(
+ serializer,
+ actual,
+ expected,
+ json
+ )
+
+ @Test
+ fun bigDecimal() {
+ fun test(expected: String, actual: BigDecimal) =
+ assertBigDecimalJsonFormAndRestored(expected, actual, BigDecimalNumericSerializer)
+
+ test("0", BigDecimal.ZERO)
+ test("1", BigDecimal.ONE)
+ test("-1", BigDecimal("-1"))
+ test("10", BigDecimal.TEN)
+ test(bdExpected1, bdActual1)
+ test(bdExpected2, bdActual2)
+ test(bdExpected3, bdActual3)
+ test(bdExpected4, bdActual4)
+ test(bdExpected5, bdActual5)
+ test(bdExpected6, bdActual6)
+ }
+
+ @Test
+ fun bigDecimalList() {
+
+ val bdList: List<BigDecimal> = listOf(
+ bdActual1,
+ bdActual2,
+ bdActual3,
+ bdActual4,
+ bdActual5,
+ bdActual6,
+ )
+
+ val expected =
+ """
+ [
+ $bdExpected1,
+ $bdExpected2,
+ $bdExpected3,
+ $bdExpected4,
+ $bdExpected5,
+ $bdExpected6
+ ]
+ """.trimIndent()
+
+ assertJsonFormAndRestored(
+ ListSerializer(BigDecimalNumericSerializer),
+ bdList,
+ expected,
+ json,
+ )
+ }
+
+ @Test
+ fun bigDecimalMap() {
+ val bdMap: Map<BigDecimal, BigDecimal> = mapOf(
+ bdActual1 to bdActual2,
+ bdActual3 to bdActual4,
+ bdActual5 to bdActual6,
+ )
+
+ val expected =
+ """
+ {
+ "$bdExpected1": $bdExpected2,
+ "$bdExpected3": $bdExpected4,
+ "$bdExpected5": $bdExpected6
+ }
+ """.trimIndent()
+
+ assertJsonFormAndRestored(
+ MapSerializer(BigDecimalNumericSerializer, BigDecimalNumericSerializer),
+ bdMap,
+ expected,
+ json,
+ )
+ }
+
+ @Test
+ fun bigDecimalHolder() {
+ val bdHolder = BigDecimalHolder(
+ bd = bdActual1,
+ bdList = listOf(
+ bdActual1,
+ bdActual2,
+ bdActual3,
+ ),
+ bdMap = mapOf(
+ bdActual1 to bdActual2,
+ bdActual3 to bdActual4,
+ bdActual5 to bdActual6,
+ ),
+ )
+
+ val expected =
+ """
+ {
+ "bd": $bdExpected1,
+ "bdList": [
+ $bdExpected1,
+ $bdExpected2,
+ $bdExpected3
+ ],
+ "bdMap": {
+ "$bdExpected1": $bdExpected2,
+ "$bdExpected3": $bdExpected4,
+ "$bdExpected5": $bdExpected6
+ }
+ }
+ """.trimIndent()
+
+ assertBigDecimalJsonFormAndRestored(
+ expected,
+ bdHolder,
+ )
+ }
+
+ companion object {
+
+ // test data
+ private val bdActual1 = BigDecimal("725345854747326287606413621318.311864440287151714280387858224")
+ private val bdActual2 = BigDecimal("336052472523017262165484244513.836582112201211216526831524328")
+ private val bdActual3 = BigDecimal("211054843014778386028147282517.011200287614476453868782405400")
+ private val bdActual4 = BigDecimal("364751025728628060231208776573.207325218263752602211531367642")
+ private val bdActual5 = BigDecimal("508257556021513833656664177125.824502734715222686411316853148")
+ private val bdActual6 = BigDecimal("127134584027580606401102614002.366672301517071543257300444000")
+
+ private const val bdExpected1 = "725345854747326287606413621318.311864440287151714280387858224"
+ private const val bdExpected2 = "336052472523017262165484244513.836582112201211216526831524328"
+ private const val bdExpected3 = "211054843014778386028147282517.011200287614476453868782405400"
+ private const val bdExpected4 = "364751025728628060231208776573.207325218263752602211531367642"
+ private const val bdExpected5 = "508257556021513833656664177125.824502734715222686411316853148"
+ private const val bdExpected6 = "127134584027580606401102614002.366672301517071543257300444000"
+ }
+
+}
+
+@Serializable
+private data class BigDecimalHolder(
+ val bd: BigDecimalKxs,
+ val bdList: List<BigDecimalKxs>,
+ val bdMap: Map<BigDecimalKxs, BigDecimalKxs>,
+)
+
+private object BigDecimalNumericSerializer : KSerializer<BigDecimal> {
+
+ override val descriptor = PrimitiveSerialDescriptor("java.math.BigDecimal", PrimitiveKind.DOUBLE)
+
+ override fun deserialize(decoder: Decoder): BigDecimal {
+ return if (decoder is JsonDecoder) {
+ BigDecimal(decoder.decodeJsonElement().jsonPrimitive.content)
+ } else {
+ BigDecimal(decoder.decodeString())
+ }
+ }
+
+ override fun serialize(encoder: Encoder, value: BigDecimal) {
+ val bdString = value.toPlainString()
+
+ if (encoder is JsonEncoder) {
+ encoder.encodeJsonElement(JsonUnquotedLiteral(bdString))
+ } else {
+ encoder.encodeString(bdString)
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
new file mode 100644
index 0000000..1f4958a
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2019 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.test.typeTokenOf
+import org.junit.Test
+import java.util.HashMap
+import java.util.HashSet
+import kotlin.collections.LinkedHashMap
+import kotlin.collections.Map
+import kotlin.collections.hashMapOf
+import kotlin.collections.hashSetOf
+import kotlin.reflect.*
+import kotlin.test.*
+
+
+class JavaCollectionsTest {
+ @Serializable
+ data class HasHashMap(
+ val s: String,
+ val hashMap: HashMap<Int, String>,
+ val hashSet: HashSet<Int>,
+ val linkedHashMap: LinkedHashMap<Int, String>,
+ val kEntry: Map.Entry<Int, String>?
+ )
+
+ @Test
+ fun testJavaCollectionsInsideClass() {
+ val original = HasHashMap("42", hashMapOf(1 to "1", 2 to "2"), hashSetOf(11), LinkedHashMap(), null)
+ val serializer = HasHashMap.serializer()
+ val string = Json.encodeToString(serializer = serializer, value = original)
+ assertEquals(
+ expected = """{"s":"42","hashMap":{"1":"1","2":"2"},"hashSet":[11],"linkedHashMap":{},"kEntry":null}""",
+ actual = string
+ )
+ val restored = Json.decodeFromString(deserializer = serializer, string = string)
+ assertEquals(expected = original, actual = restored)
+ }
+
+ @Test
+ fun testTopLevelMaps() {
+ // Returning null here is a deliberate choice: map constructor functions may return different specialized
+ // implementations (e.g., kotlin.collections.EmptyMap or java.util.Collections.SingletonMap)
+ // that may or may not be generic. Since we generally cannot return a generic serializer using Java class only,
+ // all attempts to get map serializer using only .javaClass should return null.
+ assertNull(serializerOrNull(emptyMap<String, String>().javaClass))
+ assertNull(serializerOrNull(mapOf<String, String>("a" to "b").javaClass))
+ assertNull(serializerOrNull(mapOf<String, String>("a" to "b", "b" to "c").javaClass))
+ // Correct ways of retrieving map serializer:
+ assertContains(
+ serializer(typeTokenOf<Map<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.LinkedHashMap<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ assertContains(
+ serializer(typeOf<LinkedHashMap<String, String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashMap"
+ )
+ }
+
+ @Test
+ fun testTopLevelSetsAndLists() {
+ // Same reasoning as for maps
+ assertNull(serializerOrNull(emptyList<String>().javaClass))
+ assertNull(serializerOrNull(listOf<String>("a").javaClass))
+ assertNull(serializerOrNull(listOf<String>("a", "b").javaClass))
+ assertNull(serializerOrNull(emptySet<String>().javaClass))
+ assertNull(serializerOrNull(setOf<String>("a").javaClass))
+ assertNull(serializerOrNull(setOf<String>("a", "b").javaClass))
+ assertContains(
+ serializer(typeTokenOf<Set<String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashSet"
+ )
+ assertContains(
+ serializer(typeTokenOf<List<String>>()).descriptor.serialName,
+ "kotlin.collections.ArrayList"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.LinkedHashSet<String>>()).descriptor.serialName,
+ "kotlin.collections.LinkedHashSet"
+ )
+ assertContains(
+ serializer(typeTokenOf<java.util.ArrayList<String>>()).descriptor.serialName,
+ "kotlin.collections.ArrayList"
+ )
+ }
+
+ @Test
+ fun testAnonymousObject() {
+ val obj: Any = object {}
+ assertNull(serializerOrNull(obj.javaClass))
+ }
+}
+
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
similarity index 81%
rename from formats/json/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
index b932b5a..a423bc8 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt
@@ -5,7 +5,6 @@
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
-import org.junit.Ignore
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
@@ -82,7 +81,7 @@
@Test
fun testBigPlaneClass() {
- val missedFields = MutableList(35) { "f$it" }
+ val missedFields = MutableList(36) { "f$it" }
val definedInJsonFields = arrayOf("f1", "f15", "f34")
val optionalFields = arrayOf("f3", "f5", "f7")
missedFields.removeAll(definedInJsonFields)
@@ -103,7 +102,6 @@
assertFailsWithMessages(listOf("p2", "c3")) {
Json {
serializersModule = module
- useArrayPolymorphism = false
}.decodeFromString<PolymorphicWrapper>("""{"nested": {"type": "a", "p1": 1, "c1": 11}}""")
}
}
@@ -112,16 +110,14 @@
@Test
fun testSealed() {
assertFailsWithMessages(listOf("p3", "c2")) {
- Json { useArrayPolymorphism = false }
- .decodeFromString<Parent>("""{"type": "child", "p1":1, "c1": 11}""")
+ Json.decodeFromString<Parent>("""{"type": "child", "p1":1, "c1": 11}""")
}
}
@Test
fun testTransient() {
assertFailsWithMessages(listOf("f3", "f4")) {
- Json { useArrayPolymorphism = false }
- .decodeFromString<WithTransient>("""{"f1":1}""")
+ Json.decodeFromString<WithTransient>("""{"f1":1}""")
}
}
@@ -133,10 +129,10 @@
}
- private inline fun assertFailsWithMessages(messages: List<String>, block: () -> Unit) {
- val exception = assertFailsWith(SerializationException::class, null, block)
- assertEquals("kotlinx.serialization.MissingFieldException", exception::class.qualifiedName)
- val missedMessages = messages.filter { !exception.message!!.contains(it) }
- assertTrue(missedMessages.isEmpty(), "Expected message '${exception.message}' to contain substrings $missedMessages")
+ private inline fun assertFailsWithMessages(fields: List<String>, block: () -> Unit) {
+ val exception = assertFailsWith(MissingFieldException::class, null, block)
+ val missedMessages = fields.filter { !exception.message!!.contains(it) }
+ assertEquals(exception.missingFields.sorted(), fields.sorted())
+ assertTrue(missedMessages.isEmpty(), "Expected message '${exception.message}' to contain substrings $fields")
}
}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
similarity index 95%
rename from formats/json/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
index db79b47..a2f900d 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt
@@ -4,7 +4,6 @@
package kotlinx.serialization
-import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.Json
@@ -14,7 +13,6 @@
import java.util.*
import kotlin.test.assertEquals
-@Serializer(forClass = Date::class)
object DateSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.Date", PrimitiveKind.STRING)
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt
new file mode 100644
index 0000000..b8dd35d
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization
+
+import kotlinx.serialization.json.Json
+import java.net.URLClassLoader
+import kotlin.reflect.*
+import kotlin.test.*
+
+class SerializerByTypeCacheTest {
+
+ @Serializable
+ class Holder(val i: Int)
+
+ @Suppress("UNCHECKED_CAST")
+ @Test
+ fun testCaching() {
+ val typeOfKType = typeOf<Holder>()
+ val parameterKType = typeOf<List<Holder>>().arguments[0].type!!
+ assertSame(serializer(), serializer<Holder>())
+ assertSame(serializer(typeOfKType), serializer(typeOfKType))
+ assertSame(serializer(parameterKType), serializer(parameterKType))
+ assertSame(serializer(), serializer(typeOfKType) as KSerializer<Holder>)
+ assertSame(serializer(parameterKType) as KSerializer<Holder>, serializer(typeOfKType) as KSerializer<Holder>)
+ }
+
+ /**
+ * Checking the case when a parameterized type is loaded in different parallel [ClassLoader]s.
+ *
+ * If the main type is loaded by a common parent [ClassLoader] (for example, a bootstrap for [List]),
+ * and the element class is loaded by different loaders, then some implementations of the [KType] (e.g. `KTypeImpl` from reflection) may not see the difference between them.
+ *
+ * As a result, a serializer for another loader will be returned from the cache, and it will generate instances, when working with which we will get an [ClassCastException].
+ *
+ * The test checks the correctness of the cache for such cases - that different serializers for different loaders will be returned.
+ *
+ * [see](https://youtrack.jetbrains.com/issue/KT-54523).
+ */
+ @Test
+ fun testDifferentClassLoaders() {
+ val elementKType1 = SimpleKType(loadClass().kotlin)
+ val elementKType2 = SimpleKType(loadClass().kotlin)
+
+ // Java class must be same (same name)
+ assertEquals(elementKType1.classifier.java.canonicalName, elementKType2.classifier.java.canonicalName)
+ // class loaders must be different
+ assertNotSame(elementKType1.classifier.java.classLoader, elementKType2.classifier.java.classLoader)
+ // due to the incorrect definition of the `equals`, KType-s are equal
+ assertEquals(elementKType1, elementKType2)
+
+ // create parametrized type `List<Foo>`
+ val kType1 = SingleParametrizedKType(List::class, elementKType1)
+ val kType2 = SingleParametrizedKType(List::class, elementKType2)
+
+ val serializer1 = serializer(kType1)
+ val serializer2 = serializer(kType2)
+
+ // when taking a serializers from cache, we must distinguish between KType-s, despite the fact that they are equivalent
+ assertNotSame(serializer1, serializer2)
+
+ // serializers must work correctly
+ Json.decodeFromString(serializer1, "[{\"i\":1}]")
+ Json.decodeFromString(serializer2, "[{\"i\":1}]")
+ }
+
+ /**
+ * Load class `example.Foo` via new class loader. Compiled class-file located in the resources.
+ */
+ private fun loadClass(): Class<*> {
+ val classesUrl = this::class.java.classLoader.getResource("class_loaders/classes/")
+ val loader1 = URLClassLoader(arrayOf(classesUrl), this::class.java.classLoader)
+ return loader1.loadClass("example.Foo")
+ }
+
+ private class SimpleKType(override val classifier: KClass<*>): KType {
+ override val annotations: List<Annotation> = emptyList()
+ override val arguments: List<KTypeProjection> = emptyList()
+
+ override val isMarkedNullable: Boolean = false
+
+ override fun equals(other: Any?): Boolean {
+ if (other !is SimpleKType) return false
+ return classifier.java.canonicalName == other.classifier.java.canonicalName
+ }
+
+ override fun hashCode(): Int {
+ return classifier.java.canonicalName.hashCode()
+ }
+ }
+
+
+ private class SingleParametrizedKType(override val classifier: KClass<*>, val parameterType: KType): KType {
+ override val annotations: List<Annotation> = emptyList()
+
+ override val arguments: List<KTypeProjection> = listOf(KTypeProjection(KVariance.INVARIANT, parameterType))
+
+ override val isMarkedNullable: Boolean = false
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as SingleParametrizedKType
+
+ if (classifier != other.classifier) return false
+ if (parameterType != other.parameterType) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = classifier.hashCode()
+ result = 31 * result + parameterType.hashCode()
+ return result
+ }
+ }
+}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
similarity index 69%
rename from formats/json/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
index e6fbecb..dcf3e95 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
@@ -17,17 +17,17 @@
private class Data(val s: String)
private class BadDecoder : AbstractDecoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = 42
}
@Test
- fun testJsonDecodingException() = checkRecovered<JsonDecodingException> {
+ fun testJsonDecodingException() = checkRecovered("JsonDecodingException") {
Json.decodeFromString<String>("42")
}
@Test
- fun testJsonEncodingException() = checkRecovered<JsonEncodingException> {
+ fun testJsonEncodingException() = checkRecovered("JsonEncodingException") {
Json.encodeToString(Double.NaN)
}
@@ -38,12 +38,6 @@
serializer.deserialize(BadDecoder())
}
- @Test
- // checks simple name because MFE is internal class
- fun testMissingFieldException() = checkRecovered("MissingFieldException") {
- Json.decodeFromString<Data>("{}")
- }
-
private fun checkRecovered(exceptionClassSimpleName: String, block: () -> Unit) = runBlocking {
val result = runCatching {
callBlockWithRecovery(block)
@@ -57,19 +51,6 @@
assertEquals(exceptionClassSimpleName, e::class.simpleName!!)
}
- private inline fun <reified E : Exception> checkRecovered(noinline block: () -> Unit) = runBlocking {
- val result = runCatching {
- callBlockWithRecovery(block)
- }
- assertTrue(result.isFailure, "Block should have failed")
- val e = result.exceptionOrNull()!!
- assertEquals(E::class, e::class)
- val cause = e.cause
- assertNotNull(cause, "Exception should have cause: $e")
- assertEquals(e.message, cause.message)
- assertEquals(E::class, cause::class)
- }
-
// KLUDGE: A separate function with state-machine to ensure coroutine DebugMetadata is generated. See KT-41789
private suspend fun callBlockWithRecovery(block: () -> Unit) {
yield()
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
similarity index 67%
rename from formats/json/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
index b576a2c..7019eda 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt
@@ -1,14 +1,16 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
+
package kotlinx.serialization.features
-import kotlinx.serialization.SerializationException
-import kotlinx.serialization.StringData
+import kotlinx.serialization.*
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.*
import kotlinx.serialization.json.internal.BATCH_SIZE
+import kotlinx.serialization.modules.*
import kotlinx.serialization.test.*
import org.junit.Test
import java.io.ByteArrayInputStream
@@ -85,4 +87,45 @@
}
}
+ interface Poly
+
+ @Serializable
+ @SerialName("Impl")
+ data class Impl(val str: String) : Poly
+
+ @Test
+ fun testPolymorphismWhenCrossingBatchSizeNonLeadingKey() {
+ val json = Json {
+ serializersModule = SerializersModule {
+ polymorphic(Poly::class) {
+ subclass(Impl::class, Impl.serializer())
+ }
+ }
+ }
+
+ val longString = "a".repeat(BATCH_SIZE - 5)
+ val string = """{"str":"$longString", "type":"Impl"}"""
+ val golden = Impl(longString)
+
+ val deserialized = json.decodeViaStream(serializer<Poly>(), string)
+ assertEquals(golden, deserialized as Impl)
+ }
+
+ @Test
+ fun testPolymorphismWhenCrossingBatchSize() {
+ val json = Json {
+ serializersModule = SerializersModule {
+ polymorphic(Poly::class) {
+ subclass(Impl::class, Impl.serializer())
+ }
+ }
+ }
+
+ val aLotOfWhiteSpaces = " ".repeat(BATCH_SIZE - 5)
+ val string = """{$aLotOfWhiteSpaces"type":"Impl", "str":"value"}"""
+ val golden = Impl("value")
+
+ val deserialized = json.decodeViaStream(serializer<Poly>(), string)
+ assertEquals(golden, deserialized as Impl)
+ }
}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
similarity index 89%
rename from formats/json/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
index aad9a0f..2388766 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt
@@ -12,8 +12,8 @@
import kotlinx.serialization.features.sealed.SealedChild
import kotlinx.serialization.features.sealed.SealedParent
import kotlinx.serialization.json.*
-import kotlinx.serialization.json.internal.JsonDecodingException
import kotlinx.serialization.test.assertFailsWithMessage
+import kotlinx.serialization.test.assertFailsWithSerial
import org.junit.Test
import java.io.*
import kotlin.test.*
@@ -83,6 +83,7 @@
iter.assertNext(StringData("b"))
iter.assertNext(StringData("c"))
assertFalse(iter.hasNext())
+ assertFalse(iter.hasNext()) // Subsequent calls to .hasNext() should not throw EOF or anything
assertFailsWithMessage<SerializationException>("EOF") {
iter.next()
}
@@ -142,7 +143,7 @@
val input2 = """[1, 2, 3]qwert"""
val input3 = """[1,2 3]"""
withInputs(input1, input2, input3) {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
json.decodeToSequence(it, Int.serializer()).toList()
}
}
@@ -151,13 +152,13 @@
@Test
fun testMultilineArrays() {
val input = "[1,2,3]\n[4,5,6]\n[7,8,9]"
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
json.decodeToSequence<List<Int>>(input.asInputStream(), DecodeSequenceMode.AUTO_DETECT).toList()
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
json.decodeToSequence<Int>(input.asInputStream(), DecodeSequenceMode.AUTO_DETECT).toList()
}
- assertFailsWith<JsonDecodingException> { // we do not merge lists
+ assertFailsWithSerial("JsonDecodingException") { // we do not merge lists
json.decodeToSequence<Int>(input.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED).toList()
}
val parsed = json.decodeToSequence<List<Int>>(input.asInputStream(), DecodeSequenceMode.WHITESPACE_SEPARATED).toList()
@@ -167,7 +168,7 @@
@Test
fun testStrictArrayCheck() {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
json.decodeToSequence<StringData>(inputStringWsSeparated.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED)
}
}
@@ -186,4 +187,11 @@
assertEquals(inputList, json.decodeToSequence(paddedWs.asInputStream(), StringData.serializer(), DecodeSequenceMode.ARRAY_WRAPPED).toList())
}
+ @Test
+ fun testToIteratorAndBack() = withInputs { ins ->
+ val iterator = Json.decodeToSequence(ins, StringData.serializer()).iterator()
+ val values = iterator.asSequence().take(3).toList()
+ assertEquals(inputList, values)
+ assertFalse(iterator.hasNext())
+ }
}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
similarity index 68%
rename from formats/json/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
index 287e443..554deab 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt
@@ -4,20 +4,12 @@
package kotlinx.serialization.features
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.*
-import kotlinx.coroutines.runBlocking
import kotlinx.serialization.*
import kotlinx.serialization.Serializable
-import kotlinx.serialization.builtins.serializer
-import kotlinx.serialization.features.sealed.SealedChild
-import kotlinx.serialization.features.sealed.SealedParent
import kotlinx.serialization.json.*
-import kotlinx.serialization.json.internal.JsonDecodingException
import kotlinx.serialization.test.assertFailsWithMessage
import org.junit.Test
import java.io.*
-import kotlin.test.*
class JsonSequencePathTest {
@@ -33,7 +25,7 @@
val iterator = Json.decodeToSequence<Data>(source).iterator()
iterator.next() // Ignore
assertFailsWithMessage<SerializationException>(
- "Expected quotation mark '\"', but had '2' instead at path: \$.data.s"
+ "Expected quotation mark '\"', but had '4' instead at path: \$.data.s"
) { iterator.next() }
}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
similarity index 92%
rename from formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
index 5227ca4..a600b9d 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt
@@ -15,6 +15,7 @@
import java.lang.reflect.*
import kotlin.reflect.*
import kotlin.test.*
+import kotlin.time.*
class SerializerByTypeTest {
@@ -26,10 +27,10 @@
@Serializable
data class Data(val l: List<String>, val b: Box<Int>)
- @Serializable
+ @Serializable(WithCustomDefault.Companion::class)
data class WithCustomDefault(val n: Int) {
- @Serializer(forClass = WithCustomDefault::class)
- companion object {
+
+ companion object: KSerializer<WithCustomDefault> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("WithCustomDefault", PrimitiveKind.INT)
override fun serialize(encoder: Encoder, value: WithCustomDefault) = encoder.encodeInt(value.n)
override fun deserialize(decoder: Decoder) = WithCustomDefault(decoder.decodeInt())
@@ -208,15 +209,6 @@
assertEquals(expected, json.encodeToString(serial2 as KSerializer<T>, value))
}
- @PublishedApi
- internal open class TypeBase<T>
-
- public inline fun <reified T> typeTokenOf(): Type {
- val base = object : TypeBase<T>() {}
- val superType = base::class.java.genericSuperclass!!
- return (superType as ParameterizedType).actualTypeArguments.first()!!
- }
-
class IntBox(val i: Int)
object CustomIntSerializer : KSerializer<IntBox> {
@@ -283,4 +275,18 @@
serializer(typeTokenOf<Array<NonSerializable>>())
}
}
+
+ @OptIn(ExperimentalTime::class)
+ @Test
+ fun testSerializersAreIntrinsified() {
+ val direct = measureTime {
+ Json.encodeToString(IntData.serializer(), IntData(10))
+ }
+ val directMs = direct.inWholeMicroseconds
+ val indirect = measureTime {
+ Json.encodeToString(IntData(10))
+ }
+ val indirectMs = indirect.inWholeMicroseconds
+ if (indirectMs > directMs + (directMs / 4)) error("Direct ($directMs) and indirect ($indirectMs) times are too far apart")
+ }
}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt
new file mode 100644
index 0000000..35dc16f
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt
@@ -0,0 +1,85 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.test.assertFailsWithMessage
+import org.junit.Test
+import java.io.*
+import java.util.*
+import kotlin.random.Random
+import kotlin.test.*
+
+
+@Serializable(with = LargeBase64StringSerializer::class)
+data class LargeBinaryData(val binaryData: ByteArray) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as LargeBinaryData
+
+ if (!binaryData.contentEquals(other.binaryData)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return binaryData.contentHashCode()
+ }
+}
+
+@Serializable
+data class ClassWithBinaryDataField(val binaryField: LargeBinaryData)
+
+object LargeBase64StringSerializer : KSerializer<LargeBinaryData> {
+ private val b64Decoder: Base64.Decoder = Base64.getDecoder()
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LargeStringContent", PrimitiveKind.STRING)
+
+ override fun deserialize(decoder: Decoder): LargeBinaryData {
+ require(decoder is ChunkedDecoder) { "Only chunked decoder supported" }
+
+ var reminder = ""
+ val decodedBytes = ByteArrayOutputStream().use { bos ->
+ decoder.decodeStringChunked {
+ val actualChunk = reminder + it
+ val reminderLength = actualChunk.length % 4
+ val alignedLength = actualChunk.length - reminderLength
+ val alignedChunk = actualChunk.take(alignedLength)
+ reminder = actualChunk.takeLast(reminderLength)
+ bos.write(b64Decoder.decode(alignedChunk))
+ }
+ bos.toByteArray()
+ }
+
+ return LargeBinaryData(decodedBytes)
+ }
+
+ override fun serialize(encoder: Encoder, value: LargeBinaryData) {
+ encoder.encodeString(Base64.getEncoder().encodeToString(value.binaryData))
+ }
+}
+
+class JsonChunkedBase64DecoderTest : JsonTestBase() {
+
+ @Test
+ fun decodeBase64String() {
+ val sourceObject =
+ ClassWithBinaryDataField(LargeBinaryData(Random.nextBytes(16 * 1024))) // After encoding to Base64 will be larger than 16k (JsonLexer#BATCH_SIZE)
+ val serializedObject = Json.encodeToString(sourceObject)
+
+ JsonTestingMode.values().forEach { mode ->
+ if (mode == JsonTestingMode.TREE) {
+ assertFailsWithMessage<IllegalArgumentException>(
+ "Only chunked decoder supported", "Shouldn't decode JSON in TREE mode"
+ ) {
+ Json.decodeFromString<ClassWithBinaryDataField>(serializedObject, mode)
+ }
+ } else {
+ val deserializedObject = Json.decodeFromString<ClassWithBinaryDataField>(serializedObject, mode)
+ assertEquals(sourceObject.binaryField, deserializedObject.binaryField)
+ }
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt
new file mode 100644
index 0000000..cdcbab7
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt
@@ -0,0 +1,78 @@
+package kotlinx.serialization.json
+
+import kotlinx.coroutines.*
+import kotlinx.serialization.Serializable
+import kotlinx.serialization.builtins.*
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import kotlin.random.*
+import kotlin.test.*
+
+// Stresses out that JSON decoded in parallel does not interfere (mostly via caching of various buffers)
+class JsonConcurrentStressTest : JsonTestBase() {
+ private val charset = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz0123456789"
+
+ @Test
+ fun testDecodeInParallelSimpleList() = doTest(100) { mode ->
+ val value = (1..10000).map { Random.nextDouble() }
+ val string = Json.encodeToString(ListSerializer(Double.serializer()), value, mode)
+ assertEquals(value, Json.decodeFromString(ListSerializer(Double.serializer()), string, mode))
+ }
+
+ @Serializable
+ data class Foo(val s: String, val f: Foo?)
+
+ @Test
+ fun testDecodeInParallelListOfPojo() = doTest(1_000) { mode ->
+ val value = (1..100).map {
+ val randomString = getRandomString()
+ val nestedFoo = Foo("null抢\u000E鋽윝䑜厼\uF70A紲ᢨ䣠null⛾䉻嘖緝ᯧnull쎶\u0005null" + randomString, null)
+ Foo(getRandomString(), nestedFoo)
+ }
+ val string = Json.encodeToString(ListSerializer(Foo.serializer()), value, mode)
+ assertEquals(value, Json.decodeFromString(ListSerializer(Foo.serializer()), string, mode))
+ }
+
+ @Test
+ fun testDecodeInParallelPojo() = doTest(100_000) { mode ->
+ val randomString = getRandomString()
+ val nestedFoo = Foo("null抢\u000E鋽윝䑜厼\uF70A紲ᢨ䣠null⛾䉻嘖緝ᯧnull쎶\u0005null" + randomString, null)
+ val randomFoo = Foo(getRandomString(), nestedFoo)
+ val string = Json.encodeToString(Foo.serializer(), randomFoo, mode)
+ assertEquals(randomFoo, Json.decodeFromString(Foo.serializer(), string, mode))
+ }
+
+ @Test
+ fun testDecodeInParallelSequencePojo() = runBlocking<Unit> {
+ for (i in 1 until 1_000) {
+ launch(Dispatchers.Default) {
+ val values = (1..100).map {
+ val randomString = getRandomString()
+ val nestedFoo = Foo("null抢\u000E鋽윝䑜厼\uF70A紲ᢨ䣠null⛾䉻嘖緝ᯧnull쎶\u0005null" + randomString, null)
+ Foo(getRandomString(), nestedFoo)
+ }
+ val baos = ByteArrayOutputStream()
+ for (value in values) {
+ Json.encodeToStream(Foo.serializer(), value, baos)
+ }
+ val bais = ByteArrayInputStream(baos.toByteArray())
+ assertEquals(values, Json.decodeToSequence(bais, Foo.serializer()).toList())
+ }
+ }
+ }
+
+ private fun getRandomString() = (1..Random.nextInt(0, charset.length)).map { charset[it] }.joinToString(separator = "")
+
+ private fun doTest(iterations: Int, block: (JsonTestingMode) -> Unit) {
+ runBlocking<Unit> {
+ for (i in 1 until iterations) {
+ launch(Dispatchers.Default) {
+ parametrizedTest {
+ block(it)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt
new file mode 100644
index 0000000..e56f173
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt
@@ -0,0 +1,40 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.Json.Default.decodeFromString
+import org.junit.*
+import org.junit.Test
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlin.test.*
+
+class MissingFieldExceptionWithPathTest {
+
+ @Test // Repro for #2212
+ fun testMfeIsNotReappliedMultipleTimes() {
+ val inputMalformed = """{"title": "...","cast": [{}]"""
+ try {
+ Json.decodeFromString<Movie>(inputMalformed)
+ fail("Unreacheable state")
+ } catch (e: MissingFieldException) {
+ val fullStackTrace = e.stackTraceToString()
+ val i1 = fullStackTrace.toString().indexOf("at path")
+ val i2 = fullStackTrace.toString().lastIndexOf("at path")
+ assertEquals(i1, i2)
+ assertTrue(i1 != -1)
+ }
+ }
+
+ @Serializable
+ data class Movie(
+ val title: String,
+ val cast: List<Cast>,
+ )
+
+ @Serializable
+ data class Cast(
+ val name: String
+ )
+}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt
Binary files differ
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
similarity index 100%
rename from formats/json/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
similarity index 91%
rename from formats/json/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
rename to formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
index ebb49c3..9220bbd 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt
@@ -11,7 +11,7 @@
): String {
val output = ByteArrayOutputStream()
encodeToStream(serializer, value, output)
- return output.toString()
+ return output.toString(Charsets.UTF_8.name())
}
actual fun <T> Json.decodeViaStream(
diff --git a/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt
new file mode 100644
index 0000000..2b04274
--- /dev/null
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+import java.lang.reflect.*
+
+
+@PublishedApi
+internal open class TypeBase<T>
+
+public inline fun <reified T> typeTokenOf(): Type {
+ val base = object : TypeBase<T>() {}
+ val superType = base::class.java.genericSuperclass!!
+ return (superType as ParameterizedType).actualTypeArguments.first()!!
+}
diff --git a/formats/json/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt b/formats/json-tests/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt
similarity index 100%
rename from formats/json/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt
rename to formats/json-tests/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt
diff --git a/formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json-tests/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
similarity index 76%
rename from formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
rename to formats/json-tests/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index 32806c1..5824904 100644
--- a/formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/formats/json-tests/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -4,8 +4,5 @@
package kotlinx.serialization.test
-import kotlin.native.concurrent.SharedImmutable
-
-@SharedImmutable
public actual val currentPlatform: Platform = Platform.NATIVE
diff --git a/formats/json/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt
similarity index 100%
rename from formats/json/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt
rename to formats/json-tests/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt
diff --git a/formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..fd359b7
--- /dev/null
+++ b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file
diff --git a/formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt
new file mode 100644
index 0000000..6102e58
--- /dev/null
+++ b/formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt
@@ -0,0 +1,19 @@
+package kotlinx.serialization.test
+
+import kotlinx.serialization.DeserializationStrategy
+import kotlinx.serialization.SerializationStrategy
+import kotlinx.serialization.json.Json
+
+actual fun <T> Json.encodeViaStream(
+ serializer: SerializationStrategy<T>,
+ value: T
+): String {
+ TODO("supported on JVM only")
+}
+
+actual fun <T> Json.decodeViaStream(
+ serializer: DeserializationStrategy<T>,
+ input: String
+): T {
+ TODO("supported on JVM only")
+}
\ No newline at end of file
diff --git a/formats/json/api/kotlinx-serialization-json.api b/formats/json/api/kotlinx-serialization-json.api
index 1fe1440..6874d74 100644
--- a/formats/json/api/kotlinx-serialization-json.api
+++ b/formats/json/api/kotlinx-serialization-json.api
@@ -1,7 +1,17 @@
+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;
public static final field WHITESPACE_SEPARATED Lkotlinx/serialization/json/DecodeSequenceMode;
+ public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lkotlinx/serialization/json/DecodeSequenceMode;
public static fun values ()[Lkotlinx/serialization/json/DecodeSequenceMode;
}
@@ -70,6 +80,7 @@
public final class kotlinx/serialization/json/JsonArrayBuilder {
public fun <init> ()V
public final fun add (Lkotlinx/serialization/json/JsonElement;)Z
+ public final fun addAll (Ljava/util/Collection;)Z
public final fun build ()Lkotlinx/serialization/json/JsonArray;
}
@@ -85,11 +96,15 @@
public final class kotlinx/serialization/json/JsonBuilder {
public final fun getAllowSpecialFloatingPointValues ()Z
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
public final fun getExplicitNulls ()Z
public final fun getIgnoreUnknownKeys ()Z
+ public final fun getNamingStrategy ()Lkotlinx/serialization/json/JsonNamingStrategy;
public final fun getPrettyPrint ()Z
public final fun getPrettyPrintIndent ()Ljava/lang/String;
public final fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
@@ -98,12 +113,16 @@
public final fun isLenient ()Z
public final fun setAllowSpecialFloatingPointValues (Z)V
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
public final fun setExplicitNulls (Z)V
public final fun setIgnoreUnknownKeys (Z)V
public final fun setLenient (Z)V
+ public final fun setNamingStrategy (Lkotlinx/serialization/json/JsonNamingStrategy;)V
public final fun setPrettyPrint (Z)V
public final fun setPrettyPrintIndent (Ljava/lang/String;)V
public final fun setSerializersModule (Lkotlinx/serialization/modules/SerializersModule;)V
@@ -115,7 +134,7 @@
public abstract fun discriminator ()Ljava/lang/String;
}
-public final class kotlinx/serialization/json/JsonClassDiscriminator$Impl : kotlinx/serialization/json/JsonClassDiscriminator {
+public synthetic class kotlinx/serialization/json/JsonClassDiscriminator$Impl : kotlinx/serialization/json/JsonClassDiscriminator {
public fun <init> (Ljava/lang/String;)V
public final synthetic fun discriminator ()Ljava/lang/String;
}
@@ -124,16 +143,21 @@
public fun <init> ()V
public final fun getAllowSpecialFloatingPointValues ()Z
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
public final fun getExplicitNulls ()Z
public final fun getIgnoreUnknownKeys ()Z
+ public final fun getNamingStrategy ()Lkotlinx/serialization/json/JsonNamingStrategy;
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 final fun setClassDiscriminatorMode (Lkotlinx/serialization/json/ClassDiscriminatorMode;)V
public fun toString ()Ljava/lang/String;
}
@@ -169,6 +193,10 @@
public static final fun add (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/lang/Boolean;)Z
public static final fun add (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/lang/Number;)Z
public static final fun add (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/lang/String;)Z
+ public static final fun add (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/lang/Void;)Z
+ public static final fun addAllBooleans (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/util/Collection;)Z
+ public static final fun addAllNumbers (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/util/Collection;)Z
+ public static final fun addAllStrings (Lkotlinx/serialization/json/JsonArrayBuilder;Ljava/util/Collection;)Z
public static final fun addJsonArray (Lkotlinx/serialization/json/JsonArrayBuilder;Lkotlin/jvm/functions/Function1;)Z
public static final fun addJsonObject (Lkotlinx/serialization/json/JsonArrayBuilder;Lkotlin/jvm/functions/Function1;)Z
public static final fun buildJsonArray (Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonArray;
@@ -176,6 +204,7 @@
public static final fun put (Lkotlinx/serialization/json/JsonObjectBuilder;Ljava/lang/String;Ljava/lang/Boolean;)Lkotlinx/serialization/json/JsonElement;
public static final fun put (Lkotlinx/serialization/json/JsonObjectBuilder;Ljava/lang/String;Ljava/lang/Number;)Lkotlinx/serialization/json/JsonElement;
public static final fun put (Lkotlinx/serialization/json/JsonObjectBuilder;Ljava/lang/String;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement;
+ public static final fun put (Lkotlinx/serialization/json/JsonObjectBuilder;Ljava/lang/String;Ljava/lang/Void;)Lkotlinx/serialization/json/JsonElement;
public static final fun putJsonArray (Lkotlinx/serialization/json/JsonObjectBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonElement;
public static final fun putJsonObject (Lkotlinx/serialization/json/JsonObjectBuilder;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lkotlinx/serialization/json/JsonElement;
}
@@ -184,6 +213,12 @@
public static final fun JsonPrimitive (Ljava/lang/Boolean;)Lkotlinx/serialization/json/JsonPrimitive;
public static final fun JsonPrimitive (Ljava/lang/Number;)Lkotlinx/serialization/json/JsonPrimitive;
public static final fun JsonPrimitive (Ljava/lang/String;)Lkotlinx/serialization/json/JsonPrimitive;
+ public static final fun JsonPrimitive (Ljava/lang/Void;)Lkotlinx/serialization/json/JsonNull;
+ public static final fun JsonPrimitive-7apg3OU (B)Lkotlinx/serialization/json/JsonPrimitive;
+ public static final fun JsonPrimitive-VKZWuLQ (J)Lkotlinx/serialization/json/JsonPrimitive;
+ public static final fun JsonPrimitive-WZ4Q5Ns (I)Lkotlinx/serialization/json/JsonPrimitive;
+ public static final fun JsonPrimitive-xj2QHRw (S)Lkotlinx/serialization/json/JsonPrimitive;
+ public static final fun JsonUnquotedLiteral (Ljava/lang/String;)Lkotlinx/serialization/json/JsonPrimitive;
public static final fun getBoolean (Lkotlinx/serialization/json/JsonPrimitive;)Z
public static final fun getBooleanOrNull (Lkotlinx/serialization/json/JsonPrimitive;)Ljava/lang/Boolean;
public static final fun getContentOrNull (Lkotlinx/serialization/json/JsonPrimitive;)Ljava/lang/String;
@@ -233,11 +268,21 @@
public abstract fun names ()[Ljava/lang/String;
}
-public final class kotlinx/serialization/json/JsonNames$Impl : kotlinx/serialization/json/JsonNames {
+public synthetic class kotlinx/serialization/json/JsonNames$Impl : kotlinx/serialization/json/JsonNames {
public fun <init> ([Ljava/lang/String;)V
public final synthetic fun names ()[Ljava/lang/String;
}
+public abstract interface class kotlinx/serialization/json/JsonNamingStrategy {
+ public static final field Builtins Lkotlinx/serialization/json/JsonNamingStrategy$Builtins;
+ public abstract fun serialNameForJson (Lkotlinx/serialization/descriptors/SerialDescriptor;ILjava/lang/String;)Ljava/lang/String;
+}
+
+public final class kotlinx/serialization/json/JsonNamingStrategy$Builtins {
+ public final fun getKebabCase ()Lkotlinx/serialization/json/JsonNamingStrategy;
+ public final fun getSnakeCase ()Lkotlinx/serialization/json/JsonNamingStrategy;
+}
+
public final class kotlinx/serialization/json/JsonNull : kotlinx/serialization/json/JsonPrimitive {
public static final field INSTANCE Lkotlinx/serialization/json/JsonNull;
public fun getContent ()Ljava/lang/String;
@@ -355,3 +400,34 @@
public static final fun encodeToStream (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;Ljava/io/OutputStream;)V
}
+public abstract interface class kotlinx/serialization/json/internal/InternalJsonReader {
+ public abstract fun read ([CII)I
+}
+
+public abstract interface class kotlinx/serialization/json/internal/InternalJsonWriter {
+ public abstract fun release ()V
+ public abstract fun write (Ljava/lang/String;)V
+ public abstract fun writeChar (C)V
+ public abstract fun writeLong (J)V
+ public abstract fun writeQuoted (Ljava/lang/String;)V
+}
+
+public final class kotlinx/serialization/json/internal/JsonStreamsKt {
+ public static final fun decodeByReader (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/internal/InternalJsonReader;)Ljava/lang/Object;
+ public static final fun decodeToSequenceByReader (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/InternalJsonReader;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;)Lkotlin/sequences/Sequence;
+ public static synthetic fun decodeToSequenceByReader$default (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/InternalJsonReader;Lkotlinx/serialization/DeserializationStrategy;Lkotlinx/serialization/json/DecodeSequenceMode;ILjava/lang/Object;)Lkotlin/sequences/Sequence;
+ public static final fun encodeByWriter (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/internal/InternalJsonWriter;Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)V
+}
+
+public final class kotlinx/serialization/json/internal/StreamingJsonDecoderKt {
+ public static final fun decodeStringToJsonTree (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/DeserializationStrategy;Ljava/lang/String;)Lkotlinx/serialization/json/JsonElement;
+}
+
+public final class kotlinx/serialization/json/internal/TreeJsonDecoderKt {
+ public static final fun readJson (Lkotlinx/serialization/json/Json;Lkotlinx/serialization/json/JsonElement;Lkotlinx/serialization/DeserializationStrategy;)Ljava/lang/Object;
+}
+
+public final class kotlinx/serialization/json/internal/TreeJsonEncoderKt {
+ public static final fun writeJson (Lkotlinx/serialization/json/Json;Ljava/lang/Object;Lkotlinx/serialization/SerializationStrategy;)Lkotlinx/serialization/json/JsonElement;
+}
+
diff --git a/formats/json/build.gradle b/formats/json/build.gradle
index 2294445..d35bcb5 100644
--- a/formats/json/build.gradle
+++ b/formats/json/build.gradle
@@ -1,3 +1,5 @@
+import static KotlinVersion.isKotlinVersionAtLeast
+
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -7,26 +9,50 @@
apply from: rootProject.file("gradle/native-targets.gradle")
apply from: rootProject.file("gradle/configure-source-sets.gradle")
-kotlin {
+// disable kover tasks because there are no tests in the project
+tasks.named("koverHtmlReport") {
+ enabled = false
+}
+tasks.named("koverXmlReport") {
+ enabled = false
+}
+tasks.named("koverVerify") {
+ enabled = false
+}
+kotlin {
sourceSets {
+ configureEach {
+ languageSettings {
+ optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ optIn("kotlinx.serialization.json.internal.JsonFriendModuleApi")
+ }
+ }
commonMain {
dependencies {
api project(":kotlinx-serialization-core")
}
}
-
- jvmTest {
- dependencies {
- implementation 'com.google.code.gson:gson:2.8.5'
- implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
- }
+ jsWasmMain {
+ dependsOn(sourceSets.commonMain)
+ }
+ jsMain {
+ dependsOn(sourceSets.jsWasmMain)
+ }
+ wasmJsMain {
+ dependsOn(sourceSets.jsWasmMain)
+ }
+ wasmWasiMain {
+ dependsOn(sourceSets.jsWasmMain)
}
}
}
-compileTestKotlinJsLegacy {
- exclude '**/PropertyInitializerTest.kt'
-}
-
Java9Modularity.configureJava9ModuleInfo(project)
+
+// This task should be disabled because of no need to build and publish intermediate JsWasm sourceset
+tasks.whenTaskAdded { task ->
+ if (task.name == 'compileJsWasmMainKotlinMetadata') {
+ task.enabled = false
+ }
+}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
index 3cdcf10..2a144fe 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/Json.kt
@@ -7,6 +7,7 @@
import kotlinx.serialization.*
import kotlinx.serialization.json.internal.*
import kotlinx.serialization.modules.*
+import kotlinx.serialization.descriptors.*
import kotlin.native.concurrent.*
/**
@@ -50,7 +51,6 @@
* 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(
public val configuration: JsonConfiguration,
override val serializersModule: SerializersModule
@@ -67,7 +67,8 @@
* The default instance of [Json] with default configuration.
*/
@ThreadLocal // to support caching
- public companion object Default : Json(JsonConfiguration(), EmptySerializersModule)
+ @OptIn(ExperimentalSerializationApi::class)
+ public companion object Default : Json(JsonConfiguration(), EmptySerializersModule())
/**
* Serializes the [value] into an equivalent JSON using the given [serializer].
@@ -75,14 +76,9 @@
* @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 = JsonStringBuilder()
+ val result = JsonToStringWriter()
try {
- val encoder = StreamingJsonEncoder(
- result, this,
- WriteMode.OBJ,
- arrayOfNulls(WriteMode.values().size)
- )
- encoder.encodeSerializableValue(serializer, value)
+ encodeByWriter(this@Json, result, serializer, value)
return result.toString()
} finally {
result.release()
@@ -90,13 +86,24 @@
}
/**
+ * Decodes and deserializes the given JSON [string] to the value of type [T] using deserializer
+ * retrieved from the reified type parameter.
+ *
+ * @throws SerializationException in case of any decoding-specific error
+ * @throws IllegalArgumentException if the decoded input is not a valid instance of [T]
+ */
+ public inline fun <reified T> decodeFromString(@FormatLanguage("json", "", "") string: String): T =
+ decodeFromString(serializersModule.serializer(), string)
+
+ /**
* 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].
+ * @throws [SerializationException] if the given JSON string is not a valid JSON input for the type [T]
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
*/
- public final override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, string: String): T {
+ public final override fun <T> decodeFromString(deserializer: DeserializationStrategy<T>, @FormatLanguage("json", "", "") string: String): T {
val lexer = StringJsonLexer(string)
- val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor)
+ val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor, null)
val result = input.decodeSerializableValue(deserializer)
lexer.expectEof()
return result
@@ -104,35 +111,89 @@
/**
* Serializes the given [value] into an equivalent [JsonElement] using the given [serializer]
*
- * @throws [SerializationException] if the given value cannot be serialized.
+ * @throws [SerializationException] if the given value cannot be serialized to JSON
*/
public fun <T> encodeToJsonElement(serializer: SerializationStrategy<T>, value: T): JsonElement {
- return writeJson(value, serializer)
+ return writeJson(this@Json, 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].
+ * @throws [SerializationException] if the given JSON element is not a valid JSON input for the type [T]
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
*/
public fun <T> decodeFromJsonElement(deserializer: DeserializationStrategy<T>, element: JsonElement): T {
- return readJson(element, deserializer)
+ return readJson(this@Json, 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
+ * @throws [SerializationException] if the given string is not a valid JSON
*/
- public fun parseToJsonElement(string: String): JsonElement {
+ public fun parseToJsonElement(@FormatLanguage("json", "", "") string: String): JsonElement {
return decodeFromString(JsonElementSerializer, string)
}
}
/**
+ * Description of JSON input shape used for decoding to sequence.
+ *
+ * The sequence represents a stream of objects parsed one by one;
+ * [DecodeSequenceMode] defines a separator between these objects.
+ * Typically, these objects are not separated by meaningful characters ([WHITESPACE_SEPARATED]),
+ * or the whole stream is a large array of objects separated with commas ([ARRAY_WRAPPED]).
+ */
+@ExperimentalSerializationApi
+public enum class DecodeSequenceMode {
+ /**
+ * Declares that objects in the input stream are separated by whitespace characters.
+ *
+ * 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.
+ * Each individual object is parsed lazily, when it is requested from the resulting sequence.
+ *
+ * Whitespace character is either ' ', '\n', '\r' or '\t'.
+ *
+ * Example of `WHITESPACE_SEPARATED` stream content:
+ * ```
+ * """{"key": "value"}{"key": "value2"} {"key2": "value2"}"""
+ * ```
+ */
+ WHITESPACE_SEPARATED,
+
+ /**
+ * Declares that objects in the input stream are wrapped in the JSON array.
+ * Each individual object in the array is parsed lazily when it is requested from the resulting sequence.
+ *
+ * The stream is read as multiple JSON objects wrapped into a JSON array.
+ * The stream must start with an array start character `[` and end with an array end character `]`,
+ * otherwise, [JsonDecodingException] is thrown.
+ *
+ * Example of `ARRAY_WRAPPED` stream content:
+ * ```
+ * """[{"key": "value"}, {"key": "value2"},{"key2": "value2"}]"""
+ * ```
+ */
+ ARRAY_WRAPPED,
+
+ /**
+ * Declares that parser itself should select between [WHITESPACE_SEPARATED] and [ARRAY_WRAPPED] modes.
+ * The selection is performed by looking at the first meaningful character of the stream.
+ *
+ * In most cases, auto-detection is sufficient to correctly parse an input.
+ * If the input is _whitespace-separated stream of the arrays_, parser could select an incorrect mode,
+ * for that [DecodeSequenceMode] must be specified explicitly.
+ *
+ * Example of an exceptional case:
+ * `[1, 2, 3] [4, 5, 6]\n[7, 8, 9]`
+ */
+ AUTO_DETECT;
+}
+
+/**
* Creates an instance of [Json] configured from the optionally given [Json instance][from] and adjusted with [builderAction].
*/
-@OptIn(ExperimentalSerializationApi::class)
public fun Json(from: Json = Json.Default, builderAction: JsonBuilder.() -> Unit): Json {
val builder = JsonBuilder(from)
builder.builderAction()
@@ -154,7 +215,8 @@
* Deserializes the given [json] element into a value of type [T] using a deserializer retrieved
* from reified type parameter.
*
- * @throws [SerializationException] if the given JSON string is malformed or cannot be deserialized to the value of type [T].
+ * @throws [SerializationException] if the given JSON element is not a valid JSON input for the type [T]
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
*/
public inline fun <reified T> Json.decodeFromJsonElement(json: JsonElement): T =
decodeFromJsonElement(serializersModule.serializer(), json)
@@ -192,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.
*/
@@ -225,8 +286,8 @@
public var prettyPrintIndent: String = json.configuration.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.
+ * 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.
*
* `false` by default.
@@ -237,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
@@ -246,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.
@@ -264,14 +337,72 @@
public var useAlternativeNames: Boolean = json.configuration.useAlternativeNames
/**
+ * Specifies [JsonNamingStrategy] that should be used for all properties in classes for serialization and deserialization.
+ *
+ * `null` by default.
+ *
+ * This strategy is applied for all entities that have [StructureKind.CLASS].
+ */
+ @ExperimentalSerializationApi
+ public var namingStrategy: JsonNamingStrategy? = json.configuration.namingStrategy
+
+ /**
+ * Enables decoding enum values in a case-insensitive manner.
+ * Encoding is not affected.
+ *
+ * This affects both enum serial names and alternative names (specified with the [JsonNames] annotation).
+ * In the following example, string `[VALUE_A, VALUE_B]` will be printed:
+ * ```
+ * enum class E { VALUE_A, @JsonNames("ALTERNATIVE") VALUE_B }
+ *
+ * @Serializable
+ * data class Outer(val enums: List<E>)
+ *
+ * val j = Json { decodeEnumsCaseInsensitive = true }
+ * println(j.decodeFromString<Outer>("""{"enums":["value_A", "alternative"]}""").enums)
+ * ```
+ *
+ * If this feature is enabled,
+ * it is no longer possible to decode enum values that have the same name in a lowercase form.
+ * The following code will throw a serialization exception:
+ *
+ * ```
+ * enum class BadEnum { Bad, BAD }
+ * val j = Json { decodeEnumsCaseInsensitive = true }
+ * j.decodeFromString<Box<BadEnum>>("""{"boxed":"bad"}""")
+ * ```
+ */
+ @ExperimentalSerializationApi
+ public var decodeEnumsCaseInsensitive: Boolean = json.configuration.decodeEnumsCaseInsensitive
+
+ /**
+ * Allows parser to accept trailing (ending) commas in JSON objects and arrays,
+ * making inputs like `[1, 2, 3,]` valid.
+ *
+ * Does not affect encoding.
+ * `false` by default.
+ */
+ @ExperimentalSerializationApi
+ public var allowTrailingComma: Boolean = json.configuration.allowTrailingComma
+
+ /**
* Module with contextual and polymorphic serializers to be used in the resulting [Json] instance.
+ *
+ * @see SerializersModule
+ * @see Contextual
+ * @see Polymorphic
*/
public var serializersModule: SerializersModule = json.serializersModule
@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) {
@@ -290,7 +421,8 @@
encodeDefaults, ignoreUnknownKeys, isLenient,
allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
- classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames
+ classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
+ namingStrategy, decodeEnumsCaseInsensitive, allowTrailingComma, classDiscriminatorMode
)
}
}
@@ -303,7 +435,7 @@
}
private fun validateConfiguration() {
- if (serializersModule == EmptySerializersModule) return // Fast-path for in-place JSON allocations
+ if (serializersModule == EmptySerializersModule()) return // Fast-path for in-place JSON allocations
val collector = PolymorphismValidator(configuration.useArrayPolymorphism, configuration.classDiscriminator)
serializersModule.dumpTo(collector)
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt
index aae6988..4ec5a2b 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt
@@ -24,12 +24,16 @@
* data class Project(@JsonNames("title") val name: String)
*
* val project = Json.decodeFromString<Project>("""{"name":"kotlinx.serialization"}""")
- * println(project)
+ * println(project) // OK
* val oldProject = Json.decodeFromString<Project>("""{"title":"kotlinx.coroutines"}""")
- * println(oldProject)
+ * println(oldProject) // Also OK
* ```
*
* This annotation has lesser priority than [SerialName].
+ * In practice, this means that if property A has `@SerialName("foo")` annotation, and property B has `@JsonNames("foo")` annotation,
+ * Json key `foo` will be deserialized into property A.
+ *
+ * Using the same alternative name for different properties across one class is prohibited and leads to a deserialization exception.
*
* @see JsonBuilder.useAlternativeNames
*/
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt
index 612cfc7..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]
@@ -9,12 +11,12 @@
* 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
+ * Standalone configuration object is meaningless and can nor be used outside the
* [Json], neither new [Json] instance can be created from it.
*
* Detailed description of each property is available in [JsonBuilder] class.
*/
-public class JsonConfiguration internal constructor(
+public class JsonConfiguration @OptIn(ExperimentalSerializationApi::class) internal constructor(
public val encodeDefaults: Boolean = false,
public val ignoreUnknownKeys: Boolean = false,
public val isLenient: Boolean = false,
@@ -28,7 +30,15 @@
public val useArrayPolymorphism: Boolean = false,
public val classDiscriminator: String = "type",
public val allowSpecialFloatingPointValues: Boolean = false,
- public val useAlternativeNames: Boolean = true
+ public val useAlternativeNames: Boolean = true,
+ @ExperimentalSerializationApi
+ public val namingStrategy: JsonNamingStrategy? = null,
+ @ExperimentalSerializationApi
+ public val decodeEnumsCaseInsensitive: Boolean = false,
+ @ExperimentalSerializationApi
+ public val allowTrailingComma: Boolean = false,
+ @ExperimentalSerializationApi
+ public var classDiscriminatorMode: ClassDiscriminatorMode = ClassDiscriminatorMode.POLYMORPHIC,
) {
/** @suppress Dokka **/
@@ -37,6 +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)"
+ "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/JsonContentPolymorphicSerializer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonContentPolymorphicSerializer.kt
index 7255a59..e11b98f 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonContentPolymorphicSerializer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonContentPolymorphicSerializer.kt
@@ -55,11 +55,12 @@
*
* // Now both statements will yield different subclasses of Payment:
*
- * Json.parse(PaymentSerializer, """{"amount":"1.0","date":"03.02.2020"}""")
- * Json.parse(PaymentSerializer, """{"amount":"2.0","date":"03.02.2020","reason":"complaint"}""")
+ * Json.decodeFromString(PaymentSerializer, """{"amount":"1.0","date":"03.02.2020"}""")
+ * Json.decodeFromString(PaymentSerializer, """{"amount":"2.0","date":"03.02.2020","reason":"complaint"}""")
* ```
*
- * @param T A root class for all classes that could be possibly encountered during serialization and deserialization.
+ * @param T A root type for all classes that could be possibly encountered during serialization and deserialization.
+ * Must be non-final class or interface.
* @param baseClass A class token for [T].
*/
@OptIn(ExperimentalSerializationApi::class)
@@ -96,7 +97,7 @@
/**
* Determines a particular strategy for deserialization by looking on a parsed JSON [element].
*/
- protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<out T>
+ protected abstract fun selectDeserializer(element: JsonElement): DeserializationStrategy<T>
private fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing {
val subClassName = subClass.simpleName ?: "$subClass"
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt
index 7d21330..74abf34 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:Suppress("unused")
@@ -7,6 +7,9 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
+import kotlinx.serialization.builtins.serializer
+import kotlinx.serialization.descriptors.SerialDescriptor
+import kotlinx.serialization.internal.InlinePrimitiveDescriptor
import kotlinx.serialization.json.internal.*
/**
@@ -45,37 +48,107 @@
public override fun toString(): String = content
}
-/**
- * Creates [JsonPrimitive] from the given boolean.
- */
+/** Creates a [JsonPrimitive] from the given boolean. */
public fun JsonPrimitive(value: Boolean?): JsonPrimitive {
if (value == null) return JsonNull
return JsonLiteral(value, isString = false)
}
-/**
- * Creates [JsonPrimitive] from the given number.
- */
+/** Creates a [JsonPrimitive] from the given number. */
public fun JsonPrimitive(value: Number?): JsonPrimitive {
if (value == null) return JsonNull
return JsonLiteral(value, isString = false)
}
/**
- * Creates [JsonPrimitive] from the given string.
+ * Creates a numeric [JsonPrimitive] from the given [UByte].
+ *
+ * The value will be encoded as a JSON number.
*/
+@ExperimentalSerializationApi
+public fun JsonPrimitive(value: UByte): JsonPrimitive = JsonPrimitive(value.toULong())
+
+/**
+ * Creates a numeric [JsonPrimitive] from the given [UShort].
+ *
+ * The value will be encoded as a JSON number.
+ */
+@ExperimentalSerializationApi
+public fun JsonPrimitive(value: UShort): JsonPrimitive = JsonPrimitive(value.toULong())
+
+/**
+ * Creates a numeric [JsonPrimitive] from the given [UInt].
+ *
+ * The value will be encoded as a JSON number.
+ */
+@ExperimentalSerializationApi
+public fun JsonPrimitive(value: UInt): JsonPrimitive = JsonPrimitive(value.toULong())
+
+/**
+ * Creates a numeric [JsonPrimitive] from the given [ULong].
+ *
+ * The value will be encoded as a JSON number.
+ */
+@SuppressAnimalSniffer // Long.toUnsignedString(long)
+@ExperimentalSerializationApi
+public fun JsonPrimitive(value: ULong): JsonPrimitive = JsonUnquotedLiteral(value.toString())
+
+/** Creates a [JsonPrimitive] from the given string. */
public fun JsonPrimitive(value: String?): JsonPrimitive {
if (value == null) return JsonNull
return JsonLiteral(value, isString = true)
}
+/** Creates [JsonNull]. */
+@ExperimentalSerializationApi
+@Suppress("FunctionName", "UNUSED_PARAMETER") // allows to call `JsonPrimitive(null)`
+public fun JsonPrimitive(value: Nothing?): JsonNull = JsonNull
+
+/**
+ * Creates a [JsonPrimitive] from the given string, without surrounding it in quotes.
+ *
+ * This function is provided for encoding raw JSON values that cannot be encoded using the [JsonPrimitive] functions.
+ * For example,
+ *
+ * * precise numeric values (avoiding floating-point precision errors associated with [Double] and [Float]),
+ * * large numbers,
+ * * or complex JSON objects.
+ *
+ * Be aware that it is possible to create invalid JSON using this function.
+ *
+ * Creating a literal unquoted value of `null` (as in, `value == "null"`) is forbidden. If you want to create
+ * JSON null literal, use [JsonNull] object, otherwise, use [JsonPrimitive].
+ *
+ * @see JsonPrimitive is the preferred method for encoding JSON primitives.
+ * @throws JsonEncodingException if `value == "null"`
+ */
+@ExperimentalSerializationApi
+@Suppress("FunctionName")
+public fun JsonUnquotedLiteral(value: String?): JsonPrimitive {
+ return when (value) {
+ null -> JsonNull
+ JsonNull.content -> throw JsonEncodingException("Creating a literal unquoted value of 'null' is forbidden. If you want to create JSON null literal, use JsonNull object, otherwise, use JsonPrimitive")
+ else -> JsonLiteral(value, isString = false, coerceToInlineType = jsonUnquotedLiteralDescriptor)
+ }
+}
+
+/** Used as a marker to indicate during encoding that the [JsonEncoder] should use `encodeInline()` */
+internal val jsonUnquotedLiteralDescriptor: SerialDescriptor =
+ InlinePrimitiveDescriptor("kotlinx.serialization.json.JsonUnquotedLiteral", String.serializer())
+
+
// JsonLiteral is deprecated for public use and no longer available. Please use JsonPrimitive instead
internal class JsonLiteral internal constructor(
body: Any,
- public override val isString: Boolean
+ public override val isString: Boolean,
+ internal val coerceToInlineType: SerialDescriptor? = null,
) : JsonPrimitive() {
public override val content: String = body.toString()
+ init {
+ if (coerceToInlineType != null) require(coerceToInlineType.isInline)
+ }
+
public override fun toString(): String =
if (isString) buildString { printQuoted(content) }
else content
@@ -90,6 +163,7 @@
return true
}
+ @SuppressAnimalSniffer // Boolean.hashCode(boolean)
public override fun hashCode(): Int {
var result = isString.hashCode()
result = 31 * result + content.hashCode()
@@ -113,7 +187,9 @@
* traditional methods like [Map.get] or [Map.getValue] to obtain Json elements.
*/
@Serializable(JsonObjectSerializer::class)
-public class JsonObject(private val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content {
+public class JsonObject(
+ private val content: Map<String, JsonElement>
+) : JsonElement(), Map<String, JsonElement> by content {
public override fun equals(other: Any?): Boolean = content == other
public override fun hashCode(): Int = content.hashCode()
public override fun toString(): String {
@@ -177,23 +253,35 @@
* Returns content of the current element as int
* @throws NumberFormatException if current element is not a valid representation of number
*/
-public val JsonPrimitive.int: Int get() = content.toInt()
+public val JsonPrimitive.int: Int
+ get() {
+ val result = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() }
+ if (result !in Int.MIN_VALUE..Int.MAX_VALUE) throw NumberFormatException("$content is not an Int")
+ return result.toInt()
+ }
/**
* Returns content of the current element as int or `null` if current element is not a valid representation of number
*/
-public val JsonPrimitive.intOrNull: Int? get() = content.toIntOrNull()
+public val JsonPrimitive.intOrNull: Int?
+ get() {
+ val result = mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() } ?: return null
+ if (result !in Int.MIN_VALUE..Int.MAX_VALUE) return null
+ return result.toInt()
+ }
/**
* Returns content of current element as long
* @throws NumberFormatException if current element is not a valid representation of number
*/
-public val JsonPrimitive.long: Long get() = content.toLong()
+public val JsonPrimitive.long: Long get() = mapExceptions { StringJsonLexer(content).consumeNumericLiteral() }
/**
* Returns content of current element as long or `null` if current element is not a valid representation of number
*/
-public val JsonPrimitive.longOrNull: Long? get() = content.toLongOrNull()
+public val JsonPrimitive.longOrNull: Long?
+ get() =
+ mapExceptionsToNull { StringJsonLexer(content).consumeNumericLiteral() }
/**
* Returns content of current element as double
@@ -221,7 +309,8 @@
* Returns content of current element as boolean
* @throws IllegalStateException if current element doesn't represent boolean
*/
-public val JsonPrimitive.boolean: Boolean get() = content.toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
+public val JsonPrimitive.boolean: Boolean
+ get() = content.toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
/**
* Returns content of current element as boolean or `null` if current element is not a valid representation of boolean
@@ -236,6 +325,22 @@
private fun JsonElement.error(element: String): Nothing =
throw IllegalArgumentException("Element ${this::class} is not a $element")
+private inline fun <T> mapExceptionsToNull(f: () -> T): T? {
+ return try {
+ f()
+ } catch (e: JsonDecodingException) {
+ null
+ }
+}
+
+private inline fun <T> mapExceptions(f: () -> T): T {
+ return try {
+ f()
+ } catch (e: JsonDecodingException) {
+ throw NumberFormatException(e.message)
+ }
+}
+
@PublishedApi
internal fun unexpectedJson(key: String, expected: String): Nothing =
throw IllegalArgumentException("Element $key is not a $expected")
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementBuilders.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementBuilders.kt
index c4b925f..7a25efd 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementBuilders.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementBuilders.kt
@@ -5,7 +5,9 @@
package kotlinx.serialization.json
+import kotlinx.serialization.ExperimentalSerializationApi
import kotlin.contracts.*
+import kotlin.jvm.JvmName
/**
* Builds [JsonObject] with the given [builderAction] builder.
@@ -72,7 +74,7 @@
}
/**
- * Add the [JSON][JsonObject] produced by the [builderAction] function to a resulting json object using the given [key].
+ * Add the [JSON object][JsonObject] produced by the [builderAction] function to a resulting JSON object using the given [key].
*
* Returns the previous value associated with [key], or `null` if the key was not present.
*/
@@ -80,7 +82,7 @@
put(key, buildJsonObject(builderAction))
/**
- * Add the [JSON array][JsonArray] produced by the [builderAction] function to a resulting json object using the given [key].
+ * Add the [JSON array][JsonArray] produced by the [builderAction] function to a resulting JSON object using the given [key].
*
* Returns the previous value associated with [key], or `null` if the key was not present.
*/
@@ -109,6 +111,15 @@
public fun JsonObjectBuilder.put(key: String, value: String?): JsonElement? = put(key, JsonPrimitive(value))
/**
+ * Add `null` to a resulting JSON object using the given [key].
+ *
+ * Returns the previous value associated with [key], or `null` if the key was not present.
+ */
+@ExperimentalSerializationApi
+@Suppress("UNUSED_PARAMETER") // allows to call `put("key", null)`
+public fun JsonObjectBuilder.put(key: String, value: Nothing?): JsonElement? = put(key, JsonNull)
+
+/**
* DSL builder for a [JsonArray]. To create an instance of builder, use [buildJsonArray] build function.
*/
@JsonDslMarker
@@ -117,7 +128,7 @@
private val content: MutableList<JsonElement> = mutableListOf()
/**
- * Adds the given JSON [element] to a resulting array.
+ * Adds the given JSON [element] to a resulting JSON array.
*
* Always returns `true` similarly to [ArrayList] specification.
*/
@@ -126,33 +137,51 @@
return true
}
+ /**
+ * Adds the given JSON [elements] to a resulting JSON array.
+ *
+ * @return `true` if the list was changed as the result of the operation.
+ */
+ @ExperimentalSerializationApi
+ public fun addAll(elements: Collection<JsonElement>): Boolean =
+ content.addAll(elements)
+
@PublishedApi
internal fun build(): JsonArray = JsonArray(content)
}
/**
- * Adds the given boolean [value] to a resulting array.
+ * Adds the given boolean [value] to a resulting JSON array.
*
* Always returns `true` similarly to [ArrayList] specification.
*/
public fun JsonArrayBuilder.add(value: Boolean?): Boolean = add(JsonPrimitive(value))
/**
- * Adds the given numeric [value] to a resulting array.
+ * Adds the given numeric [value] to a resulting JSON array.
*
* Always returns `true` similarly to [ArrayList] specification.
*/
public fun JsonArrayBuilder.add(value: Number?): Boolean = add(JsonPrimitive(value))
/**
- * Adds the given string [value] to a resulting array.
+ * Adds the given string [value] to a resulting JSON array.
*
* Always returns `true` similarly to [ArrayList] specification.
*/
public fun JsonArrayBuilder.add(value: String?): Boolean = add(JsonPrimitive(value))
/**
- * Adds the [JSON][JsonObject] produced by the [builderAction] function to a resulting array.
+ * Adds `null` to a resulting JSON array.
+ *
+ * Always returns `true` similarly to [ArrayList] specification.
+ */
+@ExperimentalSerializationApi
+@Suppress("UNUSED_PARAMETER") // allows to call `add(null)`
+public fun JsonArrayBuilder.add(value: Nothing?): Boolean = add(JsonNull)
+
+/**
+ * Adds the [JSON object][JsonObject] produced by the [builderAction] function to a resulting JSON array.
*
* Always returns `true` similarly to [ArrayList] specification.
*/
@@ -160,15 +189,42 @@
add(buildJsonObject(builderAction))
/**
- * Adds the [JSON][JsonArray] produced by the [builderAction] function to a resulting array.
+ * Adds the [JSON array][JsonArray] produced by the [builderAction] function to a resulting JSON array.
*
* Always returns `true` similarly to [ArrayList] specification.
*/
public fun JsonArrayBuilder.addJsonArray(builderAction: JsonArrayBuilder.() -> Unit): Boolean =
add(buildJsonArray(builderAction))
-private const val infixToDeprecated = "Infix 'to' operator is deprecated for removal for the favour of 'add'"
-private const val unaryPlusDeprecated = "Unary plus is deprecated for removal for the favour of 'add'"
+/**
+ * Adds the given string [values] to a resulting JSON array.
+ *
+ * @return `true` if the list was changed as the result of the operation.
+ */
+@JvmName("addAllStrings")
+@ExperimentalSerializationApi
+public fun JsonArrayBuilder.addAll(values: Collection<String?>): Boolean =
+ addAll(values.map(::JsonPrimitive))
+
+/**
+ * Adds the given boolean [values] to a resulting JSON array.
+ *
+ * @return `true` if the list was changed as the result of the operation.
+ */
+@JvmName("addAllBooleans")
+@ExperimentalSerializationApi
+public fun JsonArrayBuilder.addAll(values: Collection<Boolean?>): Boolean =
+ addAll(values.map(::JsonPrimitive))
+
+/**
+ * Adds the given numeric [values] to a resulting JSON array.
+ *
+ * @return `true` if the list was changed as the result of the operation.
+ */
+@JvmName("addAllNumbers")
+@ExperimentalSerializationApi
+public fun JsonArrayBuilder.addAll(values: Collection<Number?>): Boolean =
+ addAll(values.map(::JsonPrimitive))
@DslMarker
internal annotation class JsonDslMarker
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementSerializers.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementSerializers.kt
index a6d0d0f..269f68b 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementSerializers.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonElementSerializers.kt
@@ -13,8 +13,8 @@
import kotlinx.serialization.json.internal.JsonDecodingException
/**
- * External [Serializer] object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonElement].
- * It can only be used by with [Json] format an its input ([JsonDecoder] and [JsonEncoder]).
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonElement].
+ * It can only be used by with [Json] format and its input ([JsonDecoder] and [JsonEncoder]).
* Currently, this hierarchy has no guarantees on descriptor content.
*
* Example usage:
@@ -24,7 +24,6 @@
* assertEquals(JsonObject(mapOf("key" to JsonLiteral(1.0))), literal)
* ```
*/
-@Serializer(forClass = JsonElement::class)
@PublishedApi
internal object JsonElementSerializer : KSerializer<JsonElement> {
override val descriptor: SerialDescriptor =
@@ -53,10 +52,9 @@
}
/**
- * External [Serializer] object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonPrimitive].
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonPrimitive].
* It can only be used by with [Json] format an its input ([JsonDecoder] and [JsonEncoder]).
*/
-@Serializer(forClass = JsonPrimitive::class)
@PublishedApi
internal object JsonPrimitiveSerializer : KSerializer<JsonPrimitive> {
override val descriptor: SerialDescriptor =
@@ -79,10 +77,9 @@
}
/**
- * External [Serializer] object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonNull].
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonNull].
* It can only be used by with [Json] format an its input ([JsonDecoder] and [JsonEncoder]).
*/
-@Serializer(forClass = JsonNull::class)
@PublishedApi
internal object JsonNullSerializer : KSerializer<JsonNull> {
// technically, JsonNull is an object, but it does not call beginStructure/endStructure at all
@@ -109,14 +106,20 @@
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("kotlinx.serialization.json.JsonLiteral", PrimitiveKind.STRING)
- @OptIn(ExperimentalUnsignedTypes::class, ExperimentalSerializationApi::class)
+ @OptIn(ExperimentalSerializationApi::class)
override fun serialize(encoder: Encoder, value: JsonLiteral) {
verify(encoder)
if (value.isString) {
return encoder.encodeString(value.content)
}
- value.longOrNull?.let { return encoder.encodeLong(it) }
+ if (value.coerceToInlineType != null) {
+ return encoder.encodeInline(value.coerceToInlineType).encodeString(value.content)
+ }
+
+ // use .content instead of .longOrNull as latter can process exponential notation,
+ // and it should be delegated to double when encoding.
+ value.content.toLongOrNull()?.let { return encoder.encodeLong(it) }
// most unsigned values fit to .longOrNull, but not ULong
value.content.toULongOrNull()?.let {
@@ -124,8 +127,8 @@
return
}
- value.doubleOrNull?.let { return encoder.encodeDouble(it) }
- value.booleanOrNull?.let { return encoder.encodeBoolean(it) }
+ value.content.toDoubleOrNull()?.let { return encoder.encodeDouble(it) }
+ value.content.toBooleanStrictOrNull()?.let { return encoder.encodeBoolean(it) }
encoder.encodeString(value.content)
}
@@ -138,10 +141,9 @@
}
/**
- * External [Serializer] object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonObject].
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonObject].
* It can only be used by with [Json] format an its input ([JsonDecoder] and [JsonEncoder]).
*/
-@Serializer(forClass = JsonObject::class)
@PublishedApi
internal object JsonObjectSerializer : KSerializer<JsonObject> {
@@ -164,10 +166,9 @@
}
/**
- * External [Serializer] object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonArray].
+ * Serializer object providing [SerializationStrategy] and [DeserializationStrategy] for [JsonArray].
* It can only be used by with [Json] format an its input ([JsonDecoder] and [JsonEncoder]).
*/
-@Serializer(forClass = JsonArray::class)
@PublishedApi
internal object JsonArraySerializer : KSerializer<JsonArray> {
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
new file mode 100644
index 0000000..b737fd6
--- /dev/null
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt
@@ -0,0 +1,177 @@
+package kotlinx.serialization.json
+
+import kotlinx.serialization.*
+import kotlinx.serialization.descriptors.*
+
+
+/**
+ * Represents naming strategy — a transformer for serial names in a [Json] format.
+ * Transformed serial names are used for both serialization and deserialization.
+ * A naming strategy is always applied globally in the Json configuration builder
+ * (see [JsonBuilder.namingStrategy]).
+ *
+ * Actual transformation happens in the [serialNameForJson] function.
+ * It is possible to apply additional filtering inside the transformer using the `descriptor` parameter in [serialNameForJson].
+ *
+ * Original serial names are never used after transformation, so they are ignored in a Json input.
+ * If the original serial name is present in the Json input but transformed is not,
+ * [MissingFieldException] still would be thrown. If one wants to preserve the original serial name for deserialization,
+ * one should use the [JsonNames] annotation, as its values are not transformed.
+ *
+ * ### Common pitfalls in conjunction with other Json features
+ *
+ * * Due to the nature of kotlinx.serialization framework, naming strategy transformation is applied to all properties regardless
+ * of whether their serial name was taken from the property name or provided by @[SerialName] annotation.
+ * Effectively, it means one cannot avoid transformation by explicitly specifying the serial name.
+ *
+ * * Collision of the transformed name with any other (transformed) properties serial names or any alternative names
+ * specified with [JsonNames] will lead to a deserialization exception.
+ *
+ * * Naming strategies do not transform serial names of the types used for the polymorphism, as they always should be specified explicitly.
+ * Values from [JsonClassDiscriminator] or global [JsonBuilder.classDiscriminator] also are not altered.
+ *
+ * ### Controversy about using global naming strategies
+ *
+ * Global naming strategies have one key trait that makes them a debatable and controversial topic:
+ * They are very implicit. It means that by looking only at the definition of the class,
+ * it is impossible to say which names it will have in the serialized form.
+ * As a consequence, naming strategies are not friendly to refactorings. Programmer renaming `myId` to `userId` may forget
+ * to rename `my_id`, and vice versa. Generally, any tools one can imagine work poorly with global naming strategies:
+ * Find Usages/Rename in IDE, full-text search by grep, etc. For them, the original name and the transformed are two different things;
+ * changing one without the other may introduce bugs in many unexpected ways.
+ * The lack of a single place of definition, the inability to use automated tools, and more error-prone code lead
+ * to greater maintenance efforts for code with global naming strategies.
+ * However, there are cases where usage of naming strategies is inevitable, such as interop with an existing API or migrating a large codebase.
+ * Therefore, one should carefully weigh the pros and cons before considering adding global naming strategies to an application.
+ */
+@ExperimentalSerializationApi
+public fun interface JsonNamingStrategy {
+ /**
+ * Accepts an original [serialName] (defined by property name in the class or [SerialName] annotation) and returns
+ * a transformed serial name which should be used for serialization and deserialization.
+ *
+ * Besides string manipulation operations, it is also possible to implement transformations that depend on the [descriptor]
+ * and its element (defined by [elementIndex]) currently being serialized.
+ * It is guaranteed that `descriptor.getElementName(elementIndex) == serialName`.
+ * For example, one can choose different transformations depending on [SerialInfo]
+ * annotations (see [SerialDescriptor.getElementAnnotations]) or element optionality (see [SerialDescriptor.isElementOptional]).
+ *
+ * Note that invocations of this function are cached for performance reasons.
+ * Caching strategy is an implementation detail and should not be assumed as a part of the public API contract, as it may be changed in future releases.
+ * Therefore, it is essential for this function to be pure: it should not have any side effects, and it should
+ * return the same String for a given [descriptor], [elementIndex], and [serialName], regardless of the number of invocations.
+ */
+ public fun serialNameForJson(descriptor: SerialDescriptor, elementIndex: Int, serialName: String): String
+
+ /**
+ * Contains basic, ready to use naming strategies.
+ */
+ @ExperimentalSerializationApi
+ public companion object Builtins {
+
+ /**
+ * A strategy that transforms serial names from camel case to snake case — lowercase characters with words separated by underscores.
+ * 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 underscore in front:
+ * `twoWords` -> `two_words`. No underscore is added if it was a beginning of the name: `MyProperty` -> `my_property`. Also, no underscore is added if it was already there:
+ * `camel_Case_Underscores` -> `camel_case_underscores`.
+ *
+ * **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 SnakeCase: JsonNamingStrategy = object : JsonNamingStrategy {
+ 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/JsonTransformingSerializer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt
index 8d1485d..22d87a7 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt
@@ -67,7 +67,7 @@
final override fun serialize(encoder: Encoder, value: T) {
val output = encoder.asJsonEncoder()
- var element = output.json.writeJson(value, tSerializer)
+ var element = writeJson(output.json, value, tSerializer)
element = transformSerialize(element)
output.encodeJsonElement(element)
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/CharArrayPool.common.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/CharArrayPool.common.kt
new file mode 100644
index 0000000..920b65b
--- /dev/null
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/CharArrayPool.common.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.serialization.json.internal
+
+internal expect object CharArrayPoolBatchSize {
+ fun take(): CharArray
+ fun release(array: CharArray)
+}
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 2bc080f..abdd1c4 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt
@@ -1,19 +1,19 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalSerializationApi::class)
package kotlinx.serialization.json.internal
-import kotlinx.serialization.ExperimentalSerializationApi
-import kotlinx.serialization.json.Json
-import kotlin.jvm.JvmField
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlin.jvm.*
-internal fun Composer(sb: JsonStringBuilder, json: Json): Composer =
+internal fun Composer(sb: InternalJsonWriter, json: Json): Composer =
if (json.configuration.prettyPrint) ComposerWithPrettyPrint(sb, json) else Composer(sb)
@OptIn(ExperimentalSerializationApi::class)
-internal open class Composer(@JvmField internal val sb: JsonStringBuilder) {
+internal open class Composer(@JvmField internal val writer: InternalJsonWriter) {
var writingFirst = true
protected set
@@ -27,43 +27,54 @@
writingFirst = false
}
+ open fun nextItemIfNotFirst() {
+ writingFirst = false
+ }
+
open fun space() = Unit
- fun print(v: Char) = sb.append(v)
- fun print(v: String) = sb.append(v)
- open fun print(v: Float) = sb.append(v.toString())
- open fun print(v: Double) = sb.append(v.toString())
- open fun print(v: Byte) = sb.append(v.toLong())
- open fun print(v: Short) = sb.append(v.toLong())
- open fun print(v: Int) = sb.append(v.toLong())
- open fun print(v: Long) = sb.append(v)
- open fun print(v: Boolean) = sb.append(v.toString())
- fun printQuoted(value: String): Unit = sb.appendQuoted(value)
+ fun print(v: Char) = writer.writeChar(v)
+ fun print(v: String) = writer.write(v)
+ open fun print(v: Float) = writer.write(v.toString())
+ open fun print(v: Double) = writer.write(v.toString())
+ open fun print(v: Byte) = writer.writeLong(v.toLong())
+ open fun print(v: Short) = writer.writeLong(v.toLong())
+ open fun print(v: Int) = writer.writeLong(v.toLong())
+ open fun print(v: Long) = writer.writeLong(v)
+ open fun print(v: Boolean) = writer.write(v.toString())
+ open fun printQuoted(value: String) = writer.writeQuoted(value)
}
-@ExperimentalUnsignedTypes
-internal class ComposerForUnsignedNumbers(sb: JsonStringBuilder) : Composer(sb) {
+@SuppressAnimalSniffer // Long(Integer).toUnsignedString(long)
+internal class ComposerForUnsignedNumbers(writer: InternalJsonWriter, private val forceQuoting: Boolean) : Composer(writer) {
override fun print(v: Int) {
- return super.print(v.toUInt().toString())
+ if (forceQuoting) printQuoted(v.toUInt().toString()) else print(v.toUInt().toString())
}
override fun print(v: Long) {
- return super.print(v.toULong().toString())
+ if (forceQuoting) printQuoted(v.toULong().toString()) else print(v.toULong().toString())
}
override fun print(v: Byte) {
- return super.print(v.toUByte().toString())
+ if (forceQuoting) printQuoted(v.toUByte().toString()) else print(v.toUByte().toString())
}
override fun print(v: Short) {
- return super.print(v.toUShort().toString())
+ if (forceQuoting) printQuoted(v.toUShort().toString()) else print(v.toUShort().toString())
+ }
+}
+
+@SuppressAnimalSniffer
+internal class ComposerForUnquotedLiterals(writer: InternalJsonWriter, private val forceQuoting: Boolean) : Composer(writer) {
+ override fun printQuoted(value: String) {
+ if (forceQuoting) super.printQuoted(value) else super.print(value)
}
}
internal class ComposerWithPrettyPrint(
- sb: JsonStringBuilder,
+ writer: InternalJsonWriter,
private val json: Json
-) : Composer(sb) {
+) : Composer(writer) {
private var level = 0
override fun indent() {
@@ -81,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/FormatLanguage.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt
new file mode 100644
index 0000000..275aa71
--- /dev/null
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt
@@ -0,0 +1,45 @@
+package kotlinx.serialization.json.internal;
+
+import kotlinx.serialization.InternalSerializationApi
+
+/**
+ * Multiplatform analogue of `org.intellij.lang.annotations.Language` annotation.
+ *
+ * An alias is used instead of class, because the actual class in the JVM will conflict with the class from the stdlib -
+ * we want to avoid the situation with different classes having the same fully-qualified name.
+ * [see](https://github.com/JetBrains/java-annotations/issues/34)
+ *
+ * Specifies that an element of the program represents a string that is a source code on a specified language.
+ * Code editors may use this annotation to enable syntax highlighting, code completion and other features
+ * inside the literals that assigned to the annotated variables, passed as arguments to the annotated parameters,
+ * or returned from the annotated methods.
+ * <p>
+ * This annotation also could be used as a meta-annotation, to define derived annotations for convenience.
+ * E.g. the following annotation could be defined to annotate the strings that represent Java methods:
+ *
+ * <pre>
+ * @Language(value = "JAVA", prefix = "class X{", suffix = "}")
+ * @interface JavaMethod {}
+ * </pre>
+ * <p>
+ * Note that using the derived annotation as meta-annotation is not supported.
+ * Meta-annotation works only one level deep.
+ */
+
+@InternalSerializationApi
+@Retention(AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.ANNOTATION_CLASS
+)
+public expect annotation class FormatLanguage(
+ public val value: String,
+ // default parameters are not used due to https://youtrack.jetbrains.com/issue/KT-25946/
+ public val prefix: String,
+ public val suffix: String,
+)
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/JsonElementMarker.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt
index 2535739..85a4624 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt
@@ -2,7 +2,6 @@
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package kotlinx.serialization.json.internal
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
index d1698db..c6098dd 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
@@ -46,6 +46,13 @@
hint = specialFlowingValuesHint)
}
+internal fun AbstractJsonLexer.invalidTrailingComma(entity: String = "object"): Nothing {
+ fail("Trailing comma before the end of JSON $entity",
+ position = currentPosition - 1,
+ hint = "Trailing commas are non-complaint JSON and not allowed by default. Use 'allowTrailingCommas = true' in 'Json {}' builder to support them."
+ )
+}
+
@OptIn(ExperimentalSerializationApi::class)
internal fun InvalidKeyKindException(keyDescriptor: SerialDescriptor) = JsonEncodingException(
"Value of type '${keyDescriptor.serialName}' can't be used in JSON as a key in the map. " +
@@ -70,12 +77,12 @@
internal fun UnknownKeyException(key: String, input: String) = JsonDecodingException(
-1,
- "Encountered unknown key '$key'.\n" +
+ "Encountered an unknown key '$key'.\n" +
"$ignoreUnknownKeysHint\n" +
"Current input: ${input.minify()}"
)
-private fun CharSequence.minify(offset: Int = -1): CharSequence {
+internal fun CharSequence.minify(offset: Int = -1): CharSequence {
if (length < 200) return this
if (offset == -1) {
val start = this.length - 60
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonIterator.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonIterator.kt
similarity index 93%
rename from formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonIterator.kt
rename to formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonIterator.kt
index 7900308..00f36b2 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonIterator.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonIterator.kt
@@ -56,7 +56,7 @@
private val deserializer: DeserializationStrategy<T>
) : Iterator<T> {
override fun next(): T =
- StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor)
+ StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
.decodeSerializableValue(deserializer)
override fun hasNext(): Boolean = lexer.isNotEof()
@@ -68,6 +68,7 @@
private val deserializer: DeserializationStrategy<T>
) : Iterator<T> {
private var first = true
+ private var finished = false
override fun next(): T {
if (first) {
@@ -75,7 +76,7 @@
} else {
lexer.consumeNextToken(COMMA)
}
- val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor)
+ val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
return input.decodeSerializableValue(deserializer)
}
@@ -83,7 +84,9 @@
* Note: if array separator (comma) is missing, hasNext() returns true, but next() throws an exception.
*/
override fun hasNext(): Boolean {
+ if (finished) return false
if (lexer.peekNextToken() == TC_END_LIST) {
+ finished = true
lexer.consumeNextToken(TC_END_LIST)
if (lexer.isNotEof()) {
if (lexer.peekNextToken() == TC_BEGIN_LIST) lexer.fail("There is a start of the new array after the one parsed to sequence. " +
@@ -93,7 +96,7 @@
}
return false
}
- if (!lexer.isNotEof()) lexer.fail(TC_END_LIST)
+ if (!lexer.isNotEof() && !finished) lexer.fail(TC_END_LIST)
return true
}
}
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 93e604a..9128f3a 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt
@@ -1,6 +1,7 @@
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:OptIn(ExperimentalSerializationApi::class)
package kotlinx.serialization.json.internal
@@ -10,37 +11,82 @@
import kotlinx.serialization.json.*
import kotlin.native.concurrent.*
-@SharedImmutable
-internal val JsonAlternativeNamesKey = DescriptorSchemaCache.Key<Map<String, Int>>()
+internal val JsonDeserializationNamesKey = DescriptorSchemaCache.Key<Map<String, Int>>()
-@OptIn(ExperimentalSerializationApi::class)
-internal fun SerialDescriptor.buildAlternativeNamesMap(): Map<String, Int> {
+internal val JsonSerializationNamesKey = DescriptorSchemaCache.Key<Array<String>>()
+
+private fun SerialDescriptor.buildDeserializationNamesMap(json: Json): Map<String, Int> {
fun MutableMap<String, Int>.putOrThrow(name: String, index: Int) {
+ val entity = if (kind == SerialKind.ENUM) "enum value" else "property"
if (name in this) {
throw JsonException(
- "The suggested name '$name' for property ${getElementName(index)} is already one of the names for property " +
- "${getElementName(getValue(name))} in ${this@buildAlternativeNamesMap}"
+ "The suggested name '$name' for $entity ${getElementName(index)} is already one of the names for $entity " +
+ "${getElementName(getValue(name))} in ${this@buildDeserializationNamesMap}"
)
}
this[name] = index
}
- var builder: MutableMap<String, Int>? = null
+ val builder: MutableMap<String, Int> =
+ mutableMapOf() // can be not concurrent because it is only read after creation and safely published to concurrent map
+ val useLowercaseEnums = json.decodeCaseInsensitive(this)
+ val strategyForClasses = namingStrategy(json)
for (i in 0 until elementsCount) {
getElementAnnotations(i).filterIsInstance<JsonNames>().singleOrNull()?.names?.forEach { name ->
- if (builder == null) builder = createMapForCache(elementsCount)
- builder!!.putOrThrow(name, i)
+ builder.putOrThrow(if (useLowercaseEnums) name.lowercase() else name, i)
}
+ val nameToPut = when {
+ // the branches do not intersect because useLowercase = true for enums only, and strategy != null for classes only.
+ useLowercaseEnums -> getElementName(i).lowercase()
+ strategyForClasses != null -> strategyForClasses.serialNameForJson(this, i, getElementName(i))
+ else -> null
+ }
+ nameToPut?.let { builder.putOrThrow(it, i) }
}
- return builder ?: emptyMap()
+ return builder.ifEmpty { emptyMap() }
}
/**
- * Serves same purpose as [SerialDescriptor.getElementIndex] but respects
- * [JsonNames] annotation and [JsonConfiguration.useAlternativeNames] state.
+ * Contains strategy-mapped names and @JsonNames,
+ * so original names are not stored when strategy is `null`.
+ */
+internal fun Json.deserializationNamesMap(descriptor: SerialDescriptor): Map<String, Int> =
+ schemaCache.getOrPut(descriptor, JsonDeserializationNamesKey) { descriptor.buildDeserializationNamesMap(this) }
+
+internal fun SerialDescriptor.serializationNamesIndices(json: Json, strategy: JsonNamingStrategy): Array<String> =
+ json.schemaCache.getOrPut(this, JsonSerializationNamesKey) {
+ Array(elementsCount) { i ->
+ val baseName = getElementName(i)
+ strategy.serialNameForJson(this, i, baseName)
+ }
+ }
+
+internal fun SerialDescriptor.getJsonElementName(json: Json, index: Int): String {
+ val strategy = namingStrategy(json)
+ return if (strategy == null) getElementName(index) else serializationNamesIndices(json, strategy)[index]
+}
+
+internal fun SerialDescriptor.namingStrategy(json: Json) =
+ if (kind == StructureKind.CLASS) json.configuration.namingStrategy else null
+
+private fun SerialDescriptor.getJsonNameIndexSlowPath(json: Json, name: String): Int =
+ json.deserializationNamesMap(this)[name] ?: CompositeDecoder.UNKNOWN_NAME
+
+private fun Json.decodeCaseInsensitive(descriptor: SerialDescriptor) =
+ configuration.decodeEnumsCaseInsensitive && descriptor.kind == SerialKind.ENUM
+
+/**
+ * Serves same purpose as [SerialDescriptor.getElementIndex] but respects [JsonNames] annotation
+ * and [JsonConfiguration] settings.
*/
@OptIn(ExperimentalSerializationApi::class)
internal fun SerialDescriptor.getJsonNameIndex(json: Json, name: String): Int {
+ if (json.decodeCaseInsensitive(this)) {
+ return getJsonNameIndexSlowPath(json, name.lowercase())
+ }
+
+ val strategy = namingStrategy(json)
+ if (strategy != null) return getJsonNameIndexSlowPath(json, name)
val index = getElementIndex(name)
// Fast path, do not go through ConcurrentHashMap.get
// Note, it blocks ability to detect collisions between the primary name and alternate,
@@ -48,9 +94,7 @@
if (index != CompositeDecoder.UNKNOWN_NAME) return index
if (!json.configuration.useAlternativeNames) return index
// Slow path
- val alternativeNamesMap =
- json.schemaCache.getOrPut(this, JsonAlternativeNamesKey, this::buildAlternativeNamesMap)
- return alternativeNamesMap[name] ?: CompositeDecoder.UNKNOWN_NAME
+ return getJsonNameIndexSlowPath(json, name)
}
/**
@@ -66,15 +110,22 @@
@OptIn(ExperimentalSerializationApi::class)
internal inline fun Json.tryCoerceValue(
- elementDescriptor: SerialDescriptor,
- peekNull: () -> Boolean,
+ descriptor: SerialDescriptor,
+ index: Int,
+ peekNull: (consume: Boolean) -> Boolean,
peekString: () -> String?,
onEnumCoercing: () -> Unit = {}
): Boolean {
- if (!elementDescriptor.isNullable && peekNull()) return true
+ 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)) {
+ return false
+ }
+
val enumValue = peekString()
- ?: return false // if value is not a string, decodeEnum() will throw correct exception
+ ?: return false // if value is not a string, decodeEnum() will throw correct exception
val enumIndex = elementDescriptor.getJsonNameIndex(this, enumValue)
if (enumIndex == CompositeDecoder.UNKNOWN_NAME) {
onEnumCoercing()
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt
index 4e055b2..14e70a4 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt
@@ -24,7 +24,7 @@
// Tombstone indicates that we are within a map, but the map key is currently being decoded.
// It is also used to overwrite a previous map key to avoid memory leaks and misattribution.
- object Tombstone
+ private object Tombstone
/*
* Serial descriptor, map key or the tombstone for map key
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt
new file mode 100644
index 0000000..05f025c
--- /dev/null
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt
@@ -0,0 +1,70 @@
+package kotlinx.serialization.json.internal
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.DecodeSequenceMode
+import kotlinx.serialization.json.Json
+
+@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
+internal annotation class JsonFriendModuleApi
+
+@JsonFriendModuleApi
+public interface InternalJsonWriter {
+ public fun writeLong(value: Long)
+ public fun writeChar(char: Char)
+ public fun write(text: String)
+ public fun writeQuoted(text: String)
+ public fun release()
+}
+
+@JsonFriendModuleApi
+public interface InternalJsonReader {
+ public fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int
+}
+
+@JsonFriendModuleApi
+public fun <T> encodeByWriter(json: Json, writer: InternalJsonWriter, serializer: SerializationStrategy<T>, value: T) {
+ val encoder = StreamingJsonEncoder(
+ writer, json,
+ WriteMode.OBJ,
+ arrayOfNulls(WriteMode.entries.size)
+ )
+ encoder.encodeSerializableValue(serializer, value)
+}
+
+@JsonFriendModuleApi
+public fun <T> decodeByReader(
+ json: Json,
+ deserializer: DeserializationStrategy<T>,
+ reader: InternalJsonReader
+): T {
+ val lexer = ReaderJsonLexer(reader)
+ try {
+ val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
+ val result = input.decodeSerializableValue(deserializer)
+ lexer.expectEof()
+ return result
+ } finally {
+ lexer.release()
+ }
+}
+
+@JsonFriendModuleApi
+@ExperimentalSerializationApi
+public fun <T> decodeToSequenceByReader(
+ json: Json,
+ reader: InternalJsonReader,
+ deserializer: DeserializationStrategy<T>,
+ format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
+): Sequence<T> {
+ val lexer = ReaderJsonLexer(reader, CharArray(BATCH_SIZE)) // Unpooled buffer due to lazy nature of sequence
+ val iter = JsonIterator(format, json, lexer, deserializer)
+ return Sequence { iter }.constrainOnce()
+}
+
+@JsonFriendModuleApi
+@ExperimentalSerializationApi
+public inline fun <reified T> decodeToSequenceByReader(
+ json: Json,
+ reader: InternalJsonReader,
+ format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
+): Sequence<T> = decodeToSequenceByReader(json, reader, json.serializersModule.serializer(), format)
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
deleted file mode 100644
index f9245d5..0000000
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package kotlinx.serialization.json.internal
-
-internal expect class JsonStringBuilder constructor() {
- fun append(value: Long)
- fun append(ch: Char)
- fun append(string: String)
- fun appendQuoted(string: String)
- override fun toString(): String
- fun release()
-}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
new file mode 100644
index 0000000..a6a123c
--- /dev/null
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
@@ -0,0 +1,10 @@
+package kotlinx.serialization.json.internal
+
+internal expect class JsonToStringWriter constructor() : InternalJsonWriter {
+ override fun writeChar(char: Char)
+ override fun writeLong(value: Long)
+ override fun write(text: String)
+ override fun writeQuoted(text: String)
+ override fun toString(): String
+ override fun release()
+}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt
index 7c01daa..9cb9bb3 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt
@@ -13,6 +13,7 @@
private val lexer: AbstractJsonLexer
) {
private val isLenient = configuration.isLenient
+ private val trailingCommaAllowed = configuration.allowTrailingComma
private var stackDepth = 0
private fun readObject(): JsonElement = readObjectImpl {
@@ -44,8 +45,9 @@
if (lastToken == TC_BEGIN_OBJ) { // Case of empty object
lexer.consumeNextToken(TC_END_OBJ)
} else if (lastToken == TC_COMMA) { // Trailing comma
- lexer.fail("Unexpected trailing comma")
- }
+ if (!trailingCommaAllowed) lexer.invalidTrailingComma()
+ lexer.consumeNextToken(TC_END_OBJ)
+ } // else unexpected token?
return JsonObject(result)
}
@@ -66,7 +68,8 @@
if (lastToken == TC_BEGIN_LIST) { // Case of empty object
lexer.consumeNextToken(TC_END_LIST)
} else if (lastToken == TC_COMMA) { // Trailing comma
- lexer.fail("Unexpected trailing comma")
+ if (!trailingCommaAllowed) lexer.invalidTrailingComma("array")
+ lexer.consumeNextToken(TC_END_LIST)
}
return JsonArray(result)
}
@@ -101,7 +104,7 @@
result
}
TC_BEGIN_LIST -> readArray()
- else -> lexer.fail("Cannot begin reading element, unexpected token: $token")
+ else -> lexer.fail("Cannot read Json element because of unexpected ${tokenDescription(token)}")
}
}
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 ea65c48..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,8 @@
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
+import kotlinx.serialization.modules.*
+import kotlin.jvm.*
@Suppress("UNCHECKED_CAST")
internal inline fun <T> JsonEncoder.encodePolymorphically(
@@ -16,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
@@ -55,25 +72,22 @@
}
internal fun <T> JsonDecoder.decodeSerializableValuePolymorphic(deserializer: DeserializationStrategy<T>): T {
+ // NB: changes in this method should be reflected in StreamingJsonDecoder#decodeSerializableValue
if (deserializer !is AbstractPolymorphicSerializer<*> || json.configuration.useArrayPolymorphism) {
return deserializer.deserialize(this)
}
+ val discriminator = deserializer.descriptor.classDiscriminator(json)
val jsonTree = cast<JsonObject>(decodeJsonElement(), deserializer.descriptor)
- val discriminator = deserializer.descriptor.classDiscriminator(json)
- val type = jsonTree[discriminator]?.jsonPrimitive?.content
- val actualSerializer = deserializer.findPolymorphicSerializerOrNull(this, type)
- ?: throwSerializerNotFound(type, jsonTree)
-
+ val type = jsonTree[discriminator]?.jsonPrimitive?.contentOrNull // differentiate between `"type":"null"` and `"type":null`.
@Suppress("UNCHECKED_CAST")
- return json.readPolymorphicJson(discriminator, jsonTree, actualSerializer as DeserializationStrategy<T>)
-}
-
-private fun throwSerializerNotFound(type: String?, jsonTree: JsonObject): Nothing {
- val suffix =
- if (type == null) "missing class discriminator ('null')"
- else "class discriminator '$type'"
- throw JsonDecodingException(-1, "Polymorphic serializer was not found for $suffix", jsonTree.toString())
+ val actualSerializer =
+ try {
+ deserializer.findPolymorphicSerializer(this, type)
+ } catch (it: SerializationException) { // Wrap SerializationException into JsonDecodingException to preserve input
+ throw JsonDecodingException(-1, it.message!!, jsonTree.toString())
+ } as DeserializationStrategy<T>
+ return json.readPolymorphicJson(discriminator, jsonTree, actualSerializer)
}
internal fun SerialDescriptor.classDiscriminator(json: Json): String {
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt
index 01994f7..e4606fa 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt
@@ -83,7 +83,7 @@
override fun <Base : Any> polymorphicDefaultDeserializer(
baseClass: KClass<Base>,
- defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<out Base>?
+ defaultDeserializerProvider: (className: String?) -> DeserializationStrategy<Base>?
) {
// Nothing here
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/SchemaCache.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/SchemaCache.kt
index de65fb6..b514f6e 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/SchemaCache.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/SchemaCache.kt
@@ -16,11 +16,13 @@
* To be able to work with it from multiple threads in Kotlin/Native, use @[ThreadLocal] in appropriate places.
*/
internal class DescriptorSchemaCache {
- private val map: MutableMap<SerialDescriptor, DescriptorData<Any>> = createMapForCache(1)
+ // 16 is default CHM size, as we do not know number of descriptors in an application (but it's likely not 1)
+ private val map: MutableMap<SerialDescriptor, DescriptorData<Any>> = createMapForCache(16)
@Suppress("UNCHECKED_CAST")
public operator fun <T : Any> set(descriptor: SerialDescriptor, key: Key<T>, value: T) {
- map.getOrPut(descriptor, { createMapForCache(1) })[key as Key<Any>] = value as Any
+ // Initial capacity = number of known DescriptorSchemaCache.Key instances
+ map.getOrPut(descriptor, { createMapForCache(2) })[key as Key<Any>] = value as Any
}
public fun <T : Any> getOrPut(descriptor: SerialDescriptor, key: Key<T>, defaultValue: () -> T): T {
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 bf22904..caa1f4a 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt
@@ -9,6 +9,7 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.encoding.CompositeDecoder.Companion.DECODE_DONE
import kotlinx.serialization.encoding.CompositeDecoder.Companion.UNKNOWN_NAME
+import kotlinx.serialization.internal.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.jvm.*
@@ -16,28 +17,82 @@
/**
* [JsonDecoder] which reads given JSON from [AbstractJsonLexer] field by field.
*/
-@OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
+@OptIn(ExperimentalSerializationApi::class)
internal open class StreamingJsonDecoder(
final override val json: Json,
private val mode: WriteMode,
@JvmField internal val lexer: AbstractJsonLexer,
- descriptor: SerialDescriptor
-) : JsonDecoder, AbstractDecoder() {
+ descriptor: SerialDescriptor,
+ discriminatorHolder: DiscriminatorHolder?
+) : JsonDecoder, ChunkedDecoder, AbstractDecoder() {
+
+ // A mutable reference to the discriminator that have to be skipped when in optimistic phase
+ // of polymorphic serialization, see `decodeSerializableValue`
+ internal class DiscriminatorHolder(@JvmField var discriminatorToSkip: String?)
+
+ private fun DiscriminatorHolder?.trySkip(unknownKey: String): Boolean {
+ if (this == null) return false
+ if (discriminatorToSkip == unknownKey) {
+ discriminatorToSkip = null
+ return true
+ }
+ return false
+ }
+
override val serializersModule: SerializersModule = json.serializersModule
private var currentIndex = -1
+ private var discriminatorHolder: DiscriminatorHolder? = discriminatorHolder
private val configuration = json.configuration
private val elementMarker: JsonElementMarker? = if (configuration.explicitNulls) null else JsonElementMarker(descriptor)
override fun decodeJsonElement(): JsonElement = JsonTreeReader(json.configuration, lexer).read()
- @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
try {
- return decodeSerializableValuePolymorphic(deserializer)
+ /*
+ * This is an optimized path over decodeSerializableValuePolymorphic(deserializer):
+ * dSVP reads the very next JSON tree into a memory as JsonElement and then runs TreeJsonDecoder over it
+ * in order to deal with an arbitrary order of keys, but with the price of additional memory pressure
+ * and CPU consumption.
+ * We would like to provide the best possible performance for data produced by kotlinx.serialization
+ * itself, for that we do the following optimistic optimization:
+ *
+ * 0) Remember current position in the string
+ * 1) Read the very next key of JSON structure
+ * 2) If it matches* the discriminator key, read the value, remember current position
+ * 3) Return the value, recover an initial position
+ * (*) -- if it doesn't match, fallback to dSVP method.
+ */
+ if (deserializer !is AbstractPolymorphicSerializer<*> || json.configuration.useArrayPolymorphism) {
+ return deserializer.deserialize(this)
+ }
+
+ val discriminator = deserializer.descriptor.classDiscriminator(json)
+ val type = lexer.peekLeadingMatchingValue(discriminator, configuration.isLenient)
+ ?: // Fallback to slow path if we haven't found discriminator on first try
+ return decodeSerializableValuePolymorphic<T>(deserializer as DeserializationStrategy<T>)
+
+ @Suppress("UNCHECKED_CAST")
+ val actualSerializer = try {
+ deserializer.findPolymorphicSerializer(this, type)
+ } catch (it: SerializationException) { // Wrap SerializationException into JsonDecodingException to preserve position, path, and input.
+ // Split multiline message from private core function:
+ // core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt:102
+ val message = it.message!!.substringBefore('\n').removeSuffix(".")
+ val hint = it.message!!.substringAfter('\n', missingDelimiterValue = "")
+ lexer.fail(message, hint = hint)
+ } as DeserializationStrategy<T>
+
+ discriminatorHolder = DiscriminatorHolder(discriminator)
+ return actualSerializer.deserialize(this)
+
} catch (e: MissingFieldException) {
- throw MissingFieldException(e.message + " at path: " + lexer.path.getPath(), e)
+ // Add "at path" if and only if we've just caught an exception and it hasn't been augmented yet
+ if (e.message!!.contains("at path")) throw e
+ // NB: we could've use some additional flag marker or augment the stacktrace, but it seemed to be as too much of a burden
+ throw MissingFieldException(e.missingFields, e.message + " at path: " + lexer.path.getPath(), e)
}
}
@@ -52,23 +107,25 @@
json,
newMode,
lexer,
- descriptor
+ descriptor,
+ discriminatorHolder
)
else -> if (mode == newMode && json.configuration.explicitNulls) {
this
} else {
- StreamingJsonDecoder(json, newMode, lexer, descriptor)
+ StreamingJsonDecoder(json, newMode, lexer, descriptor, discriminatorHolder)
}
}
}
override fun endStructure(descriptor: SerialDescriptor) {
- // If we're ignoring unknown keys, we have to skip all undecoded elements,
+ // If we're ignoring unknown keys, we have to skip all un-decoded elements,
// e.g. for object serialization. It can be the case when the descriptor does
// not have any elements and decodeElementIndex is not invoked at all
if (json.configuration.ignoreUnknownKeys && descriptor.elementsCount == 0) {
skipLeftoverElements(descriptor)
}
+ if (lexer.tryConsumeComma() && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma("")
// First consume the object so we know it's correct
lexer.consumeNextToken(mode.end)
// Then cleanup the path
@@ -82,7 +139,7 @@
}
override fun decodeNotNullMark(): Boolean {
- return !(elementMarker?.isUnmarkedNull ?: false) && lexer.tryConsumeNotNull()
+ return !(elementMarker?.isUnmarkedNull ?: false) && !lexer.tryConsumeNull()
}
override fun decodeNull(): Nothing? {
@@ -142,12 +199,12 @@
return if (lexer.canConsumeValue()) {
if (decodingKey) {
- if (currentIndex == -1) lexer.require(!hasComma) { "Unexpected trailing comma" }
+ if (currentIndex == -1) lexer.require(!hasComma) { "Unexpected leading comma" }
else lexer.require(hasComma) { "Expected comma after the key-value pair" }
}
++currentIndex
} else {
- if (hasComma) lexer.fail("Expected '}', but had ',' instead")
+ if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma()
CompositeDecoder.DECODE_DONE
}
}
@@ -156,13 +213,12 @@
* 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),
- { !lexer.tryConsumeNotNull() },
+ descriptor, index,
+ { lexer.tryConsumeNull(it) },
{ lexer.peekString(configuration.isLenient) },
{ lexer.consumeString() /* skip unknown enum string*/ }
)
- @Suppress("INVISIBLE_MEMBER")
private fun decodeObjectIndex(descriptor: SerialDescriptor): Int {
// hasComma checks are required to properly react on trailing commas
var hasComma = lexer.tryConsumeComma()
@@ -187,17 +243,17 @@
hasComma = handleUnknown(key)
}
}
- if (hasComma) lexer.fail("Unexpected trailing comma")
+ if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma()
return elementMarker?.nextUnmarkedIndex() ?: CompositeDecoder.DECODE_DONE
}
private fun handleUnknown(key: String): Boolean {
- if (configuration.ignoreUnknownKeys) {
+ if (configuration.ignoreUnknownKeys || discriminatorHolder.trySkip(key)) {
lexer.skipElement(configuration.isLenient)
} else {
- // Here we cannot properly update json path indicies
- // as we do not have a proper SerialDecriptor in our hands
+ // Here we cannot properly update json path indices
+ // as we do not have a proper SerialDescriptor in our hands
lexer.failOnUnknownKey(key)
}
return lexer.tryConsumeComma()
@@ -210,28 +266,19 @@
if (currentIndex != -1 && !hasComma) lexer.fail("Expected end of the array or comma")
++currentIndex
} else {
- if (hasComma) lexer.fail("Unexpected trailing comma")
+ if (hasComma && !json.configuration.allowTrailingComma) lexer.invalidTrailingComma("array")
CompositeDecoder.DECODE_DONE
}
}
-
+ /*
+ * The primitives are allowed to be quoted and unquoted
+ * to simplify map key parsing and integrations with third-party API.
+ */
override fun decodeBoolean(): Boolean {
- /*
- * We prohibit non true/false boolean literals at all as it is considered way too error-prone,
- * but allow quoted literal in relaxed mode for booleans.
- */
- return if (configuration.isLenient) {
- lexer.consumeBooleanLenient()
- } else {
- lexer.consumeBoolean()
- }
+ return lexer.consumeBooleanLenient()
}
- /*
- * The rest of the primitives are allowed to be quoted and unquoted
- * to simplify integrations with third-party API.
- */
override fun decodeByte(): Byte {
val value = lexer.consumeNumericLiteral()
// Check for overflow
@@ -293,17 +340,33 @@
}
}
- override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder =
- if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(lexer, json)
- else super.decodeInline(inlineDescriptor)
+ override fun decodeStringChunked(consumeChunk: (chunk: String) -> Unit) {
+ lexer.consumeStringChunked(configuration.isLenient, consumeChunk)
+ }
+
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder =
+ if (descriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(lexer, json)
+ else super.decodeInline(descriptor)
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int {
return enumDescriptor.getJsonNameIndexOrThrow(json, decodeString(), " at path " + lexer.path.getPath())
}
}
+@JsonFriendModuleApi // used in json-tests
+public fun <T> decodeStringToJsonTree(
+ json: Json,
+ deserializer: DeserializationStrategy<T>,
+ source: String
+): JsonElement {
+ val lexer = StringJsonLexer(source)
+ val input = StreamingJsonDecoder(json, WriteMode.OBJ, lexer, deserializer.descriptor, null)
+ val tree = input.decodeJsonElement()
+ lexer.expectEof()
+ return tree
+}
+
@OptIn(ExperimentalSerializationApi::class)
-@ExperimentalUnsignedTypes
internal class JsonDecoderForUnsignedTypes(
private val lexer: AbstractJsonLexer,
json: Json
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 cdaeeb0..cf562de 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
package kotlinx.serialization.json.internal
@@ -10,11 +10,7 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
-import kotlin.native.concurrent.*
-@ExperimentalSerializationApi
-@OptIn(ExperimentalUnsignedTypes::class)
-@SharedImmutable
private val unsignedNumberDescriptors = setOf(
UInt.serializer().descriptor,
ULong.serializer().descriptor,
@@ -22,11 +18,13 @@
UShort.serializer().descriptor
)
-@ExperimentalSerializationApi
internal val SerialDescriptor.isUnsignedNumber: Boolean
get() = this.isInline && this in unsignedNumberDescriptors
-@OptIn(ExperimentalSerializationApi::class, ExperimentalUnsignedTypes::class)
+internal val SerialDescriptor.isUnquotedLiteral: Boolean
+ get() = this.isInline && this == jsonUnquotedLiteralDescriptor
+
+@OptIn(ExperimentalSerializationApi::class)
internal class StreamingJsonEncoder(
private val composer: Composer,
override val json: Json,
@@ -35,7 +33,7 @@
) : JsonEncoder, AbstractEncoder() {
internal constructor(
- output: JsonStringBuilder, json: Json, mode: WriteMode,
+ output: InternalJsonWriter, json: Json, mode: WriteMode,
modeReuseCache: Array<JsonEncoder?>
) : this(Composer(output, json), json, mode, modeReuseCache)
@@ -98,7 +96,7 @@
override fun endStructure(descriptor: SerialDescriptor) {
if (mode.end != INVALID) {
composer.unIndent()
- composer.nextItem()
+ composer.nextItemIfNotFirst()
composer.print(mode.end)
}
}
@@ -139,7 +137,7 @@
if (!composer.writingFirst)
composer.print(COMMA)
composer.nextItem()
- encodeString(descriptor.getElementName(index))
+ encodeString(descriptor.getJsonElementName(json, index))
composer.print(COLON)
composer.space()
}
@@ -158,11 +156,20 @@
}
}
- override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder =
- if (inlineDescriptor.isUnsignedNumber) StreamingJsonEncoder(
- ComposerForUnsignedNumbers(composer.sb), json, mode, null
- )
- else super.encodeInline(inlineDescriptor)
+ override fun encodeInline(descriptor: SerialDescriptor): Encoder =
+ when {
+ descriptor.isUnsignedNumber -> StreamingJsonEncoder(composerAs(::ComposerForUnsignedNumbers), json, mode, null)
+ descriptor.isUnquotedLiteral -> StreamingJsonEncoder(composerAs(::ComposerForUnquotedLiterals), json, mode, null)
+ else -> super.encodeInline(descriptor)
+ }
+
+ private inline fun <reified T: Composer> composerAs(composerCreator: (writer: InternalJsonWriter, forceQuoting: Boolean) -> T): T {
+ // If we're inside encodeInline().encodeSerializableValue, we should preserve the forceQuoting state
+ // inside the composer, but not in the encoder (otherwise we'll get into `if (forceQuoting) encodeString(value.toString())` part
+ // and unsigned numbers would be encoded incorrectly)
+ return if (composer is T) composer
+ else composerCreator(composer.writer, forceQuoting)
+ }
override fun encodeNull() {
composer.print(NULL)
@@ -192,7 +199,7 @@
// First encode value, then check, to have a prettier error message
if (forceQuoting) encodeString(value.toString()) else composer.print(value)
if (!configuration.allowSpecialFloatingPointValues && !value.isFinite()) {
- throw InvalidFloatingPointEncoded(value, composer.sb.toString())
+ throw InvalidFloatingPointEncoded(value, composer.writer.toString())
}
}
@@ -200,7 +207,7 @@
// First encode value, then check, to have a prettier error message
if (forceQuoting) encodeString(value.toString()) else composer.print(value)
if (!configuration.allowSpecialFloatingPointValues && !value.isFinite()) {
- throw InvalidFloatingPointEncoded(value, composer.sb.toString())
+ throw InvalidFloatingPointEncoded(value, composer.writer.toString())
}
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt
index 99aed33..ed76ba0 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt
@@ -12,7 +12,6 @@
else (d - 10 + 'a'.code).toChar()
}
-@SharedImmutable
internal val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
for (c in 0..0x1f) {
val c1 = toHexChar(c shr 12)
@@ -30,7 +29,6 @@
this[0x0c] = "\\f"
}
-@SharedImmutable
internal val ESCAPE_MARKERS: ByteArray = ByteArray(93).apply {
for (c in 0..0x1f) {
this[c] = 1.toByte()
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/SuppressAnimalSniffer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/SuppressAnimalSniffer.kt
new file mode 100644
index 0000000..1ca90e7
--- /dev/null
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/SuppressAnimalSniffer.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.json.internal
+
+/**
+ * Suppresses Animal Sniffer plugin errors for certain methods.
+ * Such methods include references to Java 8 methods that are not
+ * available in Android API, but can be desugared by R8.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+internal annotation class SuppressAnimalSniffer
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 55e23a1..690b35e 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt
@@ -15,11 +15,12 @@
import kotlinx.serialization.modules.*
import kotlin.jvm.*
-internal fun <T> Json.readJson(element: JsonElement, deserializer: DeserializationStrategy<T>): T {
+@JsonFriendModuleApi
+public fun <T> readJson(json: Json, element: JsonElement, deserializer: DeserializationStrategy<T>): T {
val input = when (element) {
- is JsonObject -> JsonTreeDecoder(this, element)
- is JsonArray -> JsonTreeListDecoder(this, element)
- is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(this, element as JsonPrimitive)
+ is JsonObject -> JsonTreeDecoder(json, element)
+ is JsonArray -> JsonTreeListDecoder(json, element)
+ is JsonLiteral, JsonNull -> JsonPrimitiveDecoder(json, element as JsonPrimitive)
}
return input.decodeSerializableValue(deserializer)
}
@@ -43,7 +44,7 @@
@JvmField
protected val configuration = json.configuration
- private fun currentObject() = currentTagOrNull?.let { currentElement(it) } ?: value
+ protected fun currentObject() = currentTagOrNull?.let { currentElement(it) } ?: value
override fun decodeJsonElement(): JsonElement = currentObject()
@@ -90,16 +91,7 @@
override fun decodeTaggedNotNullMark(tag: String): Boolean = currentElement(tag) !== JsonNull
override fun decodeTaggedBoolean(tag: String): Boolean {
- val value = getPrimitiveValue(tag)
- if (!json.configuration.isLenient) {
- val literal = value.asLiteral("boolean")
- if (literal.isString) throw JsonDecodingException(
- -1, "Boolean literal for key '$tag' should be unquoted.\n$lenientHint", currentObject().toString()
- )
- }
- return value.primitive("boolean") {
- booleanOrNull ?: throw IllegalArgumentException() /* Will be handled by 'primitive' */
- }
+ return getPrimitiveValue(tag).primitive("boolean", JsonPrimitive::booleanOrNull)
}
override fun decodeTaggedByte(tag: String) = getPrimitiveValue(tag).primitive("byte") {
@@ -133,7 +125,7 @@
override fun decodeTaggedChar(tag: String): Char = getPrimitiveValue(tag).primitive("char") { content.single() }
- private inline fun <T: Any> JsonPrimitive.primitive(primitive: String, block: JsonPrimitive.() -> T?): T {
+ private inline fun <T : Any> JsonPrimitive.primitive(primitive: String, block: JsonPrimitive.() -> T?): T {
try {
return block() ?: unparsedPrimitive(primitive)
} catch (e: IllegalArgumentException) {
@@ -142,7 +134,7 @@
}
private fun unparsedPrimitive(primitive: String): Nothing {
- throw JsonDecodingException(-1, "Failed to parse '$primitive'", currentObject().toString())
+ throw JsonDecodingException(-1, "Failed to parse literal as '$primitive' value", currentObject().toString())
}
override fun decodeTaggedString(tag: String): String {
@@ -158,16 +150,20 @@
}
private fun JsonPrimitive.asLiteral(type: String): JsonLiteral {
- return this as? JsonLiteral ?: throw JsonDecodingException(-1, "Unexpected 'null' when $type was expected")
+ return this as? JsonLiteral ?: throw JsonDecodingException(-1, "Unexpected 'null' literal when non-nullable $type was expected")
}
- @OptIn(ExperimentalUnsignedTypes::class)
override fun decodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Decoder =
if (inlineDescriptor.isUnsignedNumber) JsonDecoderForUnsignedTypes(StringJsonLexer(getPrimitiveValue(tag).content), json)
else super.decodeTaggedInline(tag, inlineDescriptor)
+
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder {
+ return if (currentTagOrNull != null) super.decodeInline(descriptor)
+ else JsonPrimitiveDecoder(json, value).decodeInline(descriptor)
+ }
}
-private class JsonPrimitiveDecoder(json: Json, override val value: JsonPrimitive) : AbstractJsonTreeDecoder(json, value) {
+private class JsonPrimitiveDecoder(json: Json, override val value: JsonElement) : AbstractJsonTreeDecoder(json, value) {
init {
pushTag(PRIMITIVE_TAG)
@@ -194,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 }
)
@@ -224,40 +220,55 @@
return !forceNull && super.decodeNotNullMark()
}
- override fun elementName(desc: SerialDescriptor, index: Int): String {
- val mainName = desc.getElementName(index)
- if (!configuration.useAlternativeNames) return mainName
- // Fast path, do not go through ConcurrentHashMap.get
- // Note, it blocks ability to detect collisions between the primary name and alternate,
- // but it eliminates a significant performance penalty (about -15% without this optimization)
- if (mainName in value.keys) return mainName
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
+ val strategy = descriptor.namingStrategy(json)
+ val baseName = descriptor.getElementName(index)
+ if (strategy == null) {
+ if (!configuration.useAlternativeNames) return baseName
+ // Fast path, do not go through ConcurrentHashMap.get
+ // Note, it blocks ability to detect collisions between the primary name and alternate,
+ // but it eliminates a significant performance penalty (about -15% without this optimization)
+ if (baseName in value.keys) return baseName
+ }
// Slow path
- val alternativeNamesMap =
- json.schemaCache.getOrPut(desc, JsonAlternativeNamesKey, desc::buildAlternativeNamesMap)
- val nameInObject = value.keys.find { alternativeNamesMap[it] == index }
- return nameInObject ?: mainName
+ val deserializationNamesMap = json.deserializationNamesMap(descriptor)
+ value.keys.find { deserializationNamesMap[it] == index }?.let {
+ return it
+ }
+
+ val fallbackName = strategy?.serialNameForJson(
+ descriptor,
+ index,
+ baseName
+ ) // Key not found exception should be thrown with transformed name, not original
+ return fallbackName ?: baseName
}
override fun currentElement(tag: String): JsonElement = value.getValue(tag)
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
- /*
- * For polymorphic serialization we'd like to avoid excessive decoder creating in
- * beginStructure to properly preserve 'polyDiscriminator' field and filter it out.
- */
- if (descriptor === polyDescriptor) return this
+ // polyDiscriminator needs to be preserved so the check for unknown keys
+ // in endStructure can filter polyDiscriminator out.
+ if (descriptor === polyDescriptor) {
+ return JsonTreeDecoder(
+ json, cast(currentObject(), polyDescriptor), polyDiscriminator, polyDescriptor
+ )
+ }
+
return super.beginStructure(descriptor)
}
override fun endStructure(descriptor: SerialDescriptor) {
if (configuration.ignoreUnknownKeys || descriptor.kind is PolymorphicKind) return
// Validate keys
+ val strategy = descriptor.namingStrategy(json)
+
@Suppress("DEPRECATION_ERROR")
- val names: Set<String> =
- if (!configuration.useAlternativeNames)
- descriptor.jsonCachedSerialNames()
- else
- descriptor.jsonCachedSerialNames() + json.schemaCache[descriptor, JsonAlternativeNamesKey]?.keys.orEmpty()
+ val names: Set<String> = when {
+ strategy == null && !configuration.useAlternativeNames -> descriptor.jsonCachedSerialNames()
+ strategy != null -> json.deserializationNamesMap(descriptor).keys
+ else -> descriptor.jsonCachedSerialNames() + json.schemaCache[descriptor, JsonDeserializationNamesKey]?.keys.orEmpty()
+ }
for (key in value.keys) {
if (key !in names && key != polyDiscriminator) {
@@ -272,7 +283,7 @@
private val size: Int = keys.size * 2
private var position = -1
- override fun elementName(desc: SerialDescriptor, index: Int): String {
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
val i = index / 2
return keys[i]
}
@@ -298,7 +309,7 @@
private val size = value.size
private var currentIndex = -1
- override fun elementName(desc: SerialDescriptor, index: Int): String = (index).toString()
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String = index.toString()
override fun currentElement(tag: String): JsonElement {
return value[tag.toInt()]
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
index 74f02bf..5e3c808 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@file:OptIn(ExperimentalSerializationApi::class)
@@ -14,9 +14,10 @@
import kotlin.collections.set
import kotlin.jvm.*
-internal fun <T> Json.writeJson(value: T, serializer: SerializationStrategy<T>): JsonElement {
+@JsonFriendModuleApi
+public fun <T> writeJson(json: Json, value: T, serializer: SerializationStrategy<T>): JsonElement {
lateinit var result: JsonElement
- val encoder = JsonTreeEncoder(this) { result = it }
+ val encoder = JsonTreeEncoder(json) { result = it }
encoder.encodeSerializableValue(serializer, value)
return result
}
@@ -24,7 +25,7 @@
@ExperimentalSerializationApi
private sealed class AbstractJsonTreeEncoder(
final override val json: Json,
- private val nodeConsumer: (JsonElement) -> Unit
+ protected val nodeConsumer: (JsonElement) -> Unit
) : NamedValueEncoder(), JsonEncoder {
final override val serializersModule: SerializersModule
@@ -35,6 +36,9 @@
private var polymorphicDiscriminator: String? = null
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String =
+ descriptor.getJsonElementName(json, index)
+
override fun encodeJsonElement(element: JsonElement) {
encodeSerializableValue(JsonElementSerializer, element)
}
@@ -46,7 +50,10 @@
abstract fun putElement(key: String, element: JsonElement)
abstract fun getCurrent(): JsonElement
+ // has no tag when encoding a nullable element at root level
+ override fun encodeNotNullMark() {}
+ // has no tag when encoding a nullable element at root level
override fun encodeNull() {
val tag = currentTagOrNull ?: return nodeConsumer(JsonNull)
encodeTaggedNull(tag)
@@ -73,7 +80,6 @@
encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
encodeSerializableValue(serializer, value)
- endEncode(serializer.descriptor)
}
}
@@ -98,9 +104,20 @@
putElement(tag, JsonPrimitive(value.toString()))
}
- @OptIn(ExperimentalUnsignedTypes::class)
override fun encodeTaggedInline(tag: String, inlineDescriptor: SerialDescriptor): Encoder =
- if (inlineDescriptor.isUnsignedNumber) object : AbstractEncoder() {
+ when {
+ inlineDescriptor.isUnsignedNumber -> inlineUnsignedNumberEncoder(tag)
+ inlineDescriptor.isUnquotedLiteral -> inlineUnquotedLiteralEncoder(tag, inlineDescriptor)
+ else -> super.encodeTaggedInline(tag, inlineDescriptor)
+ }
+
+ override fun encodeInline(descriptor: SerialDescriptor): Encoder {
+ return if (currentTagOrNull != null) super.encodeInline(descriptor)
+ else JsonPrimitiveEncoder(json, nodeConsumer).encodeInline(descriptor)
+ }
+
+ @SuppressAnimalSniffer // Long(Integer).toUnsignedString(long)
+ private fun inlineUnsignedNumberEncoder(tag: String) = object : AbstractEncoder() {
override val serializersModule: SerializersModule = json.serializersModule
fun putUnquotedString(s: String) = putElement(tag, JsonLiteral(s, isString = false))
@@ -109,7 +126,12 @@
override fun encodeByte(value: Byte) = putUnquotedString(value.toUByte().toString())
override fun encodeShort(value: Short) = putUnquotedString(value.toUShort().toString())
}
- else super.encodeTaggedInline(tag, inlineDescriptor)
+
+ private fun inlineUnquotedLiteralEncoder(tag: String, inlineDescriptor: SerialDescriptor) = object : AbstractEncoder() {
+ override val serializersModule: SerializersModule get() = json.serializersModule
+
+ override fun encodeString(value: String) = putElement(tag, JsonLiteral(value, isString = false, coerceToInlineType = inlineDescriptor))
+ }
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
val consumer =
@@ -158,6 +180,7 @@
require(key === PRIMITIVE_TAG) { "This output can only consume primitives with '$PRIMITIVE_TAG' tag" }
require(content == null) { "Primitive element was already recorded. Does call to .encodeXxx happen more than once?" }
content = element
+ nodeConsumer(element)
}
override fun getCurrent(): JsonElement =
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 5e1eee7..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
@@ -4,14 +4,14 @@
package kotlinx.serialization.json.internal
-import kotlinx.serialization.json.internal.*
import kotlinx.serialization.json.internal.CharMappings.CHAR_TO_TOKEN
import kotlinx.serialization.json.internal.CharMappings.ESCAPE_2_CHAR
import kotlin.js.*
import kotlin.jvm.*
+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 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 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."
@@ -56,6 +56,20 @@
internal const val asciiCaseMask = 1 shl 5
+internal fun tokenDescription(token: Byte) = when (token) {
+ TC_STRING -> "quotation mark '\"'"
+ TC_STRING_ESC -> "string escape sequence '\\'"
+ TC_COMMA -> "comma ','"
+ TC_COLON -> "colon ':'"
+ TC_BEGIN_OBJ -> "start of the object '{'"
+ TC_END_OBJ -> "end of the object '}'"
+ TC_BEGIN_LIST -> "start of the array '['"
+ TC_END_LIST -> "end of the array ']'"
+ TC_EOF -> "end of the input"
+ TC_INVALID -> "invalid token"
+ else -> "valid token" // should never happen
+}
+
// object instead of @SharedImmutable because there is mutual initialization in [initC2ESC] and [initC2TC]
internal object CharMappings {
@JvmField
@@ -135,7 +149,7 @@
protected abstract val source: CharSequence
@JvmField
- protected var currentPosition: Int = 0 // position in source
+ internal var currentPosition: Int = 0 // position in source
@JvmField
val path = JsonPath()
@@ -200,28 +214,23 @@
}
protected fun unexpectedToken(expected: Char) {
- --currentPosition // To properly handle null
- if (currentPosition >= 0 && expected == STRING && consumeStringLenient() == NULL) {
- fail("Expected string literal but 'null' literal was found", currentPosition - 4, coerceInputValuesHint)
+ if (currentPosition > 0 && expected == STRING) {
+ val inputLiteral = withPositionRollback {
+ currentPosition--
+ consumeStringLenient()
+ }
+ if (inputLiteral == NULL)
+ fail("Expected string literal but 'null' literal was found", currentPosition - 1, coerceInputValuesHint)
}
fail(charToTokenClass(expected))
}
- internal fun fail(expectedToken: Byte): Nothing {
- // We know that the token was consumed prior to this call
+ internal fun fail(expectedToken: Byte, wasConsumed: Boolean = true): Nothing {
// Slow path, never called in normal code, can avoid optimizing it
- val expected = when (expectedToken) {
- TC_STRING -> "quotation mark '\"'"
- TC_COMMA -> "comma ','"
- TC_COLON -> "semicolon ':'"
- TC_BEGIN_OBJ -> "start of the object '{'"
- TC_END_OBJ -> "end of the object '}'"
- TC_BEGIN_LIST -> "start of the array '['"
- TC_END_LIST -> "end of the array ']'"
- else -> "valid token" // should never happen
- }
- val s = if (currentPosition == source.length || currentPosition <= 0) "EOF" else source[currentPosition - 1].toString()
- fail("Expected $expected, but had '$s' instead", currentPosition - 1)
+ val expected = tokenDescription(expectedToken)
+ val position = if (wasConsumed) currentPosition - 1 else currentPosition
+ val s = if (currentPosition == source.length || position < 0) "EOF" else source[position].toString()
+ fail("Expected $expected, but had '$s' instead", position)
}
fun peekNextToken(): Byte {
@@ -244,25 +253,28 @@
/**
* Tries to consume `null` token from input.
- * Returns `true` if the next 4 chars in input are not `null`,
- * `false` otherwise and consumes it.
+ * Returns `false` if the next 4 chars in input are not `null`,
+ * `true` otherwise and consumes it if [doConsume] is `true`.
*/
- fun tryConsumeNotNull(): Boolean {
+ fun tryConsumeNull(doConsume: Boolean = true): Boolean {
var current = skipWhitespaces()
current = prefetchOrEof(current)
// Cannot consume null due to EOF, maybe something else
val len = source.length - current
- if (len < 4 || current == -1) return true
+ if (len < 4 || current == -1) return false
for (i in 0..3) {
- if (NULL[i] != source[current + i]) return true
+ if (NULL[i] != source[current + i]) return false
}
/*
* If we're in lenient mode, this might be the string with 'null' prefix,
* distinguish it from 'null'
*/
- if (len > 4 && charToTokenClass(source[current + 4]) == TC_OTHER) return true
- currentPosition = current + 4
- return false
+ if (len > 4 && charToTokenClass(source[current + 4]) == TC_OTHER) return false
+
+ if (doConsume) {
+ currentPosition = current + 4
+ }
+ return true
}
open fun skipWhitespaces(): Int {
@@ -283,6 +295,8 @@
return current
}
+ abstract fun peekLeadingMatchingValue(keyToMatch: String, isLenient: Boolean): String?
+
fun peekString(isLenient: Boolean): String? {
val token = peekNextToken()
val string = if (isLenient) {
@@ -296,6 +310,10 @@
return string
}
+ fun discardPeeked() {
+ peekedString = null
+ }
+
open fun indexOf(char: Char, startPos: Int) = source.indexOf(char, startPos)
open fun substring(startPos: Int, endPos: Int) = source.substring(startPos, endPos)
@@ -305,6 +323,58 @@
*/
abstract fun consumeKeyString(): String
+ private fun insideString(isLenient: Boolean, char: Char): Boolean = if (isLenient) {
+ charToTokenClass(char) == TC_OTHER
+ } else {
+ char != STRING
+ }
+
+ open fun consumeStringChunked(isLenient: Boolean, consumeChunk: (stringChunk: String) -> Unit) { // open to allow simpler implementations (i.e. StringJsonLexer)
+ val nextToken = peekNextToken()
+ if (isLenient && nextToken != TC_OTHER) return // noting to consume
+
+ if (!isLenient) {
+ consumeNextToken(STRING)
+ }
+ var currentPosition = this.currentPosition
+ var lastPosition = currentPosition
+ var char = source[currentPosition] // Avoid two range checks visible in the profiler
+ var usedAppend = false
+ while (insideString(isLenient, char)) {
+ if (!isLenient && char == STRING_ESC) { // handle escaping only in non-lenient mode
+ usedAppend = true
+ currentPosition = prefetchOrEof(appendEscape(lastPosition, currentPosition))
+ lastPosition = currentPosition
+ } else {
+ currentPosition++
+ }
+ if (currentPosition >= source.length) {
+ // end of chunk
+ writeRange(lastPosition, currentPosition, usedAppend, consumeChunk)
+ usedAppend = false
+ currentPosition = prefetchOrEof(currentPosition)
+ if (currentPosition == -1)
+ fail("EOF", currentPosition)
+ lastPosition = currentPosition
+ }
+ char = source[currentPosition]
+ }
+ writeRange(lastPosition, currentPosition, usedAppend, consumeChunk)
+ this.currentPosition = currentPosition
+ if (!isLenient) {
+ consumeNextToken(STRING)
+ }
+ }
+
+ private fun writeRange(fromIndex: Int, toIndex: Int, currentChunkHasEscape: Boolean, consumeChunk: (stringChunk: String) -> Unit) {
+ if (currentChunkHasEscape) {
+ consumeChunk(decodedString(fromIndex, toIndex))
+ } else {
+ consumeChunk(substring(fromIndex, toIndex))
+ }
+ }
+
+
fun consumeString(): String {
if (peekedString != null) {
return takePeeked()
@@ -324,7 +394,7 @@
usedAppend = true
currentPosition = prefetchOrEof(appendEscape(lastPosition, currentPosition))
if (currentPosition == -1)
- fail("EOF", currentPosition)
+ fail("Unexpected EOF", currentPosition)
lastPosition = currentPosition
} else if (++currentPosition >= source.length) {
usedAppend = true
@@ -332,7 +402,7 @@
appendRange(lastPosition, currentPosition)
currentPosition = prefetchOrEof(currentPosition)
if (currentPosition == -1)
- fail("EOF", currentPosition)
+ fail("Unexpected EOF", currentPosition)
lastPosition = currentPosition
}
char = source[currentPosition]
@@ -545,11 +615,32 @@
false
}
var accumulator = 0L
+ var exponentAccumulator = 0L
var isNegative = false
+ var isExponentPositive = false
+ var hasExponent = false
val start = current
- var hasChars = true
- while (hasChars) {
+ while (current != source.length) {
val ch: Char = source[current]
+ if ((ch == 'e' || ch == 'E') && !hasExponent) {
+ if (current == start) fail("Unexpected symbol $ch in numeric literal")
+ isExponentPositive = true
+ hasExponent = true
+ ++current
+ continue
+ }
+ if (ch == '-' && hasExponent) {
+ if (current == start) fail("Unexpected symbol '-' in numeric literal")
+ isExponentPositive = false
+ ++current
+ continue
+ }
+ if (ch == '+' && hasExponent) {
+ if (current == start) fail("Unexpected symbol '+' in numeric literal")
+ isExponentPositive = true
+ ++current
+ continue
+ }
if (ch == '-') {
if (current != start) fail("Unexpected symbol '-' in numeric literal")
isNegative = true
@@ -559,12 +650,16 @@
val token = charToTokenClass(ch)
if (token != TC_OTHER) break
++current
- hasChars = current != source.length
val digit = ch - '0'
if (digit !in 0..9) fail("Unexpected symbol '$ch' in numeric literal")
+ if (hasExponent) {
+ exponentAccumulator = exponentAccumulator * 10 + digit
+ continue
+ }
accumulator = accumulator * 10 - digit
if (accumulator > 0) fail("Numeric value overflow")
}
+ val hasChars = current != start
if (start == current || (isNegative && start == current - 1)) {
fail("Expected numeric literal")
}
@@ -574,6 +669,19 @@
++current
}
currentPosition = current
+
+ fun calculateExponent(exponentAccumulator: Long, isExponentPositive: Boolean): Double = when (isExponentPositive) {
+ false -> 10.0.pow(-exponentAccumulator.toDouble())
+ true -> 10.0.pow(exponentAccumulator.toDouble())
+ }
+
+ if (hasExponent) {
+ val doubleAccumulator = accumulator.toDouble() * calculateExponent(exponentAccumulator, isExponentPositive)
+ if (doubleAccumulator > Long.MAX_VALUE || doubleAccumulator < Long.MIN_VALUE) fail("Numeric value overflow")
+ if (floor(doubleAccumulator) != doubleAccumulator) fail("Can't convert $doubleAccumulator to Long")
+ accumulator = doubleAccumulator.toLong()
+ }
+
return when {
isNegative -> accumulator
accumulator != Long.MIN_VALUE -> -accumulator
@@ -644,4 +752,13 @@
currentPosition = current + literalSuffix.length
}
+
+ private inline fun <T> withPositionRollback(action: () -> T): T {
+ val snapshot = currentPosition
+ try {
+ return action()
+ } finally {
+ currentPosition = snapshot
+ }
+ }
}
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonLexerJvm.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt
similarity index 66%
rename from formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonLexerJvm.kt
rename to formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt
index eabfd08..24e5b47 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonLexerJvm.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt
@@ -4,41 +4,41 @@
package kotlinx.serialization.json.internal
-import java.io.*
-import java.nio.charset.Charset
-
-internal const val BATCH_SIZE = 16 * 1024
+internal const val BATCH_SIZE: Int = 16 * 1024
private const val DEFAULT_THRESHOLD = 128
-
-// This size of buffered reader is very important here, because utf-8 decoding is slow.
-// Jackson and Moshi are faster because they have specialized UTF-8 parser directly over InputStream
-internal const val READER_BUF_SIZE = 16 * BATCH_SIZE
-
-
/**
* For some reason this hand-rolled implementation is faster than
* fun ArrayAsSequence(s: CharArray): CharSequence = java.nio.CharBuffer.wrap(s, 0, length)
*/
-private class ArrayAsSequence(private val source: CharArray) : CharSequence {
- override val length: Int = source.size
+internal class ArrayAsSequence(internal val buffer: CharArray) : CharSequence {
+ override var length: Int = buffer.size
- override fun get(index: Int): Char = source[index]
+ override fun get(index: Int): Char = buffer[index]
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
- return String(source, startIndex, endIndex - startIndex)
+ return buffer.concatToString(startIndex, minOf(endIndex, length))
}
+
+ fun substring(startIndex: Int, endIndex: Int): String {
+ return buffer.concatToString(startIndex, minOf(endIndex, length))
+ }
+
+ fun trim(newSize: Int) {
+ length = minOf(buffer.size, newSize)
+ }
+
+ // source.toString() is used in JsonDecodingException
+ override fun toString(): String = substring(0, length)
}
internal class ReaderJsonLexer(
- private val reader: Reader,
- private var _source: CharArray = CharArray(BATCH_SIZE)
+ private val reader: InternalJsonReader,
+ private val buffer: CharArray = CharArrayPoolBatchSize.take()
) : AbstractJsonLexer() {
private var threshold: Int = DEFAULT_THRESHOLD // chars
- constructor(i: InputStream, charset: Charset = Charsets.UTF_8) : this(i.reader(charset).buffered(READER_BUF_SIZE))
-
- override var source: CharSequence = ArrayAsSequence(_source)
+ override val source: ArrayAsSequence = ArrayAsSequence(buffer)
init {
preload(0)
@@ -73,22 +73,22 @@
return false
}
- private fun preload(spaceLeft: Int) {
- val buffer = _source
- System.arraycopy(buffer, currentPosition, buffer, 0, spaceLeft)
- var read = spaceLeft
- val sizeTotal = _source.size
- while (read != sizeTotal) {
- val actual = reader.read(buffer, read, sizeTotal - read)
+ private fun preload(unprocessedCount: Int) {
+ val buffer = source.buffer
+ if (unprocessedCount != 0) {
+ buffer.copyInto(buffer, 0, currentPosition, currentPosition + unprocessedCount)
+ }
+ var filledCount = unprocessedCount
+ val sizeTotal = source.length
+ while (filledCount != sizeTotal) {
+ val actual = reader.read(buffer, filledCount, sizeTotal - filledCount)
if (actual == -1) {
// EOF, resizing the array so it matches input size
- // Can also be done by extracting source.length to a separate var
- _source = _source.copyOf(read)
- source = ArrayAsSequence(_source)
+ source.trim(filledCount)
threshold = -1
break
}
- read += actual
+ filledCount += actual
}
currentPosition = 0
}
@@ -123,7 +123,7 @@
override fun ensureHaveChars() {
val cur = currentPosition
- val oldSize = _source.size
+ val oldSize = source.length
val spaceLeft = oldSize - cur
if (spaceLeft > threshold) return
// warning: current position is not updated during string consumption
@@ -133,10 +133,10 @@
override fun consumeKeyString(): String {
/*
- * For strings we assume that escaped symbols are rather an exception, so firstly
- * we optimistically scan for closing quote via intrinsified and blazing-fast 'indexOf',
- * than do our pessimistic check for backslash and fallback to slow-path if necessary.
- */
+ * For strings we assume that escaped symbols are rather an exception, so firstly
+ * we optimistically scan for closing quote via intrinsified and blazing-fast 'indexOf',
+ * than do our pessimistic check for backslash and fallback to slow-path if necessary.
+ */
consumeNextToken(STRING)
var current = currentPosition
val closingQuote = indexOf('"', current)
@@ -160,18 +160,25 @@
}
override fun indexOf(char: Char, startPos: Int): Int {
- val src = _source
- for (i in startPos until src.size) {
+ val src = source
+ for (i in startPos until src.length) {
if (src[i] == char) return i
}
return -1
}
override fun substring(startPos: Int, endPos: Int): String {
- return String(_source, startPos, endPos - startPos)
+ return source.substring(startPos, endPos)
}
override fun appendRange(fromIndex: Int, toIndex: Int) {
- escapedString.append(_source, fromIndex, toIndex - fromIndex)
+ escapedString.appendRange(source.buffer, fromIndex, toIndex)
+ }
+
+ // Can be carefully implemented but postponed for now
+ override fun peekLeadingMatchingValue(keyToMatch: String, isLenient: Boolean): String? = null
+
+ fun release() {
+ CharArrayPoolBatchSize.release(buffer)
}
}
diff --git a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/StringJsonLexer.kt b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/StringJsonLexer.kt
index 0ff980a..9f2e519 100644
--- a/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/StringJsonLexer.kt
+++ b/formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/StringJsonLexer.kt
@@ -73,19 +73,25 @@
if (c == expected) return
unexpectedToken(expected)
}
+ currentPosition = -1 // for correct EOF reporting
unexpectedToken(expected) // EOF
}
override fun consumeKeyString(): String {
/*
- * For strings we assume that escaped symbols are rather an exception, so firstly
- * we optimistically scan for closing quote via intrinsified and blazing-fast 'indexOf',
- * than do our pessimistic check for backslash and fallback to slow-path if necessary.
- */
+ * For strings we assume that escaped symbols are rather an exception, so firstly
+ * we optimistically scan for closing quote via intrinsified and blazing-fast 'indexOf',
+ * than do our pessimistic check for backslash and fallback to slow-path if necessary.
+ */
consumeNextToken(STRING)
val current = currentPosition
val closingQuote = source.indexOf('"', current)
- if (closingQuote == -1) fail(TC_STRING)
+ if (closingQuote == -1) {
+ // advance currentPosition to a token after the end of the string to guess position in the error msg
+ // (not always correct, as `:`/`,` are valid contents of the string, but good guess anyway)
+ consumeStringLenient()
+ fail(TC_STRING, wasConsumed = false)
+ }
// Now we _optimistically_ know where the string ends (it might have been an escaped quote)
for (i in current until closingQuote) {
// Encountered escape sequence, should fallback to "slow" path and symbolic scanning
@@ -96,4 +102,25 @@
this.currentPosition = closingQuote + 1
return source.substring(current, closingQuote)
}
+
+ override fun consumeStringChunked(isLenient: Boolean, consumeChunk: (stringChunk: String) -> Unit) {
+ (if (isLenient) consumeStringLenient() else consumeString()).chunked(BATCH_SIZE).forEach(consumeChunk)
+ }
+
+ override fun peekLeadingMatchingValue(keyToMatch: String, isLenient: Boolean): String? {
+ val positionSnapshot = currentPosition
+ try {
+ if (consumeNextToken() != TC_BEGIN_OBJ) return null // Malformed JSON, bailout
+ val firstKey = peekString(isLenient)
+ if (firstKey != keyToMatch) return null
+ discardPeeked() // consume firstKey
+ if (consumeNextToken() != TC_COLON) return null
+ return peekString(isLenient)
+ } finally {
+ // Restore the position
+ currentPosition = positionSnapshot
+ discardPeeked()
+ }
+ }
}
+
diff --git a/formats/json/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt b/formats/json/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt
deleted file mode 100644
index 26d6728..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-@file:Suppress("INLINE_CLASSES_NOT_SUPPORTED", "SERIALIZER_NOT_FOUND")
-@file:OptIn(ExperimentalUnsignedTypes::class)
-/*
- * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.serialization.features.inline
-
-import kotlinx.serialization.*
-import kotlinx.serialization.json.*
-import kotlin.test.*
-
-class UnsignedIntegersTest : JsonTestBase() {
- @Serializable
- data class AllUnsigned(
- val uInt: UInt,
- val uLong: ULong,
- val uByte: UByte,
- val uShort: UShort,
- val signedInt: Int,
- val signedLong: Long,
- val double: Double
- )
-
- @Serializable
- data class UnsignedWithoutLong(val uInt: UInt, val uByte: UByte, val uShort: UShort)
-
- @Test
- fun testUnsignedIntegersJson() {
- val data = AllUnsigned(
- Int.MAX_VALUE.toUInt() + 10.toUInt(),
- Long.MAX_VALUE.toULong() + 10.toULong(),
- 239.toUByte(),
- 65000.toUShort(),
- -42,
- Long.MIN_VALUE,
- 1.1
- )
- assertJsonFormAndRestored(
- AllUnsigned.serializer(),
- data,
- """{"uInt":2147483657,"uLong":9223372036854775817,"uByte":239,"uShort":65000,"signedInt":-42,"signedLong":-9223372036854775808,"double":1.1}""",
- )
- }
-
- @Test
- fun testUnsignedIntegersWithoutLongJson() {
- val data = UnsignedWithoutLong(
- Int.MAX_VALUE.toUInt() + 10.toUInt(),
- 239.toUByte(),
- 65000.toUShort(),
- )
- assertJsonFormAndRestored(
- UnsignedWithoutLong.serializer(),
- data,
- """{"uInt":2147483657,"uByte":239,"uShort":65000}""",
- )
- }
-}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
deleted file mode 100644
index dee87fd..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
+++ /dev/null
@@ -1,45 +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
-
-import kotlinx.serialization.*
-import kotlin.test.*
-
-class BasicTypesSerializationTest : JsonTestBase() {
-
- val goldenValue = """
- {"unit":{},"boolean":true,"byte":10,"short":20,"int":30,"long":40,"float":50.1,"double":60.1,"char":"A","string":"Str0","enum":"POSITIVE","intData":{"intV":70},"unitN":null,"booleanN":null,"byteN":11,"shortN":21,"intN":31,"longN":41,"floatN":51.1,"doubleN":61.1,"charN":"B","stringN":"Str1","enumN":"NEUTRAL","intDataN":null,"listInt":[1,2,3],"listIntN":[4,5,null],"listNInt":[6,7,8],"listNIntN":[null,9,10],"listListEnumN":[["NEGATIVE",null]],"listIntData":[{"intV":1},{"intV":2},{"intV":3}],"listIntDataN":[{"intV":1},null,{"intV":3}],"tree":{"name":"root","left":{"name":"left","left":null,"right":null},"right":{"name":"right","left":{"name":"right.left","left":null,"right":null},"right":{"name":"right.right","left":null,"right":null}}},"mapStringInt":{"one":1,"two":2,"three":3},"mapIntStringN":{"0":null,"1":"first","2":"second"},"arrays":{"arrByte":[1,2,3],"arrInt":[100,200,300],"arrIntN":[null,-1,-2],"arrIntData":[{"intV":1},{"intV":2}]}}
- """.trimIndent()
-
- @Test
- fun testSerialization() = parametrizedTest { jsonTestingMode ->
- val json = default.encodeToString(TypesUmbrella.serializer(), umbrellaInstance)
- assertEquals(goldenValue, json)
- val instance = default.decodeFromString(TypesUmbrella.serializer(), json, jsonTestingMode)
- assertEquals(umbrellaInstance, instance)
- assertNotSame(umbrellaInstance, instance)
- }
-
- @Test
- fun testTopLevelPrimitive() = parametrizedTest { jsonTestingMode ->
- testPrimitive(Unit, "{}", jsonTestingMode)
- testPrimitive(false, "false", jsonTestingMode)
- testPrimitive(1.toByte(), "1", jsonTestingMode)
- testPrimitive(2.toShort(), "2", jsonTestingMode)
- testPrimitive(3, "3", jsonTestingMode)
- testPrimitive(4L, "4", jsonTestingMode)
- testPrimitive(5.1f, "5.1", jsonTestingMode)
- testPrimitive(6.1, "6.1", jsonTestingMode)
- testPrimitive('c', "\"c\"", jsonTestingMode)
- testPrimitive("string", "\"string\"", jsonTestingMode)
- }
-
- private inline fun <reified T : Any> testPrimitive(primitive: T, expectedJson: String, jsonTestingMode: JsonTestingMode) {
- val json = default.encodeToString(primitive, jsonTestingMode)
- assertEquals(expectedJson, json)
- val instance = default.decodeFromString<T>(json, jsonTestingMode)
- assertEquals(primitive, instance)
- }
-}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt
deleted file mode 100644
index 2624821..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt
+++ /dev/null
@@ -1,44 +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
-
-import kotlin.test.*
-
-class JsonBuildersTest {
-
- @Test
- fun testBuildJson() {
- val json = buildJsonObject {
- putJsonObject("object") {
- put("k", JsonPrimitive("v"))
- }
-
- putJsonArray("array") {
- addJsonObject { put("nestedLiteral", true) }
- }
-
- val number: Number? = null
- put("null", number)
- put("primitive", JsonPrimitive(42))
- put("boolean", true)
- put("literal", "foo")
- }
- assertEquals("""{"object":{"k":"v"},"array":[{"nestedLiteral":true}],"null":null,"primitive":42,"boolean":true,"literal":"foo"}""", json.toString())
- }
-
- @Test
- fun testBuildJsonArray() {
- val json = buildJsonArray {
- add(true)
- addJsonArray {
- for (i in 1..10) add(i)
- }
- addJsonObject {
- put("stringKey", "stringValue")
- }
- }
- assertEquals("""[true,[1,2,3,4,5,6,7,8,9,10],{"stringKey":"stringValue"}]""", json.toString())
- }
-}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
deleted file mode 100644
index d0b105a..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt
+++ /dev/null
@@ -1,54 +0,0 @@
-package kotlinx.serialization.json
-
-import kotlinx.serialization.Serializable
-import kotlin.test.*
-
-class JsonElementDecodingTest : JsonTestBase() {
-
- @Serializable
- data class A(val a: Int = 42)
-
- @Test
- fun testTopLevelClass() = assertSerializedForm(A(), """{}""".trimMargin())
-
- @Test
- fun testTopLevelNullableClass() {
- assertSerializedForm<A?>(A(), """{}""")
- assertSerializedForm<A?>(null, "null")
- }
-
- @Test
- fun testTopLevelPrimitive() = assertSerializedForm(42, """42""")
-
- @Test
- fun testTopLevelNullablePrimitive() {
- assertSerializedForm<Int?>(42, """42""")
- assertSerializedForm<Int?>(null, """null""")
- }
-
- @Test
- fun testTopLevelList() = assertSerializedForm(listOf(42), """[42]""")
-
- @Test
- fun testTopLevelNullableList() {
- assertSerializedForm<List<Int>?>(listOf(42), """[42]""")
- assertSerializedForm<List<Int>?>(null, """null""")
- }
-
- private inline fun <reified T> assertSerializedForm(value: T, expectedString: String) {
- val element = Json.encodeToJsonElement(value)
- assertEquals(expectedString, element.toString())
- assertEquals(value, Json.decodeFromJsonElement(element))
- }
-
- @Test
- fun testDeepRecursion() {
- // Reported as https://github.com/Kotlin/kotlinx.serialization/issues/1594
- var json = """{ "a": %}"""
- for (i in 0..12) {
- json = json.replace("%", json)
- }
- json = json.replace("%", "0")
- Json.parseToJsonElement(json)
- }
-}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
deleted file mode 100644
index 87b0f35..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
+++ /dev/null
@@ -1,161 +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
-
-import kotlinx.serialization.*
-import kotlinx.serialization.json.internal.*
-import kotlinx.serialization.test.*
-import kotlin.test.*
-
-class JsonParserFailureModesTest : JsonTestBase() {
-
- @Serializable
- data class Holder(
- val id: Long
- )
-
- @Test
- fun testFailureModes() = parametrizedTest {
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id": "}""",
- it
- )
- }
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id": ""}""",
- it
- )
- }
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id":a}""",
- it
- )
- }
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id":2.0}""",
- it
- )
- }
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id2":2}""",
- it
- )
- }
- // 9223372036854775807 is Long.MAX_VALUE
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id":${Long.MAX_VALUE}""" + "00" + "}",
- it
- )
- }
- // -9223372036854775808 is Long.MIN_VALUE
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- Holder.serializer(),
- """{"id":9223372036854775808}""",
- it
- )
- }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{"id"}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{"id}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{"i}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{"}""", it) }
- assertFailsWithMissingField { default.decodeFromString(Holder.serializer(), """{}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{""", it) }
- }
-
- @Serializable
- class BooleanHolder(val b: Boolean)
-
- @Test
- fun testBoolean() = parametrizedTest {
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- BooleanHolder.serializer(),
- """{"b": fals}""",
- it
- )
- }
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString(
- BooleanHolder.serializer(),
- """{"b": 123}""",
- it
- )
- }
- }
-
- @Serializable
- class PrimitiveHolder(
- val b: Byte = 0, val s: Short = 0, val i: Int = 0
- )
-
- @Test
- fun testOverflow() = parametrizedTest {
- // Byte overflow
- assertFailsWith<JsonDecodingException> { default.decodeFromString<PrimitiveHolder>("""{"b": 128}""", it) }
- // Short overflow
- assertFailsWith<JsonDecodingException> { default.decodeFromString<PrimitiveHolder>("""{"s": 32768}""", it) }
- // Int overflow
- assertFailsWith<JsonDecodingException> {
- default.decodeFromString<PrimitiveHolder>(
- """{"i": 2147483648}""",
- it
- )
- }
- }
-
- @Test
- fun testNoOverflow() = parametrizedTest {
- default.decodeFromString<PrimitiveHolder>("""{"b": ${Byte.MAX_VALUE}}""", it)
- default.decodeFromString<PrimitiveHolder>("""{"b": ${Byte.MIN_VALUE}}""", it)
- default.decodeFromString<PrimitiveHolder>("""{"s": ${Short.MAX_VALUE}}""", it)
- default.decodeFromString<PrimitiveHolder>("""{"s": ${Short.MIN_VALUE}}""", it)
- default.decodeFromString<PrimitiveHolder>("""{"i": ${Int.MAX_VALUE}}""", it)
- default.decodeFromString<PrimitiveHolder>("""{"i": ${Int.MIN_VALUE}}""", it)
- default.decodeFromString<Holder>("""{"id": ${Long.MIN_VALUE.toString()}}""", it)
- default.decodeFromString<Holder>("""{"id": ${Long.MAX_VALUE.toString()}}""", it)
- }
-
- @Test
- fun testInvalidNumber() = parametrizedTest {
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":-}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":+}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":--}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":1-1}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":0-1}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":0-}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":a}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<Holder>("""{"id":-a}""", it) }
- }
-
-
- @Serializable
- data class BooleanWrapper(val b: Boolean)
-
- @Serializable
- data class StringWrapper(val s: String)
-
- @Test
- fun testUnexpectedNull() = parametrizedTest {
- assertFailsWith<JsonDecodingException> { default.decodeFromString<BooleanWrapper>("""{"b":{"b":"b"}}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<BooleanWrapper>("""{"b":null}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<StringWrapper>("""{"s":{"s":"s"}}""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString<StringWrapper>("""{"s":null}""", it) }
- }
-}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt
deleted file mode 100644
index 7a45c8d..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.serialization.json.serializers
-
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.json.*
-import kotlinx.serialization.json.internal.*
-import kotlinx.serialization.test.*
-import kotlin.test.*
-
-class JsonObjectSerializerTest : JsonTestBase() {
-
- private val expected = """{"element":{"literal":1,"nullKey":null,"nested":{"another literal":"some value"},"\\. escaped":"\\. escaped","\n new line":"\n new line"}}"""
- private val expectedTopLevel = """{"literal":1,"nullKey":null,"nested":{"another literal":"some value"},"\\. escaped":"\\. escaped","\n new line":"\n new line"}"""
-
- @Test
- fun testJsonObject() = parametrizedTest(default) {
- assertStringFormAndRestored(expected, JsonObjectWrapper(prebuiltJson()), JsonObjectWrapper.serializer())
- }
-
- @Test
- fun testJsonObjectAsElement() = parametrizedTest(default) {
- assertStringFormAndRestored(expected, JsonElementWrapper(prebuiltJson()), JsonElementWrapper.serializer())
- }
-
- @Test
- fun testTopLevelJsonObject() = parametrizedTest (default) {
- assertStringFormAndRestored(expectedTopLevel, prebuiltJson(), JsonObjectSerializer)
- }
-
- @Test
- fun testTopLevelJsonObjectAsElement() = parametrizedTest (default) {
- assertStringFormAndRestored(expectedTopLevel, prebuiltJson(), JsonElementSerializer)
- }
-
- @Test
- fun testJsonObjectToString() {
- val prebuiltJson = prebuiltJson()
- val string = lenient.encodeToString(JsonElementSerializer, prebuiltJson)
- assertEquals(string, prebuiltJson.toString())
- }
-
- @Test
- fun testDocumentationSample() {
- val string = Json.encodeToString(JsonElementSerializer, buildJsonObject { put("key", 1.0) })
- val literal = Json.decodeFromString(JsonElementSerializer, string)
- assertEquals(JsonObject(mapOf("key" to JsonPrimitive(1.0))), literal)
- }
-
- @Test
- fun testMissingCommas() = parametrizedTest { jsonTestingMode ->
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{ \"1\": \"2\" \"3\":\"4\"}", jsonTestingMode) }
- }
-
- @Test
- fun testEmptyObject() = parametrizedTest { jsonTestingMode ->
- assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObjectSerializer, "{}", jsonTestingMode))
- assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObjectSerializer, "{}", jsonTestingMode))
- assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObjectSerializer, "{\n\n}", jsonTestingMode))
- assertEquals(JsonObject(emptyMap()), lenient.decodeFromString(JsonObjectSerializer, "{ \t}", jsonTestingMode))
- }
-
- @Test
- fun testInvalidObject() = parametrizedTest { jsonTestingMode ->
- assertFailsWith<JsonDecodingException> { default.decodeFromString(JsonObjectSerializer, "{\"a\":\"b\"]", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(JsonObjectSerializer, "{", jsonTestingMode) }
- if (jsonTestingMode != JsonTestingMode.JAVA_STREAMS) // Streams support dangling characters
- assertFailsWith<JsonDecodingException> { default.decodeFromString(JsonObjectSerializer, "{}}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(JsonObjectSerializer, "{]", jsonTestingMode) }
- }
-
- @Test
- fun testWhitespaces() = parametrizedTest { jsonTestingMode ->
- assertEquals(
- JsonObject(mapOf("1" to JsonPrimitive(2), "3" to JsonPrimitive(4), "5" to JsonPrimitive(6))),
- lenient.decodeFromString(JsonObjectSerializer, "{1: 2, 3: \n 4, 5:6}", jsonTestingMode)
- )
- }
-
- @Test
- fun testExcessiveCommas() = parametrizedTest { jsonTestingMode ->
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{\"a\":\"b\",}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{\"a\",}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{,\"1\":\"2\"}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{,\"1\":\"2\",}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{,,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{,,\"1\":\"2\"}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{\"1\":\"2\",,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{\"1\":\"2\",,\"2\":\"2\"}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{, ,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(JsonObjectSerializer, "{,\n,}", jsonTestingMode) }
- }
-
- @Serializable
- data class Holder(val a: String)
-
- @Test
- fun testExcessiveCommasInObject() = parametrizedTest { jsonTestingMode ->
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{\"a\":\"b\",}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{\"a\",}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{,\"a\":\"b\"}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{,\"a\":\"b\",}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{,,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{,,\"a\":\"b\"}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{\"a\":\"b\",,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{, ,}", jsonTestingMode) }
- assertFailsWith<JsonDecodingException> { lenient.decodeFromString(Holder.serializer(), "{,\n,}", jsonTestingMode) }
- }
-
- private fun prebuiltJson(): JsonObject {
- return buildJsonObject {
- put("literal", 1)
- put("nullKey", JsonNull)
- putJsonObject("nested") {
- put("another literal", "some value")
- }
- put("\\. escaped", "\\. escaped")
- put("\n new line", "\n new line")
- }
- }
-}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt
deleted file mode 100644
index 789fb2c..0000000
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.serialization.json.serializers
-
-import kotlinx.serialization.json.*
-import kotlinx.serialization.test.*
-import kotlin.test.*
-
-class JsonPrimitiveSerializerTest : JsonTestBase() {
-
- @Test
- fun testJsonPrimitiveDouble() = parametrizedTest { jsonTestingMode ->
- if (isJs()) return@parametrizedTest // JS toString numbers
-
-
- val wrapper = JsonPrimitiveWrapper(JsonPrimitive(1.0))
- val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
- assertEquals("{\"primitive\":1.0}", string)
- assertEquals(JsonPrimitiveWrapper(JsonPrimitive(1.0)), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
- }
-
- @Test
- fun testJsonPrimitiveInt() = parametrizedTest { jsonTestingMode ->
- val wrapper = JsonPrimitiveWrapper(JsonPrimitive(1))
- val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
- assertEquals("{\"primitive\":1}", string)
- assertEquals(JsonPrimitiveWrapper(JsonPrimitive(1)), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
- }
-
-
- @Test
- fun testJsonPrimitiveString() = parametrizedTest { jsonTestingMode ->
- val wrapper = JsonPrimitiveWrapper(JsonPrimitive("foo"))
- val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
- assertEquals("{\"primitive\":\"foo\"}", string)
- assertEquals(JsonPrimitiveWrapper(JsonPrimitive("foo")), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
- }
-
- @Test
- fun testJsonPrimitiveStringNumber() = parametrizedTest { jsonTestingMode ->
- val wrapper = JsonPrimitiveWrapper(JsonPrimitive("239"))
- val string = default.encodeToString(JsonPrimitiveWrapper.serializer(), wrapper, jsonTestingMode)
- assertEquals("{\"primitive\":\"239\"}", string)
- assertEquals(JsonPrimitiveWrapper(JsonPrimitive("239")), default.decodeFromString(JsonPrimitiveWrapper.serializer(), string, jsonTestingMode))
- }
-
- @Test
- fun testTopLevelPrimitive() = parametrizedTest { jsonTestingMode ->
- val string = default.encodeToString(JsonPrimitiveSerializer, JsonPrimitive(42), jsonTestingMode)
- assertEquals("42", string)
- assertEquals(JsonPrimitive(42), default.decodeFromString(JsonPrimitiveSerializer, string))
- }
-
- @Test
- fun testTopLevelPrimitiveAsElement() = parametrizedTest { jsonTestingMode ->
- if (isJs()) return@parametrizedTest // JS toString numbers
- val string = default.encodeToString(JsonElementSerializer, JsonPrimitive(1.3), jsonTestingMode)
- assertEquals("1.3", string)
- assertEquals(JsonPrimitive(1.3), default.decodeFromString(JsonElementSerializer, string, jsonTestingMode))
- }
-
- @Test
- fun testJsonLiteralStringToString() {
- val literal = JsonPrimitive("some string literal")
- val string = default.encodeToString(JsonPrimitiveSerializer, literal)
- assertEquals(string, literal.toString())
- }
-
- @Test
- fun testJsonLiteralIntToString() {
- val literal = JsonPrimitive(0)
- val string = default.encodeToString(JsonPrimitiveSerializer, literal)
- assertEquals(string, literal.toString())
- }
-
- @Test
- fun testJsonLiterals() {
- testLiteral(0L, "0")
- testLiteral(0, "0")
- testLiteral(0.0, "0.0")
- testLiteral(0.0f, "0.0")
- testLiteral(Long.MAX_VALUE, "9223372036854775807")
- testLiteral(Long.MIN_VALUE, "-9223372036854775808")
- testLiteral(Float.MAX_VALUE, "3.4028235E38")
- testLiteral(Float.MIN_VALUE, "1.4E-45")
- testLiteral(Double.MAX_VALUE, "1.7976931348623157E308")
- testLiteral(Double.MIN_VALUE, "4.9E-324")
- testLiteral(Int.MAX_VALUE, "2147483647")
- testLiteral(Int.MIN_VALUE, "-2147483648")
- }
-
- private fun testLiteral(number: Number, jvmExpectedString: String) {
- val literal = JsonPrimitive(number)
- val string = default.encodeToString(JsonPrimitiveSerializer, literal)
- assertEquals(string, literal.toString())
- if (isJvm()) { // We can rely on stable double/float format only on JVM
- assertEquals(string, jvmExpectedString)
- }
- }
-}
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 a6658c7..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 }
)
@@ -100,18 +100,27 @@
return forceNull
}
- override fun elementName(desc: SerialDescriptor, index: Int): String {
- val mainName = desc.getElementName(index)
- if (!json.configuration.useAlternativeNames) return mainName
- // Fast path, do not go through Map.get
- // Note, it blocks ability to detect collisions between the primary name and alternate,
- // but it eliminates a significant performance penalty (about -15% without this optimization)
- if (hasName(mainName)) return mainName
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
+ val strategy = descriptor.namingStrategy(json)
+ val mainName = descriptor.getElementName(index)
+ if (strategy == null) {
+ if (!json.configuration.useAlternativeNames) return mainName
+ // Fast path, do not go through Map.get
+ // Note, it blocks ability to detect collisions between the primary name and alternate,
+ // but it eliminates a significant performance penalty (about -15% without this optimization)
+ if (hasName(mainName)) return mainName
+ }
// Slow path
- val alternativeNamesMap =
- json.schemaCache.getOrPut(desc, JsonAlternativeNamesKey, desc::buildAlternativeNamesMap)
- val nameInObject = (keys as Array<String>).find { alternativeNamesMap[it] == index }
- return nameInObject ?: mainName
+ val deserializationNamesMap = json.deserializationNamesMap(descriptor)
+ (keys as Array<String>).find { deserializationNamesMap[it] == index }?.let {
+ return it
+ }
+ val fallbackName = strategy?.serialNameForJson(
+ descriptor,
+ index,
+ mainName
+ ) // Key not found exception should be thrown with transformed name, not original
+ return fallbackName ?: mainName
}
override fun decodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor): Int {
@@ -125,7 +134,12 @@
override fun decodeTaggedChar(tag: String): Char {
return when (val value = getByTag(tag)) {
is String -> if (value.length == 1) value[0] else throw SerializationException("$value can't be represented as Char")
- is Number -> value.toChar()
+ is Number -> {
+ val num = value as? Double ?: throw SerializationException("$value is not a Number")
+ val codePoint = toJavascriptLong(num)
+ if (codePoint < 0 || codePoint > Char.MAX_VALUE.code) throw SerializationException("$value can't be represented as Char because it's not in bounds of Char.MIN_VALUE..Char.MAX_VALUE")
+ codePoint.toInt().toChar()
+ }
else -> throw SerializationException("$value can't be represented as Char")
}
}
@@ -191,7 +205,7 @@
private var currentPosition = -1
private val isKey: Boolean get() = currentPosition % 2 == 0
- override fun elementName(desc: SerialDescriptor, index: Int): String {
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String {
val i = index / 2
return keys[i] as String
}
@@ -261,7 +275,7 @@
override val size = value.length as Int
private var currentPosition = -1
- override fun elementName(desc: SerialDescriptor, index: Int): String = (index).toString()
+ override fun elementName(descriptor: SerialDescriptor, index: Int): String = (index).toString()
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
while (currentPosition < size - 1) {
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
index 4bd46d5..4c4841d 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
+++ b/formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt
@@ -81,7 +81,7 @@
when {
current.writeMode == WriteMode.MAP -> currentElementIsMapKey = current.index % 2 == 0
current.writeMode == WriteMode.LIST && descriptor.kind is PolymorphicKind -> currentName = index.toString()
- else -> currentName = descriptor.getElementName(index)
+ else -> currentName = descriptor.getJsonElementName(json, index)
}
return true
@@ -257,7 +257,7 @@
if (!json.configuration.isLenient && abs(value) > MAX_SAFE_INTEGER) {
throw IllegalArgumentException(
"$value can't be deserialized to number due to a potential precision loss. " +
- "Use the JsonConfiguration option isLenient to serialise anyway"
+ "Use the JsonConfiguration option isLenient to serialize anyway"
)
}
encodeValue(asDouble)
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt b/formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
deleted file mode 100644
index 1b79e27..0000000
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package kotlinx.serialization.json.internal
-
-internal actual class JsonStringBuilder actual constructor() {
- private val sb = StringBuilder(128)
-
- actual fun append(value: Long) {
- sb.append(value)
- }
-
- actual fun append(ch: Char) {
- sb.append(ch)
- }
-
- actual fun append(string: String) {
- sb.append(string)
- }
-
- actual fun appendQuoted(string: String) {
- sb.printQuoted(string)
- }
-
- actual override fun toString(): String {
- return sb.toString()
- }
-
- actual fun release() {
- }
-}
diff --git a/formats/json/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/json/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
deleted file mode 100644
index b87276e..0000000
--- a/formats/json/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ /dev/null
@@ -1,12 +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.test
-
-public actual val currentPlatform: Platform = if (isLegacyBackend()) Platform.JS_LEGACY else Platform.JS_IR
-
-// from https://github.com/JetBrains/kotlin/blob/569187a7516e2e5ab217158a3170d4beb0c5cb5a/js/js.translator/testData/_commonFiles/testUtils.kt#L3
-private fun isLegacyBackend(): Boolean =
- // Using eval to prevent DCE from thinking that following code depends on Kotlin module.
- eval("(typeof Kotlin != \"undefined\" && typeof Kotlin.kotlin != \"undefined\")").unsafeCast<Boolean>()
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/JsonSchemaCache.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
similarity index 100%
rename from formats/json/jsMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
diff --git a/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
new file mode 100644
index 0000000..3f27896
--- /dev/null
+++ b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
@@ -0,0 +1,12 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.serialization.json.internal
+
+
+internal actual object CharArrayPoolBatchSize {
+
+ actual fun take(): CharArray = CharArray(BATCH_SIZE)
+
+ actual fun release(array: CharArray) = Unit
+}
diff --git a/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/FormatLanguageJsWasm.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/FormatLanguageJsWasm.kt
new file mode 100644
index 0000000..176771f
--- /dev/null
+++ b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/FormatLanguageJsWasm.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017-2023 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.InternalSerializationApi
+
+@InternalSerializationApi
+@Retention(AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.ANNOTATION_CLASS
+)
+public actual annotation class FormatLanguage(
+ public actual val value: String,
+ // default parameters are not used due to https://youtrack.jetbrains.com/issue/KT-25946/
+ public actual val prefix: String,
+ public actual val suffix: String,
+)
diff --git a/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt
new file mode 100644
index 0000000..387ed7e
--- /dev/null
+++ b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt
@@ -0,0 +1,29 @@
+package kotlinx.serialization.json.internal
+
+internal actual open class JsonToStringWriter actual constructor(): InternalJsonWriter {
+ private val sb = StringBuilder(128)
+
+ actual override fun writeLong(value: Long) {
+ sb.append(value)
+ }
+
+ actual override fun writeChar(char: Char) {
+ sb.append(char)
+ }
+
+ actual override fun write(text: String) {
+ sb.append(text)
+ }
+
+ actual override fun writeQuoted(text: String) {
+ sb.printQuoted(text)
+ }
+
+ actual override fun release() {
+ sb.clear()
+ }
+
+ actual override fun toString(): String {
+ return sb.toString()
+ }
+}
diff --git a/formats/json/jsMain/src/kotlinx/serialization/json/internal/createMapForCache.kt b/formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
similarity index 100%
rename from formats/json/jsMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
rename to formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt
index 3b83299..329e309 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt
@@ -12,7 +12,7 @@
* Serializes the [value] with [serializer] into a [stream] using JSON format and UTF-8 encoding.
*
* @throws [SerializationException] if the given value cannot be serialized to JSON.
- * @throws [IOException] If an I/O error occurs and stream can't be written to.
+ * @throws [IOException] If an I/O error occurs and stream cannot be written to.
*/
@ExperimentalSerializationApi
public fun <T> Json.encodeToStream(
@@ -20,16 +20,11 @@
value: T,
stream: OutputStream
) {
- val result = JsonToWriterStringBuilder(stream)
+ val writer = JsonToJavaStreamWriter(stream)
try {
- val encoder = StreamingJsonEncoder(
- result, this,
- WriteMode.OBJ,
- arrayOfNulls(WriteMode.values().size)
- )
- encoder.encodeSerializableValue(serializer, value)
+ encodeByWriter(this, writer, serializer, value)
} finally {
- result.release()
+ writer.release()
}
}
@@ -37,7 +32,7 @@
* Serializes given [value] to [stream] using UTF-8 encoding and serializer retrieved from the reified type parameter.
*
* @throws [SerializationException] if the given value cannot be serialized to JSON.
- * @throws [IOException] If an I/O error occurs and stream can't be written to.
+ * @throws [IOException] If an I/O error occurs and stream cannot be written to.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Json.encodeToStream(
@@ -53,18 +48,20 @@
* and throws an exception if there are any dangling bytes after an object.
*
* @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
- * @throws [IOException] If an I/O error occurs and stream can't be read from.
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
+ * @throws [IOException] If an I/O error occurs and stream cannot be read from.
*/
@ExperimentalSerializationApi
public fun <T> Json.decodeFromStream(
deserializer: DeserializationStrategy<T>,
stream: InputStream
): T {
- val lexer = ReaderJsonLexer(stream)
- val input = StreamingJsonDecoder(this, WriteMode.OBJ, lexer, deserializer.descriptor)
- val result = input.decodeSerializableValue(deserializer)
- lexer.expectEof()
- return result
+ val reader = JavaStreamSerialReader(stream)
+ try {
+ return decodeByReader(this, deserializer, reader)
+ } finally {
+ reader.release()
+ }
}
/**
@@ -75,65 +72,13 @@
* and throws an exception if there are any dangling bytes after an object.
*
* @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
- * @throws [IOException] If an I/O error occurs and stream can't be read from.
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
+ * @throws [IOException] If an I/O error occurs and stream cannot be read from.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Json.decodeFromStream(stream: InputStream): T =
decodeFromStream(serializersModule.serializer(), stream)
-/**
- * Description of [decodeToSequence]'s JSON input shape.
- *
- * The sequence represents a stream of objects parsed one by one;
- * [DecodeSequenceMode] defines a separator between these objects.
- * Typically, these objects are not separated by meaningful characters ([WHITESPACE_SEPARATED]),
- * or the whole stream is a large array of objects separated with commas ([ARRAY_WRAPPED]).
- */
-@ExperimentalSerializationApi
-public enum class DecodeSequenceMode {
- /**
- * Declares that objects in the input stream are separated by whitespace characters.
- *
- * 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.
- * Each individual object is parsed lazily, when it is requested from the resulting sequence.
- *
- * Whitespace character is either ' ', '\n', '\r' or '\t'.
- *
- * Example of `WHITESPACE_SEPARATED` stream content:
- * ```
- * """{"key": "value"}{"key": "value2"} {"key2": "value2"}"""
- * ```
- */
- WHITESPACE_SEPARATED,
-
- /**
- * Declares that objects in the input stream are wrapped in the JSON array.
- * Each individual object in the array is parsed lazily when it is requested from the resulting sequence.
- *
- * The stream is read as multiple JSON objects wrapped into a JSON array.
- * The stream must start with an array start character `[` and end with an array end character `]`,
- * otherwise, [JsonDecodingException] is thrown.
- *
- * Example of `ARRAY_WRAPPED` stream content:
- * ```
- * """[{"key": "value"}, {"key": "value2"},{"key2": "value2"}]"""
- * ```
- */
- ARRAY_WRAPPED,
-
- /**
- * Declares that parser itself should select between [WHITESPACE_SEPARATED] and [ARRAY_WRAPPED] modes.
- * The selection is performed by looking on the first meaningful character of the stream.
- *
- * In most cases, auto-detection is sufficient to correctly parse an input.
- * If the input is _whitespace-separated stream of the arrays_, parser could select an incorrect mode,
- * for that [DecodeSequenceMode] must be specified explicitly.
- *
- * Example of an exceptional case:
- * `[1, 2, 3] [4, 5, 6]\n[7, 8, 9]`
- */
- AUTO_DETECT;
-}
/**
* Transforms the given [stream] into lazily deserialized sequence of elements of type [T] using UTF-8 encoding and [deserializer].
@@ -148,7 +93,8 @@
* closing it before returned sequence is evaluated completely will result in [IOException] from decoder.
*
* @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
- * @throws [IOException] If an I/O error occurs and stream can't be read from.
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
+ * @throws [IOException] If an I/O error occurs and stream cannot be read from.
*/
@ExperimentalSerializationApi
public fun <T> Json.decodeToSequence(
@@ -156,9 +102,7 @@
deserializer: DeserializationStrategy<T>,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T> {
- val lexer = ReaderJsonLexer(stream)
- val iter = JsonIterator(format, this, lexer, deserializer)
- return Sequence { iter }.constrainOnce()
+ return decodeToSequenceByReader(this, JavaStreamSerialReader(stream), deserializer, format)
}
/**
@@ -174,11 +118,11 @@
* closing it before returned sequence is evaluated fully would result in [IOException] from decoder.
*
* @throws [SerializationException] if the given JSON input cannot be deserialized to the value of type [T].
- * @throws [IOException] If an I/O error occurs and stream can't be read from.
+ * @throws [IllegalArgumentException] if the decoded input cannot be represented as a valid instance of type [T]
+ * @throws [IOException] If an I/O error occurs and stream cannot be read from.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Json.decodeToSequence(
stream: InputStream,
format: DecodeSequenceMode = DecodeSequenceMode.AUTO_DETECT
): Sequence<T> = decodeToSequence(stream, serializersModule.serializer(), format)
-
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/ArrayPools.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/ArrayPools.kt
new file mode 100644
index 0000000..0d36c6c
--- /dev/null
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/ArrayPools.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.serialization.json.internal
+
+/*
+ * Not really documented kill switch as a workaround for potential
+ * (unlikely) problems with memory consumptions.
+ */
+private val MAX_CHARS_IN_POOL = runCatching {
+ System.getProperty("kotlinx.serialization.json.pool.size").toIntOrNull()
+}.getOrNull() ?: 2 * 1024 * 1024
+
+internal open class CharArrayPoolBase {
+ private val arrays = ArrayDeque<CharArray>()
+ private var charsTotal = 0
+
+ protected fun take(size: Int): CharArray {
+ /*
+ * Initially the pool is empty, so an instance will be allocated
+ * and the pool will be populated in the 'release'
+ */
+ val candidate = synchronized(this) {
+ arrays.removeLastOrNull()?.also { charsTotal -= it.size }
+ }
+ return candidate ?: CharArray(size)
+ }
+
+ protected fun releaseImpl(array: CharArray): Unit = synchronized(this) {
+ if (charsTotal + array.size >= MAX_CHARS_IN_POOL) return@synchronized
+ charsTotal += array.size
+ arrays.addLast(array)
+ }
+}
+
+internal object CharArrayPool : CharArrayPoolBase() {
+ fun take(): CharArray = super.take(128)
+
+ // Can release array of an arbitrary size
+ fun release(array: CharArray) = releaseImpl(array)
+}
+
+// Pools char arrays of size 16K
+internal actual object CharArrayPoolBatchSize : CharArrayPoolBase() {
+
+ actual fun take(): CharArray = super.take(BATCH_SIZE)
+
+ actual fun release(array: CharArray) {
+ require(array.size == BATCH_SIZE) { "Inconsistent internal invariant: unexpected array size ${array.size}" }
+ releaseImpl(array)
+ }
+}
+
+// Byte array pool
+
+internal open class ByteArrayPoolBase {
+ private val arrays = ArrayDeque<kotlin.ByteArray>()
+ private var bytesTotal = 0
+
+ protected fun take(size: Int): ByteArray {
+ /*
+ * Initially the pool is empty, so an instance will be allocated
+ * and the pool will be populated in the 'release'
+ */
+ val candidate = synchronized(this) {
+ arrays.removeLastOrNull()?.also { bytesTotal -= it.size / 2 }
+ }
+ return candidate ?: ByteArray(size)
+ }
+
+ protected fun releaseImpl(array: ByteArray): Unit = synchronized(this) {
+ if (bytesTotal + array.size >= MAX_CHARS_IN_POOL) return@synchronized
+ bytesTotal += array.size / 2
+ arrays.addLast(array)
+ }
+}
+
+internal object ByteArrayPool8k : ByteArrayPoolBase() {
+ fun take(): ByteArray = super.take(8196)
+
+ fun release(array: ByteArray) = releaseImpl(array)
+}
+
+
+internal object ByteArrayPool : ByteArrayPoolBase() {
+ fun take(): ByteArray = super.take(512)
+
+ fun release(array: ByteArray) = releaseImpl(array)
+}
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
deleted file mode 100644
index e51b3de..0000000
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-package kotlinx.serialization.json.internal
-
-import java.util.concurrent.*
-
-internal object CharArrayPool {
- private val arrays = ArrayDeque<CharArray>()
- private var charsTotal = 0
- /*
- * Not really documented kill switch as a workaround for potential
- * (unlikely) problems with memory consumptions.
- */
- private val MAX_CHARS_IN_POOL = runCatching {
- System.getProperty("kotlinx.serialization.json.pool.size").toIntOrNull()
- }.getOrNull() ?: 1024 * 1024 // 2 MB seems to be a reasonable constraint, (1M of chars)
-
- fun take(): CharArray {
- /*
- * Initially the pool is empty, so an instance will be allocated
- * and the pool will be populated in the 'release'
- */
- val candidate = synchronized(this) {
- arrays.removeLastOrNull()?.also { charsTotal -= it.size }
- }
- return candidate ?: CharArray(128)
- }
-
- fun release(array: CharArray) = synchronized(this) {
- if (charsTotal + array.size >= MAX_CHARS_IN_POOL) return@synchronized
- charsTotal += array.size
- arrays.addLast(array)
- }
-}
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharsetReader.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharsetReader.kt
new file mode 100644
index 0000000..0f8a566
--- /dev/null
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharsetReader.kt
@@ -0,0 +1,125 @@
+package kotlinx.serialization.json.internal
+
+import java.io.*
+import java.nio.*
+import java.nio.charset.*
+
+internal class CharsetReader(
+ private val inputStream: InputStream,
+ private val charset: Charset
+) {
+ private val decoder: CharsetDecoder
+ private val byteBuffer: ByteBuffer
+
+ // Surrogate-handling in cases when a single char is requested, but two were read
+ private var hasLeftoverPotentiallySurrogateChar = false
+ private var leftoverChar = 0.toChar()
+
+ init {
+ decoder = charset.newDecoder()
+ .onMalformedInput(CodingErrorAction.REPLACE)
+ .onUnmappableCharacter(CodingErrorAction.REPLACE)
+ byteBuffer = ByteBuffer.wrap(ByteArrayPool8k.take())
+ byteBuffer.flip() // Make empty
+ }
+
+ @Suppress("NAME_SHADOWING")
+ fun read(array: CharArray, offset: Int, length: Int): Int {
+ if (length == 0) return 0
+ require(offset in 0 until array.size && length >= 0 && offset + length <= array.size) {
+ "Unexpected arguments: $offset, $length, ${array.size}"
+ }
+
+ var offset = offset
+ var length = length
+ var bytesRead = 0
+ if (hasLeftoverPotentiallySurrogateChar) {
+ array[offset] = leftoverChar
+ offset++
+ length--
+ hasLeftoverPotentiallySurrogateChar = false
+ bytesRead = 1
+ if (length == 0) return bytesRead
+ }
+ if (length == 1) {
+ // Treat single-character array reads just like read()
+ val c = oneShotReadSlowPath()
+ if (c == -1) return if (bytesRead == 0) -1 else bytesRead
+ array[offset] = c.toChar()
+ return bytesRead + 1
+ }
+ return doRead(array, offset, length) + bytesRead
+ }
+
+ private fun doRead(array: CharArray, offset: Int, length: Int): Int {
+ var charBuffer = CharBuffer.wrap(array, offset, length)
+ if (charBuffer.position() != 0) {
+ charBuffer = charBuffer.slice()
+ }
+ var isEof = false
+ while (true) {
+ val cr = decoder.decode(byteBuffer, charBuffer, isEof)
+ if (cr.isUnderflow) {
+ if (isEof) break
+ if (!charBuffer.hasRemaining()) break
+ val n = fillByteBuffer()
+ if (n < 0) {
+ isEof = true
+ if (charBuffer.position() == 0 && !byteBuffer.hasRemaining()) break
+ decoder.reset()
+ }
+ continue
+ }
+ if (cr.isOverflow) {
+ assert(charBuffer.position() > 0)
+ break
+ }
+ cr.throwException()
+ }
+ if (isEof) decoder.reset()
+ return if (charBuffer.position() == 0) -1
+ else charBuffer.position()
+ }
+
+ private fun fillByteBuffer(): Int {
+ byteBuffer.compact()
+ try {
+ // Read from the input stream, and then update the buffer
+ val limit = byteBuffer.limit()
+ val position = byteBuffer.position()
+ val remaining = if (position <= limit) limit - position else 0
+ val bytesRead = inputStream.read(byteBuffer.array(), byteBuffer.arrayOffset() + position, remaining)
+ if (bytesRead < 0) return bytesRead
+ // Method `position(I)LByteBuffer` does not exist in Java 8. For details, see comment for `flip` in `init` method
+ (byteBuffer as Buffer).position(position + bytesRead)
+ } finally {
+ byteBuffer.flip()
+ }
+ return byteBuffer.remaining()
+ }
+
+ private fun oneShotReadSlowPath(): Int {
+ // Return the leftover char, if there is one
+ if (hasLeftoverPotentiallySurrogateChar) {
+ hasLeftoverPotentiallySurrogateChar = false
+ return leftoverChar.code
+ }
+
+ val array = CharArray(2)
+ val bytesRead = read(array, 0, 2)
+ return when (bytesRead) {
+ -1 -> -1
+ 1 -> array[0].code
+ 2 -> {
+ leftoverChar = array[1]
+ hasLeftoverPotentiallySurrogateChar = true
+ array[0].code
+ }
+ else -> error("Unreachable state: $bytesRead")
+ }
+ }
+
+ public fun release() {
+ ByteArrayPool8k.release(byteBuffer.array())
+ }
+}
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt
new file mode 100644
index 0000000..9eedf1b
--- /dev/null
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2017-2023 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.InternalSerializationApi
+
+@InternalSerializationApi
+public actual typealias FormatLanguage = org.intellij.lang.annotations.Language
\ No newline at end of file
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
similarity index 84%
rename from formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
rename to formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
index 37766d9..5666724 100644
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
@@ -25,41 +25,41 @@
* 3) We pool char arrays in order to save excess resizes, allocations
* and nulls-out of arrays.
*/
-internal actual open class JsonStringBuilder(@JvmField protected var array: CharArray) {
- actual constructor(): this(CharArrayPool.take())
+internal actual class JsonToStringWriter : InternalJsonWriter {
+ private var array: CharArray = CharArrayPool.take()
+ private var size = 0
- protected var size = 0
-
- actual fun append(value: Long) {
+ actual override fun writeLong(value: Long) {
// Can be hand-rolled, but requires a lot of code and corner-cases handling
- append(value.toString())
+ write(value.toString())
}
- actual fun append(ch: Char) {
+ actual override fun writeChar(char: Char) {
ensureAdditionalCapacity(1)
- array[size++] = ch
+ array[size++] = char
}
- actual fun append(string: String) {
- val length = string.length
+ actual override fun write(text: String) {
+ val length = text.length
+ if (length == 0) return
ensureAdditionalCapacity(length)
- string.toCharArray(array, size, 0, string.length)
+ text.toCharArray(array, size, 0, text.length)
size += length
}
- actual fun appendQuoted(string: String) {
- ensureAdditionalCapacity(string.length + 2)
+ actual override fun writeQuoted(text: String) {
+ ensureAdditionalCapacity(text.length + 2)
val arr = array
var sz = size
arr[sz++] = '"'
- val length = string.length
- string.toCharArray(arr, sz, 0, length)
+ val length = text.length
+ text.toCharArray(arr, sz, 0, length)
for (i in sz until sz + length) {
val ch = arr[i].code
// Do we have unescaped symbols?
if (ch < ESCAPE_MARKERS.size && ESCAPE_MARKERS[ch] != 0.toByte()) {
// Go to slow path
- return appendStringSlowPath(i - sz, i, string)
+ return appendStringSlowPath(i - sz, i, text)
}
}
// Update the state
@@ -113,6 +113,10 @@
size = sz
}
+ actual override fun release() {
+ CharArrayPool.release(array)
+ }
+
actual override fun toString(): String {
return String(array, 0, size)
}
@@ -122,15 +126,11 @@
}
// Old size is passed and returned separately to avoid excessive [size] field read
- protected open fun ensureTotalCapacity(oldSize: Int, additional: Int): Int {
+ private fun ensureTotalCapacity(oldSize: Int, additional: Int): Int {
val newSize = oldSize + additional
if (array.size <= newSize) {
array = array.copyOf(newSize.coerceAtLeast(oldSize * 2))
}
return oldSize
}
-
- actual open fun release() {
- CharArrayPool.release(array)
- }
}
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToWriterStringBuilder.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToWriterStringBuilder.kt
deleted file mode 100644
index c406906..0000000
--- a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToWriterStringBuilder.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.serialization.json.internal
-
-import java.io.OutputStream
-import java.io.Writer
-import java.nio.charset.Charset
-
-
-internal class JsonToWriterStringBuilder(private val writer: Writer) : JsonStringBuilder(
- // maybe this can also be taken from the pool, but currently initial char array size there is 128, which is too low.
- CharArray(BATCH_SIZE)
-) {
- constructor(os: OutputStream, charset: Charset = Charsets.UTF_8): this(os.writer(charset).buffered(READER_BUF_SIZE))
-
- override fun ensureTotalCapacity(oldSize: Int, additional: Int): Int {
- val requiredSize = oldSize + additional
- val currentSize = array.size
- if (currentSize <= requiredSize) {
- flush(oldSize)
- if (additional > currentSize) {
- // Handle strings that are longer than buffer:
- // Ideally, we should make `ensureAdditionalCapacity` return boolean and fall back
- // to per-symbol path in appendQuoted on large strings,
- // but this approach is adequate for current stage, too.
- array = CharArray(requiredSize.coerceAtLeast(currentSize * 2))
- }
- return 0
- }
- return oldSize
- }
-
- private fun flush(sz: Int = size) {
- writer.write(array, 0, sz)
- size = 0
- }
-
- override fun release() {
- flush()
- writer.flush()
- }
-}
-
diff --git a/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt
new file mode 100644
index 0000000..e8e0d9a
--- /dev/null
+++ b/formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt
@@ -0,0 +1,267 @@
+package kotlinx.serialization.json.internal
+
+import java.io.InputStream
+import java.io.OutputStream
+
+internal class JsonToJavaStreamWriter(private val stream: OutputStream) : InternalJsonWriter {
+ private val buffer = ByteArrayPool.take()
+ private var charArray = CharArrayPool.take()
+ private var indexInBuffer: Int = 0
+
+ override fun writeLong(value: Long) {
+ write(value.toString())
+ }
+
+ override fun writeChar(char: Char) {
+ writeUtf8CodePoint(char.code)
+ }
+
+ override fun write(text: String) {
+ val length = text.length
+ ensureTotalCapacity(0, length)
+ text.toCharArray(charArray, 0, 0, length)
+ writeUtf8(charArray, length)
+ }
+
+ override fun writeQuoted(text: String) {
+ ensureTotalCapacity(0, text.length + 2)
+ val arr = charArray
+ arr[0] = '"'
+ val length = text.length
+ text.toCharArray(arr, 1, 0, length)
+ for (i in 1 until 1 + length) {
+ val ch = arr[i].code
+ // Do we have unescaped symbols?
+ if (ch < ESCAPE_MARKERS.size && ESCAPE_MARKERS[ch] != 0.toByte()) {
+ // Go to slow path
+ return appendStringSlowPath(i, text)
+ }
+ }
+ // Update the state
+ // Capacity is not ensured because we didn't hit the slow path and thus guessed it properly in the beginning
+
+ arr[length + 1] = '"'
+
+ writeUtf8(arr, length + 2)
+ flush()
+ }
+
+ private fun appendStringSlowPath(currentSize: Int, string: String) {
+ var sz = currentSize
+ for (i in currentSize - 1 until string.length) {
+ /*
+ * We ar already on slow path and haven't guessed the capacity properly.
+ * Reserve +2 for backslash-escaped symbols on each iteration
+ */
+ sz = ensureTotalCapacity(sz, 2)
+ val ch = string[i].code
+ // Do we have unescaped symbols?
+ if (ch < ESCAPE_MARKERS.size) {
+ /*
+ * Escape markers are populated for backslash-escaped symbols.
+ * E.g. ESCAPE_MARKERS['\b'] == 'b'.toByte()
+ * Everything else is populated with either zeros (no escapes)
+ * or ones (unicode escape)
+ */
+ when (val marker = ESCAPE_MARKERS[ch]) {
+ 0.toByte() -> {
+ charArray[sz++] = ch.toChar()
+ }
+
+ 1.toByte() -> {
+ val escapedString = ESCAPE_STRINGS[ch]!!
+ sz = ensureTotalCapacity(sz, escapedString.length)
+ escapedString.toCharArray(charArray, sz, 0, escapedString.length)
+ sz += escapedString.length
+ }
+
+ else -> {
+ charArray[sz] = '\\'
+ charArray[sz + 1] = marker.toInt().toChar()
+ sz += 2
+ }
+ }
+ } else {
+ charArray[sz++] = ch.toChar()
+ }
+ }
+ ensureTotalCapacity(sz, 1)
+ charArray[sz++] = '"'
+ writeUtf8(charArray, sz)
+ flush()
+ }
+
+ private fun ensureTotalCapacity(oldSize: Int, additional: Int): Int {
+ val newSize = oldSize + additional
+ if (charArray.size <= newSize) {
+ charArray = charArray.copyOf(newSize.coerceAtLeast(oldSize * 2))
+ }
+ return oldSize
+ }
+
+ override fun release() {
+ flush()
+ CharArrayPool.release(charArray)
+ ByteArrayPool.release(buffer)
+ }
+
+ private fun flush() {
+ stream.write(buffer, 0, indexInBuffer)
+ indexInBuffer = 0
+ }
+
+
+ @Suppress("NOTHING_TO_INLINE")
+ // ! you should never ask for more than the buffer size
+ private inline fun ensure(bytesCount: Int) {
+ if (rest() < bytesCount) {
+ flush()
+ }
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ // ! you should never ask for more than the buffer size
+ private inline fun write(byte: Int) {
+ buffer[indexInBuffer++] = byte.toByte()
+ }
+
+ @Suppress("NOTHING_TO_INLINE")
+ private inline fun rest(): Int {
+ return buffer.size - indexInBuffer
+ }
+
+ /*
+ Sources taken from okio library with minor changes, see https://github.com/square/okio
+ */
+ private fun writeUtf8(string: CharArray, count: Int) {
+ require(count >= 0) { "count < 0" }
+ require(count <= string.size) { "count > string.length: $count > ${string.size}" }
+
+ // Transcode a UTF-16 Java String to UTF-8 bytes.
+ var i = 0
+ while (i < count) {
+ var c = string[i].code
+
+ when {
+ c < 0x80 -> {
+ // Emit a 7-bit character with 1 byte.
+ ensure(1)
+ write(c) // 0xxxxxxx
+ i++
+ val runLimit = minOf(count, i + rest())
+
+ // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance
+ // improvement over independent calls to writeByte().
+ while (i < runLimit) {
+ c = string[i].code
+ if (c >= 0x80) break
+ write(c) // 0xxxxxxx
+ i++
+ }
+ }
+
+ c < 0x800 -> {
+ // Emit a 11-bit character with 2 bytes.
+ ensure(2)
+ write(c shr 6 or 0xc0) // 110xxxxx
+ write(c and 0x3f or 0x80) // 10xxxxxx
+ i++
+ }
+
+ c < 0xd800 || c > 0xdfff -> {
+ // Emit a 16-bit character with 3 bytes.
+ ensure(3)
+ write(c shr 12 or 0xe0) // 1110xxxx
+ write(c shr 6 and 0x3f or 0x80) // 10xxxxxx
+ write(c and 0x3f or 0x80) // 10xxxxxx
+ i++
+ }
+
+ else -> {
+ // c is a surrogate. Make sure it is a high surrogate & that its successor is a low
+ // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement
+ // character.
+ val low = (if (i + 1 < count) string[i + 1].code else 0)
+ if (c > 0xdbff || low !in 0xdc00..0xdfff) {
+ ensure(1)
+ write('?'.code)
+ i++
+ } else {
+ // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits)
+ // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits)
+ // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits)
+ val codePoint = 0x010000 + (c and 0x03ff shl 10 or (low and 0x03ff))
+
+ // Emit a 21-bit character with 4 bytes.
+ ensure(4)
+ write(codePoint shr 18 or 0xf0) // 11110xxx
+ write(codePoint shr 12 and 0x3f or 0x80) // 10xxxxxx
+ write(codePoint shr 6 and 0x3f or 0x80) // 10xxyyyy
+ write(codePoint and 0x3f or 0x80) // 10yyyyyy
+ i += 2
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Sources taken from okio library with minor changes, see https://github.com/square/okio
+ */
+ private fun writeUtf8CodePoint(codePoint: Int) {
+ when {
+ codePoint < 0x80 -> {
+ // Emit a 7-bit code point with 1 byte.
+ ensure(1)
+ write(codePoint)
+ }
+
+ codePoint < 0x800 -> {
+ // Emit a 11-bit code point with 2 bytes.
+ ensure(2)
+ write(codePoint shr 6 or 0xc0) // 110xxxxx
+ write(codePoint and 0x3f or 0x80) // 10xxxxxx
+ }
+
+ codePoint in 0xd800..0xdfff -> {
+ // Emit a replacement character for a partial surrogate.
+ ensure(1)
+ write('?'.code)
+ }
+
+ codePoint < 0x10000 -> {
+ // Emit a 16-bit code point with 3 bytes.
+ ensure(3)
+ write(codePoint shr 12 or 0xe0) // 1110xxxx
+ write(codePoint shr 6 and 0x3f or 0x80) // 10xxxxxx
+ write(codePoint and 0x3f or 0x80) // 10xxxxxx
+ }
+
+ codePoint <= 0x10ffff -> {
+ // Emit a 21-bit code point with 4 bytes.
+ ensure(4)
+ write(codePoint shr 18 or 0xf0) // 11110xxx
+ write(codePoint shr 12 and 0x3f or 0x80) // 10xxxxxx
+ write(codePoint shr 6 and 0x3f or 0x80) // 10xxyyyy
+ write(codePoint and 0x3f or 0x80) // 10yyyyyy
+ }
+
+ else -> {
+ throw JsonEncodingException("Unexpected code point: $codePoint")
+ }
+ }
+ }
+}
+
+internal class JavaStreamSerialReader(stream: InputStream) : InternalJsonReader {
+ // NB: not closed on purpose, it is the responsibility of the caller
+ private val reader = CharsetReader(stream, Charsets.UTF_8)
+
+ override fun read(buffer: CharArray, bufferOffset: Int, count: Int): Int {
+ return reader.read(buffer, bufferOffset, count)
+ }
+
+ fun release() {
+ reader.release()
+ }
+}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt b/formats/json/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
deleted file mode 100644
index a0ff55a..0000000
--- a/formats/json/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2019 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package kotlinx.serialization
-
-import kotlinx.serialization.json.Json
-import org.junit.Test
-import java.util.HashMap
-import java.util.HashSet
-import kotlin.collections.LinkedHashMap
-import kotlin.collections.Map
-import kotlin.collections.hashMapOf
-import kotlin.collections.hashSetOf
-import kotlin.test.assertEquals
-
-
-class JavaCollectionsTest {
- @Serializable
- data class HasHashMap(
- val s: String,
- val hashMap: HashMap<Int, String>,
- val hashSet: HashSet<Int>,
- val linkedHashMap: LinkedHashMap<Int, String>,
- val kEntry: Map.Entry<Int, String>?
- )
-
- @Test
- fun test() {
- val original = HasHashMap("42", hashMapOf(1 to "1", 2 to "2"), hashSetOf(11), LinkedHashMap(), null)
- val serializer = HasHashMap.serializer()
- val string = Json.encodeToString(serializer = serializer, value = original)
- assertEquals(
- expected = """{"s":"42","hashMap":{"1":"1","2":"2"},"hashSet":[11],"linkedHashMap":{},"kEntry":null}""",
- actual = string
- )
- val restored = Json.decodeFromString(deserializer = serializer, string = string)
- assertEquals(expected = original, actual = restored)
- }
-}
diff --git a/formats/json/jvmTest/src/kotlinx/serialization/json/ParallelJsonStressTest.kt b/formats/json/jvmTest/src/kotlinx/serialization/json/ParallelJsonStressTest.kt
deleted file mode 100644
index 3901aab..0000000
--- a/formats/json/jvmTest/src/kotlinx/serialization/json/ParallelJsonStressTest.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package kotlinx.serialization.json
-
-import kotlinx.coroutines.*
-import kotlinx.serialization.builtins.*
-import kotlinx.serialization.test.*
-import org.junit.*
-import kotlin.concurrent.*
-import kotlin.random.*
-
-class ParallelJsonStressTest : JsonTestBase() {
- private val iterations = 1_000_000
-
- @Test
- fun testDecodeInParallel() = runBlocking<Unit> {
- for (i in 1..1000) {
- launch(Dispatchers.Default) {
- val value = (1..10000).map { Random.nextDouble() }
- assertSerializedAndRestored(value, ListSerializer(Double.serializer()))
- }
- }
- }
-}
diff --git a/formats/json/nativeMain/src/kotlinx/serialization/json/JsonSchemaCache.kt b/formats/json/nativeMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
index 16e223a..4961997 100644
--- a/formats/json/nativeMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
+++ b/formats/json/nativeMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
@@ -5,8 +5,10 @@
package kotlinx.serialization.json
import kotlinx.serialization.json.internal.*
+import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.ref.*
import kotlin.random.*
+import kotlin.native.concurrent.*
/**
* This maps emulate thread-locality of DescriptorSchemaCache for Native.
@@ -25,6 +27,7 @@
/**
* Because WeakReference itself does not have proper equals/hashCode
*/
+@OptIn(ExperimentalNativeApi::class)
private class WeakJson(json: Json) {
private val ref = WeakReference(json)
private val initialHashCode = json.hashCode()
diff --git a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
new file mode 100644
index 0000000..03c9926
--- /dev/null
+++ b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt
@@ -0,0 +1,9 @@
+/*
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+package kotlinx.serialization.json.internal
+
+internal actual object CharArrayPoolBatchSize {
+ actual fun take(): CharArray = CharArray(BATCH_SIZE)
+ actual fun release(array: CharArray) = Unit
+}
diff --git a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt
new file mode 100644
index 0000000..4f2fff0
--- /dev/null
+++ b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2017-2023 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.InternalSerializationApi
+
+@InternalSerializationApi
+@Retention(AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.ANNOTATION_CLASS
+)
+public actual annotation class FormatLanguage(
+ public actual val value: String,
+ // default parameters are not used due to https://youtrack.jetbrains.com/issue/KT-25946/
+ public actual val prefix: String,
+ public actual val suffix: String,
+)
\ No newline at end of file
diff --git a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
deleted file mode 100644
index 1b79e27..0000000
--- a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package kotlinx.serialization.json.internal
-
-internal actual class JsonStringBuilder actual constructor() {
- private val sb = StringBuilder(128)
-
- actual fun append(value: Long) {
- sb.append(value)
- }
-
- actual fun append(ch: Char) {
- sb.append(ch)
- }
-
- actual fun append(string: String) {
- sb.append(string)
- }
-
- actual fun appendQuoted(string: String) {
- sb.printQuoted(string)
- }
-
- actual override fun toString(): String {
- return sb.toString()
- }
-
- actual fun release() {
- }
-}
diff --git a/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
new file mode 100644
index 0000000..137f3bc
--- /dev/null
+++ b/formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt
@@ -0,0 +1,29 @@
+package kotlinx.serialization.json.internal
+
+internal actual open class JsonToStringWriter actual constructor(): InternalJsonWriter {
+ private val sb = StringBuilder(128)
+
+ actual override fun writeLong(value: Long) {
+ sb.append(value)
+ }
+
+ actual override fun writeChar(char: Char) {
+ sb.append(char)
+ }
+
+ actual override fun write(text: String) {
+ sb.append(text)
+ }
+
+ actual override fun writeQuoted(text: String) {
+ sb.printQuoted(text)
+ }
+
+ actual override fun release() {
+ // nothing to flush
+ }
+
+ actual override fun toString(): String {
+ return sb.toString()
+ }
+}
diff --git a/formats/properties/commonMain/src/kotlinx/serialization/properties/Properties.kt b/formats/properties/commonMain/src/kotlinx/serialization/properties/Properties.kt
index 9d411ad..8760950 100644
--- a/formats/properties/commonMain/src/kotlinx/serialization/properties/Properties.kt
+++ b/formats/properties/commonMain/src/kotlinx/serialization/properties/Properties.kt
@@ -53,6 +53,19 @@
protected abstract fun encode(value: Any): Value
+ @Suppress("UNCHECKED_CAST")
+ final override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) {
+ if (serializer is AbstractPolymorphicSerializer<*>) {
+ val casted = serializer as AbstractPolymorphicSerializer<Any>
+ val actualSerializer = casted.findPolymorphicSerializer(this, value as Any)
+ encodeTaggedString(nested("type"), actualSerializer.descriptor.serialName)
+
+ return actualSerializer.serialize(this, value)
+ }
+
+ return serializer.serialize(this, value)
+ }
+
override fun encodeTaggedValue(tag: String, value: Any) {
map[tag] = encode(value)
}
@@ -89,6 +102,18 @@
return structure(descriptor).also { copyTagsTo(it) }
}
+ final override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
+ if (deserializer is AbstractPolymorphicSerializer<*>) {
+ val type = map[nested("type")]?.toString()
+ val actualSerializer: DeserializationStrategy<Any> = deserializer.findPolymorphicSerializer(this, type)
+
+ @Suppress("UNCHECKED_CAST")
+ return actualSerializer.deserialize(this) as T
+ }
+
+ return deserializer.deserialize(this)
+ }
+
final override fun decodeTaggedValue(tag: String): Value {
return map.getValue(tag)
}
@@ -188,7 +213,7 @@
* A [Properties] instance that can be used as default and does not have any [SerializersModule] installed.
*/
@ExperimentalSerializationApi
- public companion object Default : Properties(EmptySerializersModule, null)
+ public companion object Default : Properties(EmptySerializersModule(), null)
}
@OptIn(ExperimentalSerializationApi::class)
diff --git a/formats/properties/commonTest/src/kotlinx/serialization/properties/SealedClassSerializationFromPropertiesTest.kt b/formats/properties/commonTest/src/kotlinx/serialization/properties/SealedClassSerializationFromPropertiesTest.kt
new file mode 100644
index 0000000..f933232
--- /dev/null
+++ b/formats/properties/commonTest/src/kotlinx/serialization/properties/SealedClassSerializationFromPropertiesTest.kt
@@ -0,0 +1,118 @@
+package kotlinx.serialization.properties
+
+import kotlin.reflect.KProperty1
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+
+class SealedClassSerializationFromPropertiesTest {
+ @Serializable
+ sealed class BaseClass {
+ abstract val firstProperty: Long
+ abstract val secondProperty: String
+ }
+
+ @SerialName("FIRSTCHILD")
+ @Serializable
+ data class FirstChild(override val firstProperty: Long, override val secondProperty: String) : BaseClass()
+
+ @SerialName("SECONDCHILD")
+ @Serializable
+ data class SecondChild(override val firstProperty: Long, override val secondProperty: String) : BaseClass()
+
+ @Serializable
+ data class CompositeClass(val item: BaseClass)
+
+ @Test
+ fun testPropertiesDeserialization() {
+ val props = mapOf(
+ "type" to "FIRSTCHILD",
+ "firstProperty" to 1L,
+ "secondProperty" to "one"
+ )
+
+ val instance: BaseClass = Properties.decodeFromMap(props)
+
+ assertIs<FirstChild>(instance)
+ assertEquals(instance.firstProperty, 1)
+ assertEquals(instance.secondProperty, "one")
+ }
+
+ @Test
+ fun testPropertiesSerialization() {
+ val instance: BaseClass = FirstChild(
+ firstProperty = 1L, secondProperty = "one"
+ )
+
+ val instanceProperties = Properties.encodeToMap(instance)
+
+ assertEquals("FIRSTCHILD", instanceProperties["type"])
+ assertEquals(1L, instanceProperties["firstProperty"])
+ assertEquals("one", instanceProperties["secondProperty"])
+ }
+
+ @Test
+ fun testWrappedPropertiesDeserialization() {
+ val props = mapOf(
+ "0.type" to "FIRSTCHILD",
+ "0.firstProperty" to 1L,
+ "0.secondProperty" to "one",
+ "1.type" to "SECONDCHILD",
+ "1.firstProperty" to 2L,
+ "1.secondProperty" to "two"
+ )
+
+ val instances: List<BaseClass> = Properties.decodeFromMap(props)
+
+ val expected = listOf(FirstChild(1, "one"), SecondChild(2, "two"))
+ assertEquals(expected, instances)
+ }
+
+ @Test
+ fun testWrappedPropertiesSerialization() {
+ val instances: List<BaseClass> = listOf(
+ FirstChild(firstProperty = 1L, secondProperty = "one"),
+ SecondChild(firstProperty = 2L, secondProperty = "two")
+ )
+
+ val instanceProperties = Properties.encodeToMap(instances)
+
+ assertEquals("FIRSTCHILD", instanceProperties["0.type"])
+ assertEquals(1L, instanceProperties["0.firstProperty"])
+ assertEquals("one", instanceProperties["0.secondProperty"])
+ assertEquals("SECONDCHILD", instanceProperties["1.type"])
+ assertEquals(2L, instanceProperties["1.firstProperty"])
+ assertEquals("two", instanceProperties["1.secondProperty"])
+ }
+
+ @Test
+ fun testCompositeClassPropertiesDeserialization() {
+ val props = mapOf(
+ "item.type" to "SECONDCHILD",
+ "item.firstProperty" to 7L,
+ "item.secondProperty" to "nothing"
+ )
+
+ val composite: CompositeClass = Properties.decodeFromMap(props)
+
+ assertEquals(CompositeClass(SecondChild(7L, "nothing")), composite)
+ }
+
+ @Test
+ fun testCompositeClassPropertiesSerialization() {
+ val composite = CompositeClass(
+ item = FirstChild(
+ firstProperty = 5L,
+ secondProperty = "something"
+ )
+ )
+
+ val compositeProperties = Properties.encodeToMap(composite)
+
+ assertEquals("FIRSTCHILD", compositeProperties["item.type"])
+ assertEquals(5L, compositeProperties["item.firstProperty"])
+ assertEquals("something", compositeProperties["item.secondProperty"])
+ }
+}
\ No newline at end of file
diff --git a/formats/protobuf/api/kotlinx-serialization-protobuf.api b/formats/protobuf/api/kotlinx-serialization-protobuf.api
index 65093b2..c0d61b9 100644
--- a/formats/protobuf/api/kotlinx-serialization-protobuf.api
+++ b/formats/protobuf/api/kotlinx-serialization-protobuf.api
@@ -25,6 +25,7 @@
public static final field DEFAULT Lkotlinx/serialization/protobuf/ProtoIntegerType;
public static final field FIXED Lkotlinx/serialization/protobuf/ProtoIntegerType;
public static final field SIGNED Lkotlinx/serialization/protobuf/ProtoIntegerType;
+ public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lkotlinx/serialization/protobuf/ProtoIntegerType;
public static fun values ()[Lkotlinx/serialization/protobuf/ProtoIntegerType;
}
@@ -33,7 +34,7 @@
public abstract fun number ()I
}
-public final class kotlinx/serialization/protobuf/ProtoNumber$Impl : kotlinx/serialization/protobuf/ProtoNumber {
+public synthetic class kotlinx/serialization/protobuf/ProtoNumber$Impl : kotlinx/serialization/protobuf/ProtoNumber {
public fun <init> (I)V
public final synthetic fun number ()I
}
@@ -41,7 +42,7 @@
public abstract interface annotation class kotlinx/serialization/protobuf/ProtoPacked : java/lang/annotation/Annotation {
}
-public final class kotlinx/serialization/protobuf/ProtoPacked$Impl : kotlinx/serialization/protobuf/ProtoPacked {
+public synthetic class kotlinx/serialization/protobuf/ProtoPacked$Impl : kotlinx/serialization/protobuf/ProtoPacked {
public fun <init> ()V
}
@@ -49,7 +50,7 @@
public abstract fun type ()Lkotlinx/serialization/protobuf/ProtoIntegerType;
}
-public final class kotlinx/serialization/protobuf/ProtoType$Impl : kotlinx/serialization/protobuf/ProtoType {
+public synthetic class kotlinx/serialization/protobuf/ProtoType$Impl : kotlinx/serialization/protobuf/ProtoType {
public fun <init> (Lkotlinx/serialization/protobuf/ProtoIntegerType;)V
public final synthetic fun type ()Lkotlinx/serialization/protobuf/ProtoIntegerType;
}
diff --git a/formats/protobuf/build.gradle b/formats/protobuf/build.gradle
index c2183b2..9f93b18 100644
--- a/formats/protobuf/build.gradle
+++ b/formats/protobuf/build.gradle
@@ -21,8 +21,11 @@
}
kotlin {
-
sourceSets {
+ configureEach {
+ languageSettings.optIn("kotlinx.serialization.internal.CoreFriendModuleApi")
+ }
+
commonMain {
dependencies {
api project(":kotlinx-serialization-core")
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoBuf.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoBuf.kt
index 8f447ef..92bb2f5 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoBuf.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoBuf.kt
@@ -11,12 +11,14 @@
/**
* Implements [encoding][encodeToByteArray] and [decoding][decodeFromByteArray] classes to/from bytes
- * using [Proto2][https://developers.google.com/protocol-buffers/docs/proto] specification.
- * It is typically used by constructing an application-specific instance, with configured specific behaviour
+ * using [Protocol buffers](https://protobuf.dev/) specification.
+ * It is typically used by constructing an application-specific instance, with configured specific behavior
* and, if necessary, registered custom serializers (in [SerializersModule] provided by [serializersModule] constructor parameter).
+ * Default encoding is proto2, although proto3 can be used with a number of tweaks (see the section below for details).
+ *
*
* ### Correspondence between Protobuf message definitions and Kotlin classes
- * Given a ProtoBuf definition with one required field, one optional field and one optional field with a custom default
+ * Given a ProtoBuf definition with one required field, one optional field, and one optional field with a custom default
* value:
* ```
* message MyMessage {
@@ -32,27 +34,29 @@
* data class MyMessage(val first: Int, val second: Int = 0, val third: Int = 42)
* ```
*
- * By default, protobuf fields ids are being assigned to Kotlin properties in incremental order, i.e.
- * the first property in the class has id 1, the second has id 2, and so forth.
- * If you need a more stable order (e.g. to avoid breaking changes when reordering properties),
- * provide custom ids using [ProtoNumber] annotation.
+ * By default, protobuf fields numbers are being assigned to Kotlin properties in incremental order, i.e.,
+ * the first property in the class has number 1, the second has number 2, and so forth.
+ * If you need a more stable order (e.g., to avoid breaking changes when reordering properties),
+ * provide custom numbers using [ProtoNumber] annotation.
*
- * By default, all integer numbers are encoded using [varint][https://developers.google.com/protocol-buffers/docs/encoding#varints]
- * encoding. This behaviour can be changed via [ProtoType] annotation.
+ * By default, all integer values are encoded using [varint](https://protobuf.dev/programming-guides/encoding/#varints)
+ * encoding. This behavior can be changed via [ProtoType] annotation.
*
* ### Known caveats and limitations
* Lists are represented as repeated fields. Because format spec says that if the list is empty,
- * there are no elements in the stream with such tag, you must explicitly mark any
- * field of list type with default = emptyList(). Same for maps.
- * There's no special support for `oneof` protobuf fields. However, this implementation
+ * there are no elements in the stream with such tag, you have to explicitly add to any
+ * property of `List` type a default value equals to `emptyList()`. Same for maps.
+ * There is no special support for `oneof` protobuf fields. However, this implementation
* supports standard kotlinx.serialization's polymorphic and sealed serializers,
- * using their default form (message of serialName: string and other embedded message with actual content).
+ * using their default form (message consisting of `serialName: string` and other embedded message with actual content).
*
* ### Proto3 support
- * This implementation does not support repeated packed fields, so you won't be able to deserialize
- * Proto3 lists. However, other messages could be decoded. You have to remember that since fields in Proto3
- * messages by default are implicitly optional,
- * corresponding Kotlin properties have to be nullable with default value `null`.
+ *
+ * proto2 and proto3 specifications use the same encoding, so you can use this class to decode Proto3 messages.
+ * However, the message structure is slightly different, so you should remember the following:
+ *
+ * - In proto3, fields by default are implicitly optional, so corresponding Kotlin properties have to be nullable and have a default value `null`.
+ * - In proto3, all lists use packed encoding by default. To be able to decode them, annotation [ProtoPacked] should be used on all properties with type `List`.
*
* ### Usage example
* ```
@@ -112,6 +116,9 @@
* @param encodeDefaults specifies whether default values are encoded.
* False by default; meaning that properties with values equal to defaults will be elided.
* @param serializersModule application-specific [SerializersModule] to provide custom serializers.
+ * @see ProtoNumber
+ * @see ProtoType
+ * @see ProtoPacked
*/
@ExperimentalSerializationApi
public sealed class ProtoBuf(
@@ -122,7 +129,7 @@
/**
* The default instance of [ProtoBuf].
*/
- public companion object Default : ProtoBuf(false, EmptySerializersModule)
+ public companion object Default : ProtoBuf(false, EmptySerializersModule())
override fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray {
val output = ByteArrayOutput()
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoTypes.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoTypes.kt
index 3b62d4d..109ffb8 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoTypes.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoTypes.kt
@@ -11,7 +11,7 @@
* Specifies protobuf field number (a unique number for a field in the protobuf message)
* assigned to a Kotlin property.
*
- * See [https://developers.google.com/protocol-buffers/docs/proto#assigning-field-numbers]
+ * See [Assigning field numbers](https://protobuf.dev/programming-guides/proto2/#assigning) for details.
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
@@ -19,15 +19,14 @@
public annotation class ProtoNumber(public val number: Int)
/**
- * Represents a number format in protobuf encoding.
+ * Represents a number format in protobuf encoding set by [ProtoType] annotation.
*
* [DEFAULT] is default varint encoding (intXX),
* [SIGNED] is signed ZigZag representation (sintXX), and
* [FIXED] is fixedXX type.
* uintXX and sfixedXX are not supported yet.
*
- * See [https://developers.google.com/protocol-buffers/docs/proto#scalar]
- * @see ProtoType
+ * See [Scalar value types](https://protobuf.dev/programming-guides/proto2/#scalar) for details.
*/
@Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING")
@ExperimentalSerializationApi
@@ -48,7 +47,7 @@
/**
- * Instructs that a particular collection should be written as [packed array](https://developers.google.com/protocol-buffers/docs/encoding#packed)
+ * Instructs that a particular collection should be written as a [packed array](https://protobuf.dev/programming-guides/encoding/#packed).
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt
index 0977391..861e2bf 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt
@@ -3,7 +3,7 @@
*/
@file:OptIn(ExperimentalSerializationApi::class)
-@file:Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
+@file:Suppress("UNCHECKED_CAST")
package kotlinx.serialization.protobuf.internal
@@ -80,7 +80,7 @@
private fun findIndexByTag(descriptor: SerialDescriptor, protoTag: Int): Int {
// Fast-path: tags are incremental, 1-based
- if (protoTag < descriptor.elementsCount) {
+ if (protoTag < descriptor.elementsCount && protoTag >= 0) {
val protoId = extractProtoId(descriptor, protoTag, true)
if (protoId == protoTag) return protoTag
}
@@ -213,7 +213,7 @@
val mapEntrySerial =
kotlinx.serialization.builtins.MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val oldSet = (previousValue as? Map<Any?, Any?>)?.entries
- val setOfEntries = LinkedHashSetSerializer(mapEntrySerial).merge(this, oldSet)
+ val setOfEntries = (SetSerializer(mapEntrySerial) as AbstractCollectionSerializer<Map.Entry<Any?, Any?>, Set<Map.Entry<Any?, Any?>>, *>).merge(this, oldSet)
return setOfEntries.associateBy({ it.key }, { it.value }) as T
}
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedDecoder.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedDecoder.kt
index 8a5a382..953c1b3 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedDecoder.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedDecoder.kt
@@ -94,8 +94,8 @@
}
}
- override fun decodeInline(inlineDescriptor: SerialDescriptor): Decoder {
- return decodeTaggedInline(popTag(), inlineDescriptor)
+ override fun decodeInline(descriptor: SerialDescriptor): Decoder {
+ return decodeTaggedInline(popTag(), descriptor)
}
override fun decodeInlineElement(
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt
index 01532df..84e5839 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt
@@ -154,8 +154,8 @@
encodeNullableSerializableValue(serializer, value)
}
- override fun encodeInline(inlineDescriptor: SerialDescriptor): Encoder {
- return encodeTaggedInline(popTag(), inlineDescriptor)
+ override fun encodeInline(descriptor: SerialDescriptor): Encoder {
+ return encodeTaggedInline(popTag(), descriptor)
}
override fun encodeInlineElement(descriptor: SerialDescriptor, index: Int): Encoder {
diff --git a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/SuppressAnimalSniffer.kt b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/SuppressAnimalSniffer.kt
new file mode 100644
index 0000000..35607ec
--- /dev/null
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/SuppressAnimalSniffer.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.internal
+
+/**
+ * Suppresses Animal Sniffer plugin errors for certain methods.
+ * Such methods include references to Java 8 methods that are not
+ * available in Android API, but can be desugared by R8.
+ */
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
+internal annotation class SuppressAnimalSniffer
\ No newline at end of file
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 e54370f..4f4ca9c 100644
--- a/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt
+++ b/formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt
@@ -190,7 +190,7 @@
val annotations = messageDescriptor.getElementAnnotations(index)
- val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index + 1
+ val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: (index + 1)
if (!usedNumbers.add(number)) {
throw IllegalArgumentException("Field number $number is repeated in the class with serial name ${messageDescriptor.serialName}")
}
@@ -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
}
}
@@ -319,19 +324,35 @@
}
val safeSerialName = removeLineBreaks(enumDescriptor.serialName)
if (safeSerialName != enumName) {
- append("// serial name '").append(enumName).appendLine('\'')
+ append("// serial name '").append(safeSerialName).appendLine('\'')
}
append("enum ").append(enumName).appendLine(" {")
- enumDescriptor.elementDescriptors.forEachIndexed { number, element ->
+ val usedNumbers: MutableSet<Int> = mutableSetOf()
+ val duplicatedNumbers: MutableSet<Int> = mutableSetOf()
+ enumDescriptor.elementDescriptors.forEachIndexed { index, element ->
val elementName = element.protobufEnumElementName
elementName.checkIsValidIdentifier {
"The enum element name '$elementName' is invalid in the " +
"protobuf schema. Serial name of the enum class '${enumDescriptor.serialName}'"
}
+
+ val annotations = enumDescriptor.getElementAnnotations(index)
+ val number = annotations.filterIsInstance<ProtoNumber>().singleOrNull()?.number ?: index
+ if (!usedNumbers.add(number)) {
+ duplicatedNumbers.add(number)
+ }
+
append(" ").append(elementName).append(" = ").append(number).appendLine(';')
}
+ if (duplicatedNumbers.isNotEmpty()) {
+ throw IllegalArgumentException(
+ "The class with serial name ${enumDescriptor.serialName} has duplicate " +
+ "elements with numbers $duplicatedNumbers"
+ )
+ }
+
appendLine('}')
}
@@ -417,6 +438,7 @@
}
}
+ @SuppressAnimalSniffer // Boolean.hashCode(boolean) in compiler-generated hashCode implementation
private data class TypeDefinition(
val descriptor: SerialDescriptor,
val isSynthetic: Boolean = false,
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
index 7ef9729..eb6ebe7 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -46,7 +46,6 @@
@Serializable
data class PolyBox(@Polymorphic val boxed: SimpleAbstract)
-@SharedImmutable
val SimplePolymorphicModule = SerializersModule {
polymorphic(SimpleAbstract::class) {
subclass(SimpleIntInheritor.serializer())
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNothingTest.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNothingTest.kt
new file mode 100644
index 0000000..e90ff2b
--- /dev/null
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNothingTest.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf
+
+import kotlinx.serialization.*
+import kotlinx.serialization.test.*
+import kotlin.test.*
+
+class ProtobufNothingTest {
+ @Serializable
+ /*private*/ data class NullableNothingBox(val value: Nothing?) // `private` doesn't work on the JS legacy target
+
+ @Serializable
+ private data class ParameterizedBox<T : Any>(val value: T?)
+
+ private inline fun <reified T> testConversion(data: T, expectedHexString: String) {
+ val string = ProtoBuf.encodeToHexString(data).uppercase()
+ assertEquals(expectedHexString, string)
+ assertEquals(data, ProtoBuf.decodeFromHexString(string))
+ }
+
+ @Test
+ fun testNothing() {
+ testConversion(NullableNothingBox(null), "")
+ testConversion(ParameterizedBox(null), "")
+ }
+}
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNullAndDefaultTest.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNullAndDefaultTest.kt
index 88450aa..da7521c 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNullAndDefaultTest.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNullAndDefaultTest.kt
@@ -5,7 +5,6 @@
package kotlinx.serialization.protobuf
import kotlinx.serialization.*
-import kotlinx.serialization.test.isJsLegacy
import kotlin.test.*
class ProtobufNullAndDefaultTest {
@@ -22,7 +21,6 @@
fun testProtobufDropDefaults() {
val proto = ProtoBuf { encodeDefaults = false }
assertEquals(0, proto.encodeToByteArray(ProtoWithNullDefault()).size)
- if (isJsLegacy()) return // because of @EncodeDefault
assertFailsWith<SerializationException> { proto.encodeToByteArray(ProtoWithNullDefaultAlways()) }
assertEquals(0, proto.encodeToByteArray(ProtoWithNullDefaultNever()).size)
}
@@ -31,7 +29,6 @@
fun testProtobufEncodeDefaults() {
val proto = ProtoBuf { encodeDefaults = true }
assertFailsWith<SerializationException> { proto.encodeToByteArray(ProtoWithNullDefault()) }
- if (isJsLegacy()) return // because of @EncodeDefault
assertFailsWith<SerializationException> { proto.encodeToByteArray(ProtoWithNullDefaultAlways()) }
assertEquals(0, proto.encodeToByteArray(ProtoWithNullDefaultNever()).size)
}
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/schema/SchemaValidationsTest.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/schema/SchemaValidationsTest.kt
index b733231..0330250 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/schema/SchemaValidationsTest.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/schema/SchemaValidationsTest.kt
@@ -3,7 +3,6 @@
import kotlinx.serialization.*
import kotlinx.serialization.protobuf.*
import kotlin.test.Test
-import kotlin.test.assertContains
import kotlin.test.assertFailsWith
class SchemaValidationsTest {
@@ -39,6 +38,33 @@
SECOND
}
+ @Serializable
+ enum class EnumWithExplicitProtoNumberDuplicate {
+ @ProtoNumber(2)
+ FIRST,
+ @ProtoNumber(2)
+ SECOND,
+ }
+
+ @Serializable
+ enum class EnumWithImplicitProtoNumberDuplicate {
+ FIRST,
+ @ProtoNumber(0)
+ SECOND,
+ }
+
+ @Test
+ fun testExplicitDuplicateEnumElementProtoNumber() {
+ val descriptors = listOf(EnumWithExplicitProtoNumberDuplicate.serializer().descriptor)
+ assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
+ }
+
+ @Test
+ fun testImplicitDuplicateEnumElementProtoNumber() {
+ val descriptors = listOf(EnumWithImplicitProtoNumberDuplicate.serializer().descriptor)
+ assertFailsWith(IllegalArgumentException::class) { ProtoBufSchemaGenerator.generateSchemaText(descriptors) }
+ }
+
@Test
fun testInvalidEnumElementSerialName() {
val descriptors = listOf(InvalidEnumElementName.serializer().descriptor)
diff --git a/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt b/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
index c4a6b98..e746679 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt
@@ -5,12 +5,13 @@
package kotlinx.serialization.test
enum class Platform {
- JVM, JS_LEGACY, JS_IR, NATIVE
+ JVM, JS, NATIVE, WASM
}
public expect val currentPlatform: Platform
-public fun isJs(): Boolean = currentPlatform == Platform.JS_LEGACY || currentPlatform == Platform.JS_IR
-public fun isJsLegacy(): Boolean = currentPlatform == Platform.JS_LEGACY
+public fun isJs(): Boolean = currentPlatform == Platform.JS
+
public fun isJvm(): Boolean = currentPlatform == Platform.JVM
public fun isNative(): Boolean = currentPlatform == Platform.NATIVE
+public fun isWasm(): Boolean = currentPlatform == Platform.WASM
\ No newline at end of file
diff --git a/formats/protobuf/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/protobuf/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index abbac9d..0cde699 100644
--- a/formats/protobuf/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/formats/protobuf/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -4,9 +4,4 @@
package kotlinx.serialization.test
-public actual val currentPlatform: Platform = if (isLegacyBackend()) Platform.JS_LEGACY else Platform.JS_IR
-
-// from https://github.com/JetBrains/kotlin/blob/569187a7516e2e5ab217158a3170d4beb0c5cb5a/js/js.translator/testData/_commonFiles/testUtils.kt#L3
-private fun isLegacyBackend(): Boolean =
- // Using eval to prevent DCE from thinking that following code depends on Kotlin module.
- eval("(typeof Kotlin != \"undefined\" && typeof Kotlin.kotlin != \"undefined\")").unsafeCast<Boolean>()
+public actual val currentPlatform: Platform = Platform.JS
diff --git a/formats/protobuf/jvmTest/resources/EnumWithProtoNumber.proto b/formats/protobuf/jvmTest/resources/EnumWithProtoNumber.proto
new file mode 100644
index 0000000..2152803
--- /dev/null
+++ b/formats/protobuf/jvmTest/resources/EnumWithProtoNumber.proto
@@ -0,0 +1,11 @@
+syntax = "proto2";
+
+package kotlinx.serialization.protobuf.schema.generator;
+
+// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.EnumWithProtoNumber'
+enum EnumWithProtoNumber {
+ ZERO = 0;
+ THREE = 3;
+ TWO = 2;
+ FIVE = 5;
+}
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 e8a0b4c..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'
@@ -131,6 +143,14 @@
map<int32, int32> nullableOptionalMap = 8;
}
+// serial name 'kotlinx.serialization.protobuf.schema.GenerationTest.EnumWithProtoNumber'
+enum EnumWithProtoNumber {
+ ZERO = 0;
+ THREE = 3;
+ TWO = 2;
+ FIVE = 5;
+}
+
enum OverriddenEnumName {
FIRST = 0;
OverriddenElementName = 1;
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/PolymorphicWithJvmClassTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/PolymorphicWithJvmClassTest.kt
index df1a3fd..ae2d593 100644
--- a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/PolymorphicWithJvmClassTest.kt
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/PolymorphicWithJvmClassTest.kt
@@ -17,7 +17,6 @@
@Serializable
data class DateWrapper(@ProtoNumber(1) @Polymorphic val date: Date)
- @Serializer(forClass = Date::class)
object DateSerializer : KSerializer<Date> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("java.util.Date", PrimitiveKind.STRING)
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/RandomTests.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/RandomTests.kt
index 0c94c6e..2a12424 100644
--- a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/RandomTests.kt
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/RandomTests.kt
@@ -162,11 +162,11 @@
}
}
- enum class KCoffee { AMERICANO, LATTE, CAPPUCCINO }
+ enum class KCoffee(val value: Int) { AMERICANO(0), LATTE(1), CAPPUCCINO(2), @ProtoNumber(-1) NO_COFFEE(-1) }
@Serializable
data class KTestEnum(@ProtoNumber(1) val a: KCoffee): IMessage {
- override fun toProtobufMessage() = TestEnum.newBuilder().setA(TestEnum.Coffee.forNumber(a.ordinal)).build()
+ override fun toProtobufMessage() = TestEnum.newBuilder().setA(TestEnum.Coffee.forNumber(a.value)).build()
companion object : Gen<KTestEnum> {
override fun generate(): KTestEnum = KTestEnum(Gen.oneOf<KCoffee>().generate())
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt
new file mode 100644
index 0000000..7b2dda2
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Enum(
+ @ProtoNumber(21) val optionalNestedEnum: KNestedEnum = KNestedEnum.FOO,
+ @ProtoNumber(22) val optionalForeignEnum: KForeignEnum = KForeignEnum.FOREIGN_FOO,
+ @ProtoNumber(23) val optionalAliasedEnum: KAliasedEnum = KAliasedEnum.ALIAS_FOO,
+) {
+ enum class KNestedEnum {
+ @ProtoNumber(0)
+ FOO,
+
+ @ProtoNumber(1)
+ BAR,
+
+ @ProtoNumber(2)
+ BAZ,
+
+ @ProtoNumber(-1)
+ NEG;
+
+ fun toProto() = TestMessagesProto3.TestAllTypesProto3.NestedEnum.valueOf(this.name)
+ }
+
+
+ enum class KAliasedEnum {
+ @ProtoNumber(0)
+ ALIAS_FOO,
+
+ @ProtoNumber(1)
+ ALIAS_BAR,
+
+ @ProtoNumber(2)
+ ALIAS_BAZ,
+
+ @ProtoNumber(2)
+ MOO,
+
+ @ProtoNumber(2)
+ moo,
+
+ @ProtoNumber(2)
+ bAz;
+
+ fun toProto() = TestMessagesProto3.TestAllTypesProto3.AliasedEnum.valueOf(this.name)
+ }
+}
+
+enum class KForeignEnum {
+ @ProtoNumber(0)
+ FOREIGN_FOO,
+
+ @ProtoNumber(1)
+ FOREIGN_BAR,
+
+ @ProtoNumber(2)
+ FOREIGN_BAZ;
+
+ fun toProto() = TestMessagesProto3.ForeignEnum.valueOf(this.name)
+}
+
+class Proto3EnumTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Enum(
+ optionalNestedEnum = KTestMessagesProto3Enum.KNestedEnum.NEG,
+ optionalForeignEnum = KForeignEnum.FOREIGN_BAR,
+ optionalAliasedEnum = KTestMessagesProto3Enum.KAliasedEnum.ALIAS_BAR
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.optionalNestedEnum.toProto(), restored.optionalNestedEnum)
+ assertEquals(message.optionalForeignEnum.toProto(), restored.optionalForeignEnum)
+ assertEquals(message.optionalAliasedEnum.toProto(), restored.optionalAliasedEnum)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Enum>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt
new file mode 100644
index 0000000..a961424
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Map(
+ @ProtoNumber(56) val mapInt32Int32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(57) val mapInt64Int64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(58) val mapUint32Uint32: Map<UInt, UInt> = emptyMap(),
+ @ProtoNumber(59) val mapUint64Uint64: Map<ULong, ULong> = emptyMap(),
+ @ProtoNumber(60) val mapSint32Sint32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(61) val mapSint64Sint64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(62) val mapFixed32Fixed32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(63) val mapFixed64Fixed64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(64) val mapSfixed32Sfixed32: Map<Int, Int> = emptyMap(),
+ @ProtoNumber(65) val mapSfixed64Sfixed64: Map<Long, Long> = emptyMap(),
+ @ProtoNumber(66) val mapInt32Float: Map<Int, Float> = emptyMap(),
+ @ProtoNumber(67) val mapInt32Double: Map<Int, Double> = emptyMap(),
+ @ProtoNumber(68) val mapBoolBool: Map<Boolean, Boolean> = emptyMap(),
+ @ProtoNumber(69) val mapStringString: Map<String, String> = emptyMap(),
+ @ProtoNumber(70) val mapStringBytes: Map<String, ByteArray> = emptyMap(),
+ @ProtoNumber(71) val mapStringNestedMessage: Map<String, KTestMessagesProto3Message.KNestedMessage> = emptyMap(),
+ @ProtoNumber(72) val mapStringForeignMessage: Map<String, KForeignMessage> = emptyMap(),
+ @ProtoNumber(73) val mapStringNestedEnum: Map<String, KTestMessagesProto3Enum.KNestedEnum> = emptyMap(),
+ @ProtoNumber(74) val mapStringForeignEnum: Map<String, KForeignEnum> = emptyMap(),
+)
+
+class Proto3MapTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Map(
+ mapInt32Int32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapInt64Int64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ mapUint32Uint32 = Gen.map(Gen.int().map { it.toUInt() }, Gen.int().map { it.toUInt() }).generate(),
+ mapUint64Uint64 = Gen.map(Gen.int().map { it.toULong() }, Gen.int().map { it.toULong() }).generate(),
+ mapInt32Float = Gen.map(Gen.int(), Gen.float()).generate(),
+ mapInt32Double = Gen.map(Gen.int(), Gen.double()).generate(),
+ mapBoolBool = Gen.map(Gen.bool(), Gen.bool()).generate(),
+ mapStringString = Gen.map(Gen.string(), Gen.string()).generate(),
+ mapStringBytes = Gen.map(Gen.string(), Gen.string().map { it.toByteArray() }).generate(),
+ mapStringNestedMessage = mapOf(
+ "asd_1" to KTestMessagesProto3Message.KNestedMessage(
+ 1,
+ null
+ ),
+ "asi_#" to KTestMessagesProto3Message.KNestedMessage(
+ 2,
+ KTestMessagesProto3Message(
+ KTestMessagesProto3Message.KNestedMessage(3, null),
+ )
+ )
+ ),
+ mapStringForeignMessage = mapOf(
+ "" to KForeignMessage(1),
+ "-2" to KForeignMessage(-12),
+ ),
+ mapStringNestedEnum = Gen.map(
+ Gen.string(), Gen.oneOf(
+ KTestMessagesProto3Enum.KNestedEnum.entries,
+ )
+ ).generate(),
+ mapStringForeignEnum = Gen.map(
+ Gen.string(), Gen.oneOf(
+ KForeignEnum.entries,
+ )
+ ).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+
+ assertEquals(message.mapInt32Int32, restored.mapInt32Int32Map)
+ assertEquals(message.mapInt64Int64, restored.mapInt64Int64Map)
+ assertEquals(
+ message.mapUint32Uint32,
+ restored.mapUint32Uint32Map.map { it.key.toUInt() to it.value.toUInt() }.toMap()
+ )
+ assertEquals(
+ message.mapUint64Uint64,
+ restored.mapUint64Uint64Map.map { it.key.toULong() to it.value.toULong() }.toMap()
+ )
+ assertEquals(message.mapInt32Float, restored.mapInt32FloatMap)
+ assertEquals(message.mapInt32Double, restored.mapInt32DoubleMap)
+ assertEquals(message.mapBoolBool, restored.mapBoolBoolMap)
+ assertEquals(message.mapStringString, restored.mapStringStringMap)
+ assertContentEquals(
+ message.mapStringBytes.mapValues { it.value.toString(Charsets.UTF_32) }.entries.toList(),
+ restored.mapStringBytesMap.mapValues { it.value.toByteArray().toString(Charsets.UTF_32) }.entries.toList()
+ )
+ assertEquals(
+ message.mapStringNestedMessage.mapValues { it.value.toProto() },
+ restored.mapStringNestedMessageMap
+ )
+ assertEquals(
+ message.mapStringForeignMessage.mapValues { it.value.toProto() },
+ restored.mapStringForeignMessageMap
+ )
+ assertEquals(
+ message.mapStringNestedEnum.mapValues { it.value.name },
+ restored.mapStringNestedEnumMap.mapValues { it.value.name },
+ )
+ assertEquals(
+ message.mapStringForeignEnum.mapValues { it.value.name },
+ restored.mapStringForeignEnumMap.mapValues { it.value.name }
+ )
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Map>(restored.toByteArray())
+ assertEquals(message.copy(mapStringBytes = mapOf()), restoredMessage.copy(mapStringBytes = mapOf()))
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2417
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Map(
+ mapSint32Sint32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapSint64Sint64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ mapFixed32Fixed32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapFixed64Fixed64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ mapSfixed32Sfixed32 = Gen.map(Gen.int(), Gen.int()).generate(),
+ mapSfixed64Sfixed64 = Gen.map(Gen.long(), Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+
+ assertContentEquals(message.mapSint32Sint32.entries.toList(), restored.mapSint32Sint32Map.entries.toList())
+ assertContentEquals(message.mapSint64Sint64.entries.toList(), restored.mapSint64Sint64Map.entries.toList())
+ assertContentEquals(message.mapFixed32Fixed32.entries.toList(), restored.mapFixed32Fixed32Map.entries.toList())
+ assertContentEquals(message.mapFixed64Fixed64.entries.toList(), restored.mapFixed64Fixed64Map.entries.toList())
+ assertContentEquals(
+ message.mapSfixed32Sfixed32.entries.toList(),
+ restored.mapSfixed32Sfixed32Map.entries.toList()
+ )
+ assertContentEquals(
+ message.mapSfixed64Sfixed64.entries.toList(),
+ restored.mapSfixed64Sfixed64Map.entries.toList()
+ )
+
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Map>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt
new file mode 100644
index 0000000..c369d6e
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Message(
+ @ProtoNumber(18) val optionalNestedMessage: KNestedMessage? = null,
+ @ProtoNumber(19) val optionalForeignMessage: KForeignMessage? = null,
+) {
+ @Serializable
+ data class KNestedMessage(
+ @ProtoNumber(1) val a: Int = 0,
+ @ProtoNumber(2) val corecursive: KTestMessagesProto3Message? = null,
+ ) {
+ fun toProto(): TestMessagesProto3.TestAllTypesProto3.NestedMessage =
+ TestMessagesProto3.TestAllTypesProto3.NestedMessage.parseFrom(
+ ProtoBuf.encodeToByteArray(this)
+ )
+ }
+}
+
+@Serializable
+data class KForeignMessage(
+ @ProtoNumber(1) val c: Int = 0,
+) {
+ fun toProto(): TestMessagesProto3.ForeignMessage =
+ TestMessagesProto3.ForeignMessage.parseFrom(
+ ProtoBuf.encodeToByteArray(this)
+ )
+}
+
+class Proto3MessageTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Message(
+ optionalNestedMessage = KTestMessagesProto3Message.KNestedMessage(
+ a = 150,
+ corecursive = KTestMessagesProto3Message(
+ optionalNestedMessage = KTestMessagesProto3Message.KNestedMessage(
+ a = 42,
+ )
+ )
+ ),
+ optionalForeignMessage = KForeignMessage(
+ c = 150,
+ )
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+ assertEquals(message.optionalNestedMessage?.a, restored.optionalNestedMessage.a)
+ assertEquals(
+ message.optionalNestedMessage?.corecursive?.optionalNestedMessage?.a,
+ restored.optionalNestedMessage.corecursive.optionalNestedMessage.a
+ )
+ assertEquals(message.optionalForeignMessage?.c, restored.optionalForeignMessage.c)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt
new file mode 100644
index 0000000..fda811e
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessageProto3Oneof(
+ @ProtoNumber(111) val oneofUint32: UInt? = null,
+ @ProtoNumber(112) val oneofNestedMessage: KTestMessagesProto3Message.KNestedMessage? = null,
+ @ProtoNumber(113) val oneofString: String? = null,
+ @ProtoNumber(114) val oneofBytes: ByteArray? = null,
+ @ProtoNumber(115) val oneofBool: Boolean? = null,
+ @ProtoNumber(116) val oneofUint64: ULong? = null,
+ @ProtoNumber(117) val oneofFloat: Float? = null,
+ @ProtoNumber(118) val oneofDouble: Double? = null,
+ @ProtoNumber(119) val oneofEnum: KTestMessagesProto3Enum.KNestedEnum? = null,
+) {
+ init {
+ require(
+ listOf(
+ oneofUint32,
+ oneofNestedMessage,
+ oneofString,
+ oneofBytes,
+ oneofBool,
+ oneofUint64,
+ oneofFloat,
+ oneofDouble,
+ oneofEnum,
+ ).count { it != null } == 1
+ )
+ }
+}
+
+class Proto3OneofTest {
+
+ /**
+ * Verify that the given [KTestMessageProto3Oneof] is correctly encoded and decoded as
+ * [TestMessagesProto3.TestAllTypesProto3] by running the [verificationFunction]. This
+ * method also verifies that the encoded and decoded message is equal to the original message.
+ *
+ * @param verificationFunction a function that verifies the encoded and decoded message. First parameter
+ * is the original message and the second parameter is the decoded protobuf library message.
+ * @receiver the [KTestMessageProto3Oneof] to verify
+ */
+ private fun KTestMessageProto3Oneof.verify(
+ verificationFunction: (KTestMessageProto3Oneof, TestMessagesProto3.TestAllTypesProto3) -> Unit,
+ ) {
+ val bytes = ProtoBuf.encodeToByteArray(this)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ verificationFunction.invoke(this, restored)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessageProto3Oneof>(restored.toByteArray())
+
+ // [equals] method is not implemented for [ByteArray] so we need to compare it separately.
+ assertEquals(this, restoredMessage.copy(oneofBytes = this.oneofBytes))
+ assertContentEquals(this.oneofBytes, restoredMessage.oneofBytes)
+ }
+
+ @Test
+ fun uint32() {
+ KTestMessageProto3Oneof(oneofUint32 = 150u).verify { self, restored ->
+ assertEquals(self.oneofUint32, restored.oneofUint32.toUInt())
+ }
+ }
+
+ @Test
+ fun nestedMessage() {
+ KTestMessageProto3Oneof(
+ oneofNestedMessage = KTestMessagesProto3Message.KNestedMessage(a = 150),
+ ).verify { self, restored ->
+ assertEquals(self.oneofNestedMessage?.a, restored.oneofNestedMessage.a)
+ }
+ }
+
+ @Test
+ fun string() {
+ KTestMessageProto3Oneof(oneofString = "150").verify { self, restored ->
+ assertEquals(self.oneofString, restored.oneofString)
+ }
+ }
+
+ @Test
+ fun bytes() {
+ KTestMessageProto3Oneof(oneofBytes = "150".toByteArray()).verify { self, restored ->
+ assertContentEquals(self.oneofBytes, restored.oneofBytes.toByteArray())
+ }
+ }
+
+ @Test
+ fun bool() {
+ KTestMessageProto3Oneof(oneofBool = true).verify { self, restored ->
+ assertEquals(self.oneofBool, restored.oneofBool)
+ }
+ }
+
+ @Test
+ fun uint64() {
+ KTestMessageProto3Oneof(oneofUint64 = 150uL).verify { self, restored ->
+ assertEquals(self.oneofUint64, restored.oneofUint64.toULong())
+ }
+ }
+
+ @Test
+ fun float() {
+ KTestMessageProto3Oneof(oneofFloat = 150f).verify { self, restored ->
+ assertEquals(self.oneofFloat, restored.oneofFloat)
+ }
+ }
+
+ @Test
+ fun double() {
+ KTestMessageProto3Oneof(oneofDouble = 150.0).verify { self, restored ->
+ assertEquals(self.oneofDouble, restored.oneofDouble)
+ }
+ }
+
+ @Test
+ fun enum() {
+ KTestMessageProto3Oneof(oneofEnum = KTestMessagesProto3Enum.KNestedEnum.BAR).verify { self, restored ->
+ assertEquals(self.oneofEnum?.name, restored.oneofEnum.name)
+ }
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt
new file mode 100644
index 0000000..e0da0bb
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Packed(
+ @ProtoNumber(75) @ProtoPacked val packedInt32: List<Int> = emptyList(),
+ @ProtoNumber(76) @ProtoPacked val packedInt64: List<Long> = emptyList(),
+ @ProtoNumber(77) @ProtoPacked val packedUint32: List<UInt> = emptyList(),
+ @ProtoNumber(78) @ProtoPacked val packedUint64: List<ULong> = emptyList(),
+ @ProtoNumber(79) @ProtoPacked val packedSint32: List<Int> = emptyList(),
+ @ProtoNumber(80) @ProtoPacked val packedSint64: List<Long> = emptyList(),
+ @ProtoNumber(81) @ProtoPacked val packedFixed32: List<Int> = emptyList(),
+ @ProtoNumber(82) @ProtoPacked val packedFixed64: List<Long> = emptyList(),
+ @ProtoNumber(83) @ProtoPacked val packedSfixed32: List<Int> = emptyList(),
+ @ProtoNumber(84) @ProtoPacked val packedSfixed64: List<Long> = emptyList(),
+ @ProtoNumber(85) @ProtoPacked val packedFloat: List<Float> = emptyList(),
+ @ProtoNumber(86) @ProtoPacked val packedDouble: List<Double> = emptyList(),
+ @ProtoNumber(87) @ProtoPacked val packedBool: List<Boolean> = emptyList(),
+)
+
+class Proto3PackedTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Packed(
+ packedInt32 = Gen.list(Gen.int()).generate(),
+ packedInt64 = Gen.list(Gen.long()).generate(),
+ packedFloat = Gen.list(Gen.float()).generate(),
+ packedDouble = Gen.list(Gen.double()).generate(),
+ packedBool = Gen.list(Gen.bool()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.packedInt32, restored.packedInt32List)
+ assertEquals(message.packedInt64, restored.packedInt64List)
+ assertEquals(message.packedFloat, restored.packedFloatList)
+ assertEquals(message.packedDouble, restored.packedDoubleList)
+ assertEquals(message.packedBool, restored.packedBoolList)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Packed>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2419
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Packed(
+ packedSint32 = Gen.list(Gen.int()).generate(),
+ packedSint64 = Gen.list(Gen.long()).generate(),
+ packedFixed32 = Gen.list(Gen.int()).generate(),
+ packedFixed64 = Gen.list(Gen.long()).generate(),
+ packedSfixed32 = Gen.list(Gen.int()).generate(),
+ packedSfixed64 = Gen.list(Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.packedSint32, restored.packedSint32List)
+ assertEquals(message.packedSint64, restored.packedSint64List)
+ assertEquals(message.packedFixed32, restored.packedFixed32List)
+ assertEquals(message.packedFixed64, restored.packedFixed64List)
+ assertEquals(message.packedSfixed32, restored.packedSfixed32List)
+ assertEquals(message.packedSfixed64, restored.packedSfixed64List)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Packed>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2418
+ fun unsigned() {
+ val message = KTestMessagesProto3Packed(
+ packedUint32 = Gen.list(Gen.int().map { it.toUInt() }).generate(),
+ packedUint64 = Gen.list(Gen.long().map { it.toULong() }).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.packedUint32, restored.packedUint32List.map { it.toUInt() })
+ assertEquals(message.packedUint64, restored.packedUint64List.map { it.toULong() })
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Packed>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt
new file mode 100644
index 0000000..a7363f8
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Primitive(
+ @ProtoNumber(1) val optionalInt32: Int = 0,
+ @ProtoNumber(2) val optionalInt64: Long = 0,
+ @ProtoNumber(3) val optionalUint32: UInt = 0U,
+ @ProtoNumber(4) val optionalUint64: ULong = 0UL,
+ @ProtoNumber(5) @ProtoType(ProtoIntegerType.SIGNED) val optionalSint32: Int = 0,
+ @ProtoNumber(6) @ProtoType(ProtoIntegerType.SIGNED) val optionalSint64: Long = 0,
+ @ProtoNumber(7) @ProtoType(ProtoIntegerType.FIXED) val optionalFixed32: Int = 0,
+ @ProtoNumber(8) @ProtoType(ProtoIntegerType.FIXED) val optionalFixed64: Long = 0,
+ @ProtoNumber(9) @ProtoType(ProtoIntegerType.FIXED) val optionalSfixed32: Int = 0,
+ @ProtoNumber(10) @ProtoType(ProtoIntegerType.FIXED) val optionalSfixed64: Long = 0,
+ @ProtoNumber(11) val optionalFloat: Float = 0.0f,
+ @ProtoNumber(12) val optionalDouble: Double = 0.0,
+ @ProtoNumber(13) val optionalBool: Boolean = false,
+ @ProtoNumber(14) val optionalString: String = "",
+ @ProtoNumber(15) val optionalBytes: ByteArray = byteArrayOf(),
+)
+
+class Proto3PrimitiveTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Primitive(
+ optionalInt32 = Int.MAX_VALUE,
+ optionalInt64 = Long.MAX_VALUE,
+ optionalUint32 = UInt.MAX_VALUE,
+ optionalUint64 = ULong.MAX_VALUE,
+ optionalSint32 = Int.MAX_VALUE,
+ optionalSint64 = Long.MAX_VALUE,
+ optionalFixed32 = Int.MAX_VALUE,
+ optionalFixed64 = Long.MAX_VALUE,
+ optionalSfixed32 = Int.MAX_VALUE,
+ optionalSfixed64 = Long.MAX_VALUE,
+ optionalFloat = Float.MAX_VALUE,
+ optionalDouble = Double.MAX_VALUE,
+ optionalBool = true,
+ optionalString = "string",
+ optionalBytes = byteArrayOf(1, 2, 3, 4, 5)
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.optionalInt32, restored.optionalInt32)
+ assertEquals(message.optionalInt64, restored.optionalInt64)
+ assertEquals(message.optionalUint32, restored.optionalUint32.toUInt())
+ assertEquals(message.optionalUint64, restored.optionalUint64.toULong())
+ assertEquals(message.optionalSint32, restored.optionalSint32)
+ assertEquals(message.optionalSint64, restored.optionalSint64)
+ assertEquals(message.optionalFixed32, restored.optionalFixed32)
+ assertEquals(message.optionalFixed64, restored.optionalFixed64)
+ assertEquals(message.optionalSfixed32, restored.optionalSfixed32)
+ assertEquals(message.optionalSfixed64, restored.optionalSfixed64)
+ assertEquals(message.optionalFloat, restored.optionalFloat)
+ assertEquals(message.optionalDouble, restored.optionalDouble)
+ assertEquals(message.optionalBool, restored.optionalBool)
+ assertEquals(message.optionalString, restored.optionalString)
+ assertContentEquals(message.optionalBytes, restored.optionalBytes.toByteArray())
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Primitive>(restored.toByteArray())
+
+ // [equals] method is not implemented for [ByteArray] so we need to compare it separately.
+ assertEquals(message, restoredMessage.copy(optionalBytes = message.optionalBytes))
+ assertContentEquals(message.optionalBytes, restoredMessage.optionalBytes)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt
new file mode 100644
index 0000000..b3dab8c
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Repeated(
+ @ProtoNumber(31) @ProtoPacked val repeatedInt32: List<Int> = emptyList(),
+ @ProtoNumber(32) @ProtoPacked val repeatedInt64: List<Long> = emptyList(),
+ @ProtoNumber(33) @ProtoPacked val repeatedUint32: List<UInt> = emptyList(),
+ @ProtoNumber(34) @ProtoPacked val repeatedUint64: List<ULong> = emptyList(),
+ @ProtoNumber(35) @ProtoPacked val repeatedSint32: List<Int> = emptyList(),
+ @ProtoNumber(36) @ProtoPacked val repeatedSint64: List<Long> = emptyList(),
+ @ProtoNumber(37) @ProtoPacked val repeatedFixed32: List<Int> = emptyList(),
+ @ProtoNumber(38) @ProtoPacked val repeatedFixed64: List<Long> = emptyList(),
+ @ProtoNumber(39) @ProtoPacked val repeatedSfixed32: List<Int> = emptyList(),
+ @ProtoNumber(40) @ProtoPacked val repeatedSfixed64: List<Long> = emptyList(),
+ @ProtoNumber(41) @ProtoPacked val repeatedFloat: List<Float> = emptyList(),
+ @ProtoNumber(42) @ProtoPacked val repeatedDouble: List<Double> = emptyList(),
+ @ProtoNumber(43) @ProtoPacked val repeatedBool: List<Boolean> = emptyList(),
+ @ProtoNumber(44) val repeatedString: List<String> = emptyList(),
+ @ProtoNumber(45) val repeatedBytes: List<ByteArray> = emptyList(),
+ @ProtoNumber(48) val repeatedNestedMessages: List<KTestMessagesProto3Message.KNestedMessage> = emptyList(),
+ @ProtoNumber(49) val repeatedForeignMessages: List<KForeignMessage> = emptyList(),
+)
+
+class Proto3RepeatedTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Repeated(
+ repeatedInt32 = Gen.list(Gen.int()).generate(),
+ repeatedInt64 = Gen.list(Gen.long()).generate(),
+ repeatedFloat = Gen.list(Gen.float()).generate(),
+ repeatedDouble = Gen.list(Gen.double()).generate(),
+ repeatedBool = Gen.list(Gen.bool()).generate(),
+ repeatedString = Gen.list(Gen.string()).generate(),
+ repeatedBytes = Gen.list(Gen.string().map { it.toByteArray() }).generate(),
+ repeatedNestedMessages = listOf(
+ KTestMessagesProto3Message.KNestedMessage(
+ 1,
+ null
+ ),
+ KTestMessagesProto3Message.KNestedMessage(
+ 2,
+ KTestMessagesProto3Message(
+ KTestMessagesProto3Message.KNestedMessage(3, null),
+ )
+ )
+ ),
+ repeatedForeignMessages = listOf(
+ KForeignMessage(1),
+ KForeignMessage(-12),
+ )
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.repeatedInt32, restored.repeatedInt32List)
+ assertEquals(message.repeatedInt64, restored.repeatedInt64List)
+ assertEquals(message.repeatedFloat, restored.repeatedFloatList)
+ assertEquals(message.repeatedDouble, restored.repeatedDoubleList)
+ assertEquals(message.repeatedBool, restored.repeatedBoolList)
+ assertEquals(message.repeatedString, restored.repeatedStringList)
+ assertEquals(message.repeatedNestedMessages.map { it.toProto() }, restored.repeatedNestedMessageList)
+ assertEquals(message.repeatedForeignMessages.map { it.toProto() }, restored.repeatedForeignMessageList)
+ assertEquals(message.repeatedBytes.map { it.toList() }, restored.repeatedBytesList.map { it.toList() })
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Repeated>(restored.toByteArray())
+ // [equals] method is not implemented for [ByteArray] so we need to compare it separately.
+ assertEquals(message, restoredMessage.copy(repeatedBytes = message.repeatedBytes))
+ assertContentEquals(
+ message.repeatedBytes.flatMap { it.toList() },
+ restoredMessage.repeatedBytes.flatMap { it.toList() },
+ )
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2419
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Repeated(
+ repeatedSint32 = Gen.list(Gen.int()).generate(),
+ repeatedSint64 = Gen.list(Gen.long()).generate(),
+ repeatedFixed32 = Gen.list(Gen.int()).generate(),
+ repeatedFixed64 = Gen.list(Gen.long()).generate(),
+ repeatedSfixed32 = Gen.list(Gen.int()).generate(),
+ repeatedSfixed64 = Gen.list(Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.repeatedUint32, restored.repeatedUint32List.map { it.toUInt() })
+ assertEquals(message.repeatedUint64, restored.repeatedUint64List.map { it.toULong() })
+ assertEquals(message.repeatedSint32, restored.repeatedSint32List)
+ assertEquals(message.repeatedSint64, restored.repeatedSint64List)
+ assertEquals(message.repeatedFixed32, restored.repeatedFixed32List)
+ assertEquals(message.repeatedFixed64, restored.repeatedFixed64List)
+ assertEquals(message.repeatedSfixed32, restored.repeatedSfixed32List)
+ assertEquals(message.repeatedSfixed64, restored.repeatedSfixed64List)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Repeated>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2418
+ fun unsigned() {
+ val message = KTestMessagesProto3Repeated(
+ repeatedUint32 = Gen.list(Gen.int().map { it.toUInt() }).generate(),
+ repeatedUint64 = Gen.list(Gen.long().map { it.toULong() }).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.repeatedUint32, restored.repeatedUint32List.map { it.toUInt() })
+ assertEquals(message.repeatedUint64, restored.repeatedUint64List.map { it.toULong() })
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Repeated>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
diff --git a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.kt b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.kt
new file mode 100644
index 0000000..dad773d
--- /dev/null
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.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.protobuf.conformance
+
+import com.google.protobuf_test_messages.proto3.*
+import io.kotlintest.properties.*
+import kotlinx.serialization.*
+import kotlinx.serialization.protobuf.*
+import kotlin.test.*
+
+@Serializable
+data class KTestMessagesProto3Unpacked(
+ @ProtoNumber(89) val unpackedInt32: List<Int> = emptyList(),
+ @ProtoNumber(90) val unpackedInt64: List<Long> = emptyList(),
+ @ProtoNumber(91) val unpackedUint32: List<UInt> = emptyList(),
+ @ProtoNumber(92) val unpackedUint64: List<ULong> = emptyList(),
+ @ProtoNumber(93) val unpackedSint32: List<Int> = emptyList(),
+ @ProtoNumber(94) val unpackedSint64: List<Long> = emptyList(),
+ @ProtoNumber(95) val unpackedFixed32: List<Int> = emptyList(),
+ @ProtoNumber(96) val unpackedFixed64: List<Long> = emptyList(),
+ @ProtoNumber(97) val unpackedSfixed32: List<Int> = emptyList(),
+ @ProtoNumber(98) val unpackedSfixed64: List<Long> = emptyList(),
+ @ProtoNumber(99) val unpackedFloat: List<Float> = emptyList(),
+ @ProtoNumber(100) val unpackedDouble: List<Double> = emptyList(),
+ @ProtoNumber(101) val unpackedBool: List<Boolean> = emptyList(),
+)
+
+class Proto3UnpackedTest {
+ @Test
+ fun default() {
+ val message = KTestMessagesProto3Unpacked(
+ unpackedInt32 = Gen.list(Gen.int()).generate(),
+ unpackedInt64 = Gen.list(Gen.long()).generate(),
+ unpackedUint32 = Gen.list(Gen.int().map { it.toUInt() }).generate(),
+ unpackedUint64 = Gen.list(Gen.long().map { it.toULong() }).generate(),
+ unpackedFloat = Gen.list(Gen.float()).generate(),
+ unpackedDouble = Gen.list(Gen.double()).generate(),
+ unpackedBool = Gen.list(Gen.bool()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.unpackedInt32, restored.unpackedInt32List)
+ assertEquals(message.unpackedInt64, restored.unpackedInt64List)
+ assertEquals(message.unpackedUint32, restored.unpackedUint32List.map { it.toUInt() })
+ assertEquals(message.unpackedUint64, restored.unpackedUint64List.map { it.toULong() })
+ assertEquals(message.unpackedFloat, restored.unpackedFloatList)
+ assertEquals(message.unpackedDouble, restored.unpackedDoubleList)
+ assertEquals(message.unpackedBool, restored.unpackedBoolList)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Unpacked>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+
+ @Test
+ @Ignore
+ // Issue: https://github.com/Kotlin/kotlinx.serialization/issues/2419
+ fun signedAndFixed() {
+ val message = KTestMessagesProto3Unpacked(
+ unpackedSint32 = Gen.list(Gen.int()).generate(),
+ unpackedSint64 = Gen.list(Gen.long()).generate(),
+ unpackedFixed32 = Gen.list(Gen.int()).generate(),
+ unpackedFixed64 = Gen.list(Gen.long()).generate(),
+ unpackedSfixed32 = Gen.list(Gen.int()).generate(),
+ unpackedSfixed64 = Gen.list(Gen.long()).generate(),
+ )
+
+ val bytes = ProtoBuf.encodeToByteArray(message)
+ val restored = TestMessagesProto3.TestAllTypesProto3.parseFrom(bytes)
+
+ assertEquals(message.unpackedSint32, restored.unpackedSint32List)
+ assertEquals(message.unpackedSint64, restored.unpackedSint64List)
+ assertEquals(message.unpackedFixed32, restored.unpackedFixed32List)
+ assertEquals(message.unpackedFixed64, restored.unpackedFixed64List)
+ assertEquals(message.unpackedSfixed32, restored.unpackedSfixed32List)
+ assertEquals(message.unpackedSfixed64, restored.unpackedSfixed64List)
+
+ val restoredMessage = ProtoBuf.decodeFromByteArray<KTestMessagesProto3Unpacked>(restored.toByteArray())
+ assertEquals(message, restoredMessage)
+ }
+}
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 7b075a4..f2a4423 100644
--- a/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt
+++ b/formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt
@@ -25,6 +25,7 @@
GenerationTest.LegacyMapHolder::class,
GenerationTest.NullableNestedCollections::class,
GenerationTest.OptionalCollections::class,
+ GenerationTest.EnumWithProtoNumber::class,
)
class GenerationTest {
@@ -60,7 +61,7 @@
@ProtoNumber(5)
val b: Int,
@ProtoNumber(3)
- val c: Int
+ val c: UInt,
)
@Serializable
@@ -83,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>,
@@ -112,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
@@ -180,6 +193,16 @@
val legacyMap: Map<List<Int>?, List<Int>?>
)
+ @Serializable
+ enum class EnumWithProtoNumber {
+ ZERO,
+ @ProtoNumber(3)
+ THREE,
+ TWO,
+ @ProtoNumber(5)
+ FIVE,
+ }
+
@Test
fun testIndividuals() {
assertSchemaForClass(OptionsClass::class, mapOf("java_package" to "api.proto", "java_outer_classname" to "Outer"))
diff --git a/formats/protobuf/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/protobuf/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
index 3622196..badc7b0 100644
--- a/formats/protobuf/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/formats/protobuf/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -5,8 +5,4 @@
package kotlinx.serialization.test
-import kotlin.native.concurrent.SharedImmutable
-
-
-@SharedImmutable
public actual val currentPlatform: Platform = Platform.NATIVE
diff --git a/formats/protobuf/testProto/test_data.proto b/formats/protobuf/testProto/test_data.proto
index f4b1f5f..2b50c60 100644
--- a/formats/protobuf/testProto/test_data.proto
+++ b/formats/protobuf/testProto/test_data.proto
@@ -59,6 +59,7 @@
Americano = 0;
Latte = 1;
Capuccino = 2;
+ NoCoffee = -1;
}
required Coffee a = 1;
}
diff --git a/formats/protobuf/testProto/test_messages_proto3.proto b/formats/protobuf/testProto/test_messages_proto3.proto
new file mode 100644
index 0000000..6b27995
--- /dev/null
+++ b/formats/protobuf/testProto/test_messages_proto3.proto
@@ -0,0 +1,289 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc. All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Test schema for proto3 messages. This test schema is used by:
+//
+// - benchmarks
+// - fuzz tests
+// - conformance tests
+//
+// https://github.com/protocolbuffers/protobuf/blob/5e03386555544e39c21236dca0097123edec8769/src/google/protobuf/test_messages_proto3.proto
+
+syntax = "proto3";
+
+package protobuf_test_messages.proto3;
+
+option java_package = "com.google.protobuf_test_messages.proto3";
+option objc_class_prefix = "Proto3";
+
+// This is the default, but we specify it here explicitly.
+option optimize_for = SPEED;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
+
+option cc_enable_arenas = true;
+
+// This proto includes every type of field in both singular and repeated
+// forms.
+//
+// Also, crucially, all messages and enums in this file are eventually
+// submessages of this message. So for example, a fuzz test of TestAllTypes
+// could trigger bugs that occur in any message type in this file. We verify
+// this stays true in a unit test.
+message TestAllTypesProto3 {
+ message NestedMessage {
+ int32 a = 1;
+ TestAllTypesProto3 corecursive = 2;
+ }
+
+ enum NestedEnum {
+ FOO = 0;
+ BAR = 1;
+ BAZ = 2;
+ NEG = -1; // Intentionally negative.
+ }
+
+ enum AliasedEnum {
+ option allow_alias = true;
+
+ ALIAS_FOO = 0;
+ ALIAS_BAR = 1;
+ ALIAS_BAZ = 2;
+ MOO = 2;
+ moo = 2;
+ bAz = 2;
+ }
+
+ // Singular
+ int32 optional_int32 = 1;
+ int64 optional_int64 = 2;
+ uint32 optional_uint32 = 3;
+ uint64 optional_uint64 = 4;
+ sint32 optional_sint32 = 5;
+ sint64 optional_sint64 = 6;
+ fixed32 optional_fixed32 = 7;
+ fixed64 optional_fixed64 = 8;
+ sfixed32 optional_sfixed32 = 9;
+ sfixed64 optional_sfixed64 = 10;
+ float optional_float = 11;
+ double optional_double = 12;
+ bool optional_bool = 13;
+ string optional_string = 14;
+ bytes optional_bytes = 15;
+
+ NestedMessage optional_nested_message = 18;
+ ForeignMessage optional_foreign_message = 19;
+
+ NestedEnum optional_nested_enum = 21;
+ ForeignEnum optional_foreign_enum = 22;
+ AliasedEnum optional_aliased_enum = 23;
+
+ string optional_string_piece = 24 [ctype = STRING_PIECE];
+ string optional_cord = 25 [ctype = CORD];
+
+ TestAllTypesProto3 recursive_message = 27;
+
+ // Repeated
+ repeated int32 repeated_int32 = 31;
+ repeated int64 repeated_int64 = 32;
+ repeated uint32 repeated_uint32 = 33;
+ repeated uint64 repeated_uint64 = 34;
+ repeated sint32 repeated_sint32 = 35;
+ repeated sint64 repeated_sint64 = 36;
+ repeated fixed32 repeated_fixed32 = 37;
+ repeated fixed64 repeated_fixed64 = 38;
+ repeated sfixed32 repeated_sfixed32 = 39;
+ repeated sfixed64 repeated_sfixed64 = 40;
+ repeated float repeated_float = 41;
+ repeated double repeated_double = 42;
+ repeated bool repeated_bool = 43;
+ repeated string repeated_string = 44;
+ repeated bytes repeated_bytes = 45;
+
+ repeated NestedMessage repeated_nested_message = 48;
+ repeated ForeignMessage repeated_foreign_message = 49;
+
+ repeated NestedEnum repeated_nested_enum = 51;
+ repeated ForeignEnum repeated_foreign_enum = 52;
+
+ repeated string repeated_string_piece = 54 [ctype = STRING_PIECE];
+ repeated string repeated_cord = 55 [ctype = CORD];
+
+ // Packed
+ repeated int32 packed_int32 = 75 [packed = true];
+ repeated int64 packed_int64 = 76 [packed = true];
+ repeated uint32 packed_uint32 = 77 [packed = true];
+ repeated uint64 packed_uint64 = 78 [packed = true];
+ repeated sint32 packed_sint32 = 79 [packed = true];
+ repeated sint64 packed_sint64 = 80 [packed = true];
+ repeated fixed32 packed_fixed32 = 81 [packed = true];
+ repeated fixed64 packed_fixed64 = 82 [packed = true];
+ repeated sfixed32 packed_sfixed32 = 83 [packed = true];
+ repeated sfixed64 packed_sfixed64 = 84 [packed = true];
+ repeated float packed_float = 85 [packed = true];
+ repeated double packed_double = 86 [packed = true];
+ repeated bool packed_bool = 87 [packed = true];
+ repeated NestedEnum packed_nested_enum = 88 [packed = true];
+
+ // Unpacked
+ repeated int32 unpacked_int32 = 89 [packed = false];
+ repeated int64 unpacked_int64 = 90 [packed = false];
+ repeated uint32 unpacked_uint32 = 91 [packed = false];
+ repeated uint64 unpacked_uint64 = 92 [packed = false];
+ repeated sint32 unpacked_sint32 = 93 [packed = false];
+ repeated sint64 unpacked_sint64 = 94 [packed = false];
+ repeated fixed32 unpacked_fixed32 = 95 [packed = false];
+ repeated fixed64 unpacked_fixed64 = 96 [packed = false];
+ repeated sfixed32 unpacked_sfixed32 = 97 [packed = false];
+ repeated sfixed64 unpacked_sfixed64 = 98 [packed = false];
+ repeated float unpacked_float = 99 [packed = false];
+ repeated double unpacked_double = 100 [packed = false];
+ repeated bool unpacked_bool = 101 [packed = false];
+ repeated NestedEnum unpacked_nested_enum = 102 [packed = false];
+
+ // Map
+ map<int32, int32> map_int32_int32 = 56;
+ map<int64, int64> map_int64_int64 = 57;
+ map<uint32, uint32> map_uint32_uint32 = 58;
+ map<uint64, uint64> map_uint64_uint64 = 59;
+ map<sint32, sint32> map_sint32_sint32 = 60;
+ map<sint64, sint64> map_sint64_sint64 = 61;
+ map<fixed32, fixed32> map_fixed32_fixed32 = 62;
+ map<fixed64, fixed64> map_fixed64_fixed64 = 63;
+ map<sfixed32, sfixed32> map_sfixed32_sfixed32 = 64;
+ map<sfixed64, sfixed64> map_sfixed64_sfixed64 = 65;
+ map<int32, float> map_int32_float = 66;
+ map<int32, double> map_int32_double = 67;
+ map<bool, bool> map_bool_bool = 68;
+ map<string, string> map_string_string = 69;
+ map<string, bytes> map_string_bytes = 70;
+ map<string, NestedMessage> map_string_nested_message = 71;
+ map<string, ForeignMessage> map_string_foreign_message = 72;
+ map<string, NestedEnum> map_string_nested_enum = 73;
+ map<string, ForeignEnum> map_string_foreign_enum = 74;
+
+ oneof oneof_field {
+ uint32 oneof_uint32 = 111;
+ NestedMessage oneof_nested_message = 112;
+ string oneof_string = 113;
+ bytes oneof_bytes = 114;
+ bool oneof_bool = 115;
+ uint64 oneof_uint64 = 116;
+ float oneof_float = 117;
+ double oneof_double = 118;
+ NestedEnum oneof_enum = 119;
+ google.protobuf.NullValue oneof_null_value = 120;
+ }
+
+ // Well-known types
+ google.protobuf.BoolValue optional_bool_wrapper = 201;
+ google.protobuf.Int32Value optional_int32_wrapper = 202;
+ google.protobuf.Int64Value optional_int64_wrapper = 203;
+ google.protobuf.UInt32Value optional_uint32_wrapper = 204;
+ google.protobuf.UInt64Value optional_uint64_wrapper = 205;
+ google.protobuf.FloatValue optional_float_wrapper = 206;
+ google.protobuf.DoubleValue optional_double_wrapper = 207;
+ google.protobuf.StringValue optional_string_wrapper = 208;
+ google.protobuf.BytesValue optional_bytes_wrapper = 209;
+
+ repeated google.protobuf.BoolValue repeated_bool_wrapper = 211;
+ repeated google.protobuf.Int32Value repeated_int32_wrapper = 212;
+ repeated google.protobuf.Int64Value repeated_int64_wrapper = 213;
+ repeated google.protobuf.UInt32Value repeated_uint32_wrapper = 214;
+ repeated google.protobuf.UInt64Value repeated_uint64_wrapper = 215;
+ repeated google.protobuf.FloatValue repeated_float_wrapper = 216;
+ repeated google.protobuf.DoubleValue repeated_double_wrapper = 217;
+ repeated google.protobuf.StringValue repeated_string_wrapper = 218;
+ repeated google.protobuf.BytesValue repeated_bytes_wrapper = 219;
+
+ google.protobuf.Duration optional_duration = 301;
+ google.protobuf.Timestamp optional_timestamp = 302;
+ google.protobuf.FieldMask optional_field_mask = 303;
+ google.protobuf.Struct optional_struct = 304;
+ google.protobuf.Any optional_any = 305;
+ google.protobuf.Value optional_value = 306;
+ google.protobuf.NullValue optional_null_value = 307;
+
+ repeated google.protobuf.Duration repeated_duration = 311;
+ repeated google.protobuf.Timestamp repeated_timestamp = 312;
+ repeated google.protobuf.FieldMask repeated_fieldmask = 313;
+ repeated google.protobuf.Struct repeated_struct = 324;
+ repeated google.protobuf.Any repeated_any = 315;
+ repeated google.protobuf.Value repeated_value = 316;
+ repeated google.protobuf.ListValue repeated_list_value = 317;
+
+ // Test field-name-to-JSON-name convention.
+ // (protobuf says names can be any valid C/C++ identifier.)
+ int32 fieldname1 = 401;
+ int32 field_name2 = 402;
+ int32 _field_name3 = 403;
+ int32 field__name4_ = 404;
+ int32 field0name5 = 405;
+ int32 field_0_name6 = 406;
+ int32 fieldName7 = 407;
+ int32 FieldName8 = 408;
+ int32 field_Name9 = 409;
+ int32 Field_Name10 = 410;
+ int32 FIELD_NAME11 = 411;
+ int32 FIELD_name12 = 412;
+ int32 __field_name13 = 413;
+ int32 __Field_name14 = 414;
+ int32 field__name15 = 415;
+ int32 field__Name16 = 416;
+ int32 field_name17__ = 417;
+ int32 Field_name18__ = 418;
+
+ // Reserved for testing unknown fields
+ reserved 501 to 510;
+}
+
+message ForeignMessage {
+ int32 c = 1;
+}
+
+enum ForeignEnum {
+ FOREIGN_FOO = 0;
+ FOREIGN_BAR = 1;
+ FOREIGN_BAZ = 2;
+}
+
+message NullHypothesisProto3 {}
+
+message EnumOnlyProto3 {
+ enum Bool {
+ kFalse = 0;
+ kTrue = 1;
+ }
+}
diff --git a/formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt b/formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt
new file mode 100644
index 0000000..72fbfd0
--- /dev/null
+++ b/formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.protobuf.internal
+
+private fun Short.reverseBytes(): Short = (((this.toInt() and 0xff) shl 8) or ((this.toInt() and 0xffff) ushr 8)).toShort()
+
+internal actual fun Int.reverseBytes(): Int =
+ ((this and 0xffff).toShort().reverseBytes().toInt() shl 16) or ((this ushr 16).toShort().reverseBytes().toInt() and 0xffff)
+
+internal actual fun Long.reverseBytes(): Long =
+ ((this and 0xffffffff).toInt().reverseBytes().toLong() shl 32) or ((this ushr 32).toInt()
+ .reverseBytes().toLong() and 0xffffffff)
diff --git a/formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt b/formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
new file mode 100644
index 0000000..fd359b7
--- /dev/null
+++ b/formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
@@ -0,0 +1,7 @@
+/*
+ * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.serialization.test
+
+public actual val currentPlatform: Platform = Platform.WASM
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 0c58f54..099a38f 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,34 +1,29 @@
#
-# Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+# Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
group=org.jetbrains.kotlinx
-version=1.3.4-SNAPSHOT
+version=1.6.4-SNAPSHOT
-kotlin.version=1.6.21
+kotlin.version=1.9.22
-# This version take precedence if 'bootstrap' property passed to project
-kotlin.version.snapshot=1.6.255-SNAPSHOT
+# This version takes precedence if 'bootstrap' property passed to project
+kotlin.version.snapshot=2.0.255-SNAPSHOT
# Also set KONAN_LOCAL_DIST environment variable in bootstrap mode to auto-assign konan.home
junit_version=4.12
jackson_version=2.10.0.pr1
-dokka_version=1.6.0
+dokka_version=1.9.10
native.deploy=
-validator_version=0.7.1
-knit_version=0.3.0
-coroutines_version=1.3.9
+validator_version=0.13.2
+knit_version=0.5.0
+# Only for tests
+coroutines_version=1.6.4
kover_version=0.4.2
+okio_version=3.6.0
kover.enabled=true
-kotlin.mpp.enableGranularSourceSetsMetadata=true
-kotlin.mpp.enableCompatibilityMetadataVariant=true
-kotlin.mpp.stability.nowarn=true
-
-kotlin.js.compiler=both
-kotlin.incremental.multiplatform=true
-
org.gradle.parallel=true
org.gradle.caching=true
diff --git a/gradle/configure-source-sets.gradle b/gradle/configure-source-sets.gradle
index e7888ee..f744b17 100644
--- a/gradle/configure-source-sets.gradle
+++ b/gradle/configure-source-sets.gradle
@@ -1,36 +1,67 @@
/*
- * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ * Copyright 2017-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+import static KotlinVersion.*
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(11))
+ }
+}
+
+tasks.withType(JavaCompile).configureEach {
+ options.release = 8
+}
+
+// Unfortunately there is no compatible version of okio for Wasm WASI target, so we need to skip to configure WASI for json-okio and json-tests.
+// json-tests uses okio with incorporate with other formatter tests so it is hard and not worth to separate it for two projects for WASI.
+// So we disable WASI target in it and we hope, that WASI version of compiler and serialization plugin are identical to the WasmJS target so WASI target is being covered.
+Boolean isOkIoOrFormatTests = (project.name == 'kotlinx-serialization-json-okio' || project.name == 'kotlinx-serialization-json-tests')
+
kotlin {
jvm {
withJava()
- configure([compilations.main, compilations.test]) {
+ compilations.configureEach {
kotlinOptions {
- jvmTarget = '1.6'
- freeCompilerArgs += "-Xsuppress-deprecated-jvm-target-warning"
+ jvmTarget = '1.8'
+ freeCompilerArgs += '-Xjdk-release=1.8'
}
}
}
js {
- nodejs {}
+ nodejs {
+ testTask {
+ useMocha {
+ timeout = "10s"
+ }
+ }
+ }
configure([compilations.main, compilations.test]) {
kotlinOptions {
sourceMap = true
moduleKind = "umd"
- metaInfo = true
}
}
}
+ wasmJs {
+ nodejs()
+ }
+
+ if (!isOkIoOrFormatTests) {
+ wasmWasi {
+ nodejs()
+ }
+ }
+
sourceSets.all {
kotlin.srcDirs = ["$it.name/src"]
resources.srcDirs = ["$it.name/resources"]
languageSettings {
progressiveMode = true
- optIn("kotlin.Experimental")
optIn("kotlin.ExperimentalMultiplatform")
optIn("kotlin.ExperimentalStdlibApi")
optIn("kotlinx.serialization.InternalSerializationApi")
@@ -75,6 +106,43 @@
}
}
+ create("wasmMain") {
+ dependsOn(commonMain)
+ }
+ create("wasmTest") {
+ dependsOn(commonTest)
+ }
+
+ wasmJsMain {
+ dependsOn(wasmMain)
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib-wasm-js'
+ }
+ }
+
+ wasmJsTest {
+ dependsOn(wasmTest)
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-test-wasm-js'
+ }
+ }
+
+ if (!isOkIoOrFormatTests) {
+ wasmWasiMain {
+ dependsOn(wasmMain)
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib-wasm-wasi'
+ }
+ }
+
+ wasmWasiTest {
+ dependsOn(wasmTest)
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-test-wasm-wasi'
+ }
+ }
+ }
+
nativeMain.dependencies {
}
}
@@ -91,6 +159,15 @@
}
targets.all {
+ compilations.all {
+ kotlinOptions {
+ if (rootProject.ext.kotlin_lv_override != null) {
+ languageVersion = rootProject.ext.kotlin_lv_override
+ freeCompilerArgs += "-Xsuppress-version-warnings"
+ }
+ freeCompilerArgs += "-Xexpect-actual-classes"
+ }
+ }
compilations.main {
kotlinOptions {
allWarningsAsErrors = true
@@ -98,7 +175,7 @@
}
}
- def targetsWithoutTestRunners = ["linuxArm32Hfp", "linuxArm64", "mingwX86"]
+ def targetsWithoutTestRunners = ["linuxArm64", "linuxArm32Hfp"]
configure(targets) {
// Configure additional binaries to run tests in the background
if (["macos", "linux", "mingw"].any { name.startsWith(it) && !targetsWithoutTestRunners.contains(name) }) {
@@ -113,3 +190,9 @@
}
}
}
+
+rootProject.extensions.findByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.class).with {
+ // canary nodejs that supports recent Wasm GC changes
+ it.nodeVersion = "21.0.0-v8-canary202309167e82ab1fa2"
+ it.nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
+}
\ No newline at end of file
diff --git a/gradle/dokka.gradle b/gradle/dokka.gradle
index 157ab82..5a208f2 100644
--- a/gradle/dokka.gradle
+++ b/gradle/dokka.gradle
@@ -7,10 +7,12 @@
def documentedSubprojects = ["kotlinx-serialization-core",
"kotlinx-serialization-json",
+ "kotlinx-serialization-json-okio",
"kotlinx-serialization-cbor",
"kotlinx-serialization-properties",
"kotlinx-serialization-hocon",
"kotlinx-serialization-protobuf"]
+
subprojects {
if (!(name in documentedSubprojects)) return
apply plugin: 'org.jetbrains.dokka'
@@ -20,6 +22,8 @@
tasks.named('dokkaHtmlPartial') {
outputDirectory = file("build/dokka")
+ pluginsMapConfiguration.set(["org.jetbrains.dokka.base.DokkaBase": """{ "templatesDir": "${rootProject.projectDir.toString().replace('\\', '/')}/dokka-templates" }"""])
+
dokkaSourceSets {
configureEach {
includes.from(rootProject.file('dokka/moduledoc.md').path)
@@ -36,6 +40,13 @@
suppress.set(true)
}
+ // Internal JSON API
+ perPackageOption {
+ matchingRegex.set("kotlinx\\.serialization.json.internal(\$|\\.).*")
+ suppress.set(true)
+ reportUndocumented.set(false)
+ }
+
// Workaround for typealias
perPackageOption {
matchingRegex.set("kotlinx\\.serialization.protobuf.internal(\$|\\.).*")
@@ -56,6 +67,18 @@
reportUndocumented.set(false)
skipDeprecated.set(true)
}
+
+ // JS/Native implementation of JVM-only `org.intellij.lang.annotations.Language` class to add syntax support by IDE.
+ perPackageOption {
+ matchingRegex.set("org\\.intellij\\.lang\\.annotations(\$|\\.).*")
+ suppress.set(true)
+ }
+
+ sourceLink {
+ localDirectory.set(rootDir)
+ remoteUrl.set(new URL("https://github.com/Kotlin/kotlinx.serialization/tree/master"))
+ remoteLineSuffix.set("#L")
+ }
}
}
}
diff --git a/gradle/kover.gradle b/gradle/kover.gradle
index 214520d..31b03eb 100644
--- a/gradle/kover.gradle
+++ b/gradle/kover.gradle
@@ -12,7 +12,7 @@
}
tasks.koverVerify {
// Core is mainly uncovered because a lot of serializers are tested with JSON
- def minPercentage = (project.name.contains("core") || project.name.contains("properties")) ? 45 : 80
+ def minPercentage = (project.name.contains("core") || project.name.contains("properties")|| project.name.contains("json-okio")) ? 44 : 80
rule {
name = "Minimal line coverage rate in percents"
bound {
diff --git a/gradle/native-targets.gradle b/gradle/native-targets.gradle
index 909fcec..8ef7f48 100644
--- a/gradle/native-targets.gradle
+++ b/gradle/native-targets.gradle
@@ -2,149 +2,44 @@
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-/**
- * Specifies what subset of Native targets to build
- *
- * ALL — all possible for compilation
- * HOST — host-specific ones, without cross compilation (e.g. do not build linuxX64 on macOS host)
- * SINGLE — only for current OS (to import into IDEA / quickly run tests)
- * DISABLED — disable all Native targets (useful with Kotlin compiler built from sources)
- *
- * For HOST mode, all targets are still listed in .module file, so HOST mode is useful for release process.
- */
-enum NativeState { ALL, HOST, SINGLE, DISABLED }
-
-def getNativeState(String description) {
- if (description == null) return NativeState.SINGLE
- switch(description.toLowerCase()) {
- case 'all':
- case 'true':
- return NativeState.ALL
- case 'host':
- return NativeState.HOST
- case 'disabled':
- return NativeState.DISABLED
- // 'single', 'false', etc
- default:
- return NativeState.SINGLE
- }
-}
-
-project.ext.ideaActive = System.getProperty('idea.active') == 'true'
-project.ext.nativeState = getNativeState(property('native.deploy'))
-project.ext.singleTargetMode = project.ext.ideaActive || (project.ext.nativeState == NativeState.SINGLE)
-
-project.ext.nativeMainSets = []
-project.ext.nativeTestSets = []
-
-/**
- * Disables compilation but leaves the target in .module file
- */
-def disableCompilation(targets) {
- configure(targets) {
- compilations.all {
- cinterops.all { project.tasks[interopProcessingTaskName].enabled = false }
- compileKotlinTask.enabled = false
- }
- binaries.all { linkTask.enabled = false }
-
- mavenPublication { publicationToDisable ->
- tasks.withType(AbstractPublishToMaven).all {
- onlyIf { publication != publicationToDisable }
- }
- tasks.withType(GenerateModuleMetadata).all {
- onlyIf { publication.get() != publicationToDisable }
- }
- }
- }
-}
-
-def getHostName() {
- def target = System.getProperty("os.name")
- if (target == 'Linux') return 'linux'
- if (target.startsWith('Windows')) return 'windows'
- if (target.startsWith('Mac')) return 'macos'
- return 'unknown'
+static def doesNotDependOnOkio(project) {
+ return !project.name.contains("json-okio") && !project.name.contains("json-tests")
}
kotlin {
- targets {
- def manager = project.ext.hostManager
- def linuxEnabled = manager.isEnabled(presets.linuxX64.konanTarget)
- def macosEnabled = manager.isEnabled(presets.macosX64.konanTarget)
- def winEnabled = manager.isEnabled(presets.mingwX64.konanTarget)
+ applyDefaultHierarchyTemplate {
- def ideaPreset = presets.linuxX64
- if (macosEnabled) ideaPreset = presets.macosX64
- if (winEnabled) ideaPreset = presets.mingwX64
- project.ext.ideaPreset = ideaPreset
- }
+ // According to https://kotlinlang.org/docs/native-target-support.html
+ // Tier 1
+ macosX64()
+ macosArm64()
+ iosSimulatorArm64()
+ iosX64()
- targets.metaClass.addTarget = { preset ->
- def target = delegate.fromPreset(preset, preset.name)
- project.ext.nativeMainSets.add(target.compilations['main'].kotlinSourceSets.first())
- project.ext.nativeTestSets.add(target.compilations['test'].kotlinSourceSets.first())
- }
+ // Tier 2
+ linuxX64()
+ linuxArm64()
+ watchosSimulatorArm64()
+ watchosX64()
+ watchosArm32()
+ watchosArm64()
+ tvosSimulatorArm64()
+ tvosX64()
+ tvosArm64()
+ iosArm64()
- targets {
- if (project.ext.nativeState == NativeState.DISABLED) return
- if (project.ext.singleTargetMode) {
- fromPreset(project.ext.ideaPreset, 'native')
- } else {
- // Linux
- addTarget(presets.linuxX64)
- addTarget(presets.linuxArm32Hfp)
- addTarget(presets.linuxArm64)
+ // Tier 3
+ mingwX64()
+ // https://github.com/square/okio/issues/1242#issuecomment-1759357336
+ if (doesNotDependOnOkio(project)) {
+ androidNativeArm32()
+ androidNativeArm64()
+ androidNativeX86()
+ androidNativeX64()
+ watchosDeviceArm64()
- // Mac & iOS
- addTarget(presets.macosX64)
-
- addTarget(presets.iosArm64)
- addTarget(presets.iosArm32)
- addTarget(presets.iosX64)
-
- addTarget(presets.watchosX86)
- addTarget(presets.watchosX64)
- addTarget(presets.watchosArm32)
- addTarget(presets.watchosArm64)
-
- addTarget(presets.tvosArm64)
- addTarget(presets.tvosX64)
-
- // Apple Silicon
- addTarget(presets.iosSimulatorArm64)
- addTarget(presets.watchosSimulatorArm64)
- addTarget(presets.tvosSimulatorArm64)
- addTarget(presets.macosArm64)
-
- // Windows
- addTarget(presets.mingwX64)
- addTarget(presets.mingwX86)
- }
-
- if (project.ext.nativeState == NativeState.HOST) {
- // linux targets that can cross-compile on all three hosts
- def linuxCrossCompileTargets = [linuxX64, linuxArm32Hfp, linuxArm64]
- if (getHostName() != "linux") {
- disableCompilation(linuxCrossCompileTargets)
- }
- }
- }
-
-
- sourceSets {
- nativeMain { dependsOn commonMain }
- // Empty source set is required in order to have native tests task
- nativeTest { dependsOn commonTest }
-
- if (!project.ext.singleTargetMode) {
- configure(project.ext.nativeMainSets) {
- dependsOn nativeMain
- }
-
- configure(project.ext.nativeTestSets) {
- dependsOn nativeTest
- }
+ // Deprecated, but not removed
+ linuxArm32Hfp()
}
}
}
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
index c16999a..2c3518e 100644
--- a/gradle/publishing.gradle
+++ b/gradle/publishing.gradle
@@ -9,8 +9,9 @@
apply from: project.rootProject.file('gradle/maven-metadata.gradle')
-def isMultiplatform = project.name in ["kotlinx-serialization-core", "kotlinx-serialization-json","kotlinx-serialization-protobuf",
- "kotlinx-serialization-cbor", "kotlinx-serialization-properties"]
+def isMultiplatform = project.name in ["kotlinx-serialization-core", "kotlinx-serialization-json", "kotlinx-serialization-json-okio",
+ "kotlinx-serialization-json-tests", "kotlinx-serialization-protobuf", "kotlinx-serialization-cbor",
+ "kotlinx-serialization-properties"]
def isBom = project.name == "kotlinx-serialization-bom"
if (!isBom) {
@@ -31,6 +32,8 @@
classifier = 'sources'
if (isMultiplatform) {
from kotlin.sourceSets.commonMain.kotlin
+ } else if (isBom) {
+ // no-op: sourceSets is [] for BOM, as it does not have sources.
} else {
from sourceSets.main.allSource
}
diff --git a/gradle/teamcity.gradle b/gradle/teamcity.gradle
index bb4cb57..950494d 100644
--- a/gradle/teamcity.gradle
+++ b/gradle/teamcity.gradle
@@ -3,7 +3,7 @@
*/
def teamcitySuffix = project.findProperty("teamcitySuffix")?.toString()
-if (project.hasProperty("teamcity") && !build_snapshot_train) {
+if (!teamcityInteractionDisabled && project.hasProperty("teamcity") && !(build_snapshot_train || rootProject.properties['build_snapshot_up'])) {
// Tell teamcity about version number
def postfix = (teamcitySuffix == null) ? "" : " ($teamcitySuffix)"
println("##teamcity[buildNumber '${project.version}${postfix}']")
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 669386b..31cca49 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/guide/build.gradle b/guide/build.gradle
index e01f660..b4261e8 100644
--- a/guide/build.gradle
+++ b/guide/build.gradle
@@ -1,3 +1,5 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
/*
* Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -5,6 +7,19 @@
apply plugin: 'kotlin'
apply plugin: 'kotlinx-serialization'
+kotlin {
+ jvmToolchain(8)
+}
+
+tasks.withType(KotlinCompile).configureEach {
+ kotlinOptions {
+ if (rootProject.ext.kotlin_lv_override != null) {
+ languageVersion = rootProject.ext.kotlin_lv_override
+ freeCompilerArgs += "-Xsuppress-version-warnings"
+ }
+ }
+}
+
dependencies {
testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
testImplementation "org.jetbrains.kotlinx:kotlinx-knit-test:$knit_version"
diff --git a/guide/example/example-builtin-12.kt b/guide/example/example-builtin-12.kt
new file mode 100644
index 0000000..4bd1da0
--- /dev/null
+++ b/guide/example/example-builtin-12.kt
@@ -0,0 +1,12 @@
+// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit.
+package example.exampleBuiltin12
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+import kotlin.time.*
+
+fun main() {
+ val duration = 1000.toDuration(DurationUnit.SECONDS)
+ println(Json.encodeToString(duration))
+}
diff --git a/guide/example/example-builtin-13.kt b/guide/example/example-builtin-13.kt
new file mode 100644
index 0000000..d1a3418
--- /dev/null
+++ b/guide/example/example-builtin-13.kt
@@ -0,0 +1,15 @@
+// This file was automatically generated from builtin-classes.md by Knit tool. Do not edit.
+package example.exampleBuiltin13
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+@Serializable
+sealed class ParametrizedParent<out R> {
+ @Serializable
+ data class ChildWithoutParameter(val value: Int) : ParametrizedParent<Nothing>()
+}
+
+fun main() {
+ println(Json.encodeToString(ParametrizedParent.ChildWithoutParameter(42)))
+}
diff --git a/guide/example/example-formats-10.kt b/guide/example/example-formats-10.kt
index 9f83804..0fc318b 100644
--- a/guide/example/example-formats-10.kt
+++ b/guide/example/example-formats-10.kt
@@ -9,7 +9,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
diff --git a/guide/example/example-formats-11.kt b/guide/example/example-formats-11.kt
index 44bceb0..942febb 100644
--- a/guide/example/example-formats-11.kt
+++ b/guide/example/example-formats-11.kt
@@ -9,7 +9,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -27,7 +27,7 @@
class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
diff --git a/guide/example/example-formats-12.kt b/guide/example/example-formats-12.kt
index ebedb30..1e83b9b 100644
--- a/guide/example/example-formats-12.kt
+++ b/guide/example/example-formats-12.kt
@@ -9,7 +9,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -27,7 +27,7 @@
class ListDecoder(val list: ArrayDeque<Any>) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
diff --git a/guide/example/example-formats-13.kt b/guide/example/example-formats-13.kt
index e63c9b9..62ecdc6 100644
--- a/guide/example/example-formats-13.kt
+++ b/guide/example/example-formats-13.kt
@@ -9,7 +9,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -32,7 +32,7 @@
class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
diff --git a/guide/example/example-formats-14.kt b/guide/example/example-formats-14.kt
index 0224916..cd823e8 100644
--- a/guide/example/example-formats-14.kt
+++ b/guide/example/example-formats-14.kt
@@ -9,7 +9,7 @@
class ListEncoder : AbstractEncoder() {
val list = mutableListOf<Any>()
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeValue(value: Any) {
list.add(value)
@@ -35,7 +35,7 @@
class ListDecoder(val list: ArrayDeque<Any>, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeValue(): Any = list.removeFirst()
diff --git a/guide/example/example-formats-15.kt b/guide/example/example-formats-15.kt
index 207daad..81928d4 100644
--- a/guide/example/example-formats-15.kt
+++ b/guide/example/example-formats-15.kt
@@ -9,7 +9,7 @@
import java.io.*
class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
override fun encodeShort(value: Short) = output.writeShort(value.toInt())
@@ -39,7 +39,7 @@
class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = input.readShort()
diff --git a/guide/example/example-formats-16.kt b/guide/example/example-formats-16.kt
index 25d8662..2460490 100644
--- a/guide/example/example-formats-16.kt
+++ b/guide/example/example-formats-16.kt
@@ -10,7 +10,7 @@
private val byteArraySerializer = serializer<ByteArray>()
class DataOutputEncoder(val output: DataOutput) : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun encodeBoolean(value: Boolean) = output.writeByte(if (value) 1 else 0)
override fun encodeByte(value: Byte) = output.writeByte(value.toInt())
override fun encodeShort(value: Short) = output.writeShort(value.toInt())
@@ -61,7 +61,7 @@
class DataInputDecoder(val input: DataInput, var elementsCount: Int = 0) : AbstractDecoder() {
private var elementIndex = 0
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0
override fun decodeByte(): Byte = input.readByte()
override fun decodeShort(): Short = input.readShort()
diff --git a/guide/example/example-json-12.kt b/guide/example/example-json-12.kt
index cc98bf5..99a872b 100644
--- a/guide/example/example-json-12.kt
+++ b/guide/example/example-json-12.kt
@@ -4,9 +4,17 @@
import kotlinx.serialization.*
import kotlinx.serialization.json.*
+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 element = Json.parseToJsonElement("""
- {"name":"kotlinx.serialization","language":"Kotlin"}
- """)
- println(element)
+ 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 97188ff..e20afe2 100644
--- a/guide/example/example-json-13.kt
+++ b/guide/example/example-json-13.kt
@@ -4,15 +4,13 @@
import kotlinx.serialization.*
import kotlinx.serialization.json.*
+val format = Json { decodeEnumsCaseInsensitive = true }
+
+enum class Cases { VALUE_A, @JsonNames("Alternative") VALUE_B }
+
+@Serializable
+data class CasesList(val cases: List<Cases>)
+
fun main() {
- val element = Json.parseToJsonElement("""
- {
- "name": "kotlinx.serialization",
- "forks": [{"votes": 42}, {"votes": 9000}, {}]
- }
- """)
- val sum = element
- .jsonObject["forks"]!!
- .jsonArray.sumOf { it.jsonObject["votes"]?.jsonPrimitive?.int ?: 0 }
- println(sum)
+ 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 0e5ba36..50de55f 100644
--- a/guide/example/example-json-14.kt
+++ b/guide/example/example-json-14.kt
@@ -4,20 +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 = buildJsonObject {
- put("name", "kotlinx.serialization")
- putJsonObject("owner") {
- put("name", "kotlin")
- }
- putJsonArray("forks") {
- addJsonObject {
- put("votes", 42)
- }
- addJsonObject {
- put("votes", 9000)
- }
- }
- }
- 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 0aa317f..384ae41 100644
--- a/guide/example/example-json-15.kt
+++ b/guide/example/example-json-15.kt
@@ -4,14 +4,9 @@
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")
- }
- val data = Json.decodeFromJsonElement<Project>(element)
- println(data)
+ val element = Json.parseToJsonElement("""
+ {"name":"kotlinx.serialization","language":"Kotlin"}
+ """)
+ println(element)
}
diff --git a/guide/example/example-json-16.kt b/guide/example/example-json-16.kt
index b66d3ac..fff287a 100644
--- a/guide/example/example-json-16.kt
+++ b/guide/example/example-json-16.kt
@@ -4,29 +4,15 @@
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"}]}
- """))
+ val element = Json.parseToJsonElement("""
+ {
+ "name": "kotlinx.serialization",
+ "forks": [{"votes": 42}, {"votes": 9000}, {}]
+ }
+ """)
+ 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 7b1b88f..72a696a 100644
--- a/guide/example/example-json-17.kt
+++ b/guide/example/example-json-17.kt
@@ -4,27 +4,20 @@
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())) {
-
- 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", listOf(User("kotlin")))
- println(Json.encodeToString(data))
+ val element = buildJsonObject {
+ put("name", "kotlinx.serialization")
+ putJsonObject("owner") {
+ put("name", "kotlin")
+ }
+ putJsonArray("forks") {
+ addJsonObject {
+ put("votes", 42)
+ }
+ addJsonObject {
+ put("votes", 9000)
+ }
+ }
+ }
+ println(element)
}
diff --git a/guide/example/example-json-18.kt b/guide/example/example-json-18.kt
index d3da62d..1b655bf 100644
--- a/guide/example/example-json-18.kt
+++ b/guide/example/example-json-18.kt
@@ -5,18 +5,13 @@
import kotlinx.serialization.json.*
@Serializable
-class Project(val name: String, val language: String)
-
-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"
- })
-}
+data class Project(val name: String, val language: String)
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 element = buildJsonObject {
+ put("name", "kotlinx.serialization")
+ put("language", "Kotlin")
+ }
+ 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 4455d63..b001c55 100644
--- a/guide/example/example-json-19.kt
+++ b/guide/example/example-json-19.kt
@@ -4,33 +4,20 @@
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.builtins.*
+import java.math.BigDecimal
-@Serializable
-abstract class Project {
- abstract val name: 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()
- }
-}
+val format = Json { prettyPrint = true }
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 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)
+ }
+
+ println(format.encodeToString(piObject))
}
diff --git a/guide/example/example-json-20.kt b/guide/example/example-json-20.kt
index e613a08..f522b3f 100644
--- a/guide/example/example-json-20.kt
+++ b/guide/example/example-json-20.kt
@@ -4,56 +4,24 @@
import kotlinx.serialization.*
import kotlinx.serialization.json.*
-import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.encoding.*
+import java.math.BigDecimal
-@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", buildClassSerialDescriptor("Ok") {
- element<String>("message")
- })
- element("Error", dataSerializer.descriptor)
- }
-
- 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
-data class Project(val name: String)
+val format = Json { prettyPrint = true }
fun main() {
- 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))
+ 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 92de429..efd6071 100644
--- a/guide/example/example-json-21.kt
+++ b/guide/example/example-json-21.kt
@@ -4,34 +4,20 @@
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")
- }
-}
+import java.math.BigDecimal
fun main() {
- println(Json.decodeFromString(UnknownProjectSerializer, """{"type":"unknown","name":"example","maintainer":"Unknown","license":"Apache 2.0"}"""))
+ 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
new file mode 100644
index 0000000..e64ab06
--- /dev/null
+++ b/guide/example/example-json-22.kt
@@ -0,0 +1,10 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson22
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+fun main() {
+ // 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
new file mode 100644
index 0000000..ffa9f7d
--- /dev/null
+++ b/guide/example/example-json-23.kt
@@ -0,0 +1,32 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson23
+
+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"}]}
+ """))
+}
diff --git a/guide/example/example-json-24.kt b/guide/example/example-json-24.kt
new file mode 100644
index 0000000..010bd27
--- /dev/null
+++ b/guide/example/example-json-24.kt
@@ -0,0 +1,30 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson24
+
+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())) {
+
+ 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", listOf(User("kotlin")))
+ println(Json.encodeToString(data))
+}
diff --git a/guide/example/example-json-25.kt b/guide/example/example-json-25.kt
new file mode 100644
index 0000000..a7d19a7
--- /dev/null
+++ b/guide/example/example-json-25.kt
@@ -0,0 +1,22 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson25
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+@Serializable
+class Project(val name: String, val language: String)
+
+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 = 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
new file mode 100644
index 0000000..b1b9299
--- /dev/null
+++ b/guide/example/example-json-26.kt
@@ -0,0 +1,36 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson26
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+import kotlinx.serialization.builtins.*
+
+@Serializable
+abstract class Project {
+ abstract val name: 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()
+ }
+}
+
+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))
+}
diff --git a/guide/example/example-json-27.kt b/guide/example/example-json-27.kt
new file mode 100644
index 0000000..5905733
--- /dev/null
+++ b/guide/example/example-json-27.kt
@@ -0,0 +1,59 @@
+// This file was automatically generated from json.md by Knit tool. Do not edit.
+package example.exampleJson27
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+
+import kotlinx.serialization.descriptors.*
+import kotlinx.serialization.encoding.*
+
+@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
+data class Project(val name: String)
+
+fun main() {
+ 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/example/example-serializer-16.kt b/guide/example/example-serializer-16.kt
index 157208f..3db0b7f 100644
--- a/guide/example/example-serializer-16.kt
+++ b/guide/example/example-serializer-16.kt
@@ -1,4 +1,3 @@
-@file:UseSerializers(DateAsLongSerializer::class)
// This file was automatically generated from serializers.md by Knit tool. Do not edit.
package example.exampleSerializer16
@@ -17,9 +16,13 @@
}
@Serializable
-class ProgrammingLanguage(val name: String, val stableReleaseDate: Date)
+class ProgrammingLanguage(
+ val name: String,
+ val releaseDates: List<@Serializable(DateAsLongSerializer::class) Date>
+)
fun main() {
- val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
+ val df = SimpleDateFormat("yyyy-MM-ddX")
+ val data = ProgrammingLanguage("Kotlin", listOf(df.parse("2023-07-06+00"), df.parse("2023-04-25+00"), df.parse("2022-12-28+00")))
println(Json.encodeToString(data))
}
diff --git a/guide/example/example-serializer-17.kt b/guide/example/example-serializer-17.kt
index e6c488e..c5624ed 100644
--- a/guide/example/example-serializer-17.kt
+++ b/guide/example/example-serializer-17.kt
@@ -1,3 +1,4 @@
+@file:UseSerializers(DateAsLongSerializer::class)
// This file was automatically generated from serializers.md by Knit tool. Do not edit.
package example.exampleSerializer17
@@ -6,21 +7,19 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
-@Serializable(with = BoxSerializer::class)
-data class Box<T>(val contents: T)
-
-class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
- override val descriptor: SerialDescriptor = dataSerializer.descriptor
- override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)
- override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
+import java.util.Date
+import java.text.SimpleDateFormat
+
+object DateAsLongSerializer : KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
+ override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
}
-@Serializable
-data class Project(val name: String)
+@Serializable
+class ProgrammingLanguage(val name: String, val stableReleaseDate: Date)
fun main() {
- val box = Box(Project("kotlinx.serialization"))
- val string = Json.encodeToString(box)
- println(string)
- println(Json.decodeFromString<Box<Project>>(string))
+ val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
+ println(Json.encodeToString(data))
}
diff --git a/guide/example/example-serializer-18.kt b/guide/example/example-serializer-18.kt
index 9162631..9987e82 100644
--- a/guide/example/example-serializer-18.kt
+++ b/guide/example/example-serializer-18.kt
@@ -8,15 +8,29 @@
import java.util.Date
import java.text.SimpleDateFormat
+
+object DateAsLongSerializer : KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsLong", PrimitiveKind.LONG)
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
+ override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
+}
+
+object DateAsSimpleTextSerializer: KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("DateAsSimpleText", PrimitiveKind.LONG)
+ private val format = SimpleDateFormat("yyyy-MM-dd")
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeString(format.format(value))
+ override fun deserialize(decoder: Decoder): Date = format.parse(decoder.decodeString())
+}
+
+typealias DateAsLong = @Serializable(DateAsLongSerializer::class) Date
+
+typealias DateAsText = @Serializable(DateAsSimpleTextSerializer::class) Date
@Serializable
-class ProgrammingLanguage(
- val name: String,
- @Contextual
- val stableReleaseDate: Date
-)
+class ProgrammingLanguage(val stableReleaseDate: DateAsText, val lastReleaseTimestamp: DateAsLong)
fun main() {
- val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
+ val format = SimpleDateFormat("yyyy-MM-ddX")
+ val data = ProgrammingLanguage(format.parse("2016-02-15+00"), format.parse("2022-07-07+00"))
println(Json.encodeToString(data))
}
diff --git a/guide/example/example-serializer-19.kt b/guide/example/example-serializer-19.kt
index da51db3..4622665 100644
--- a/guide/example/example-serializer-19.kt
+++ b/guide/example/example-serializer-19.kt
@@ -6,30 +6,21 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
-import kotlinx.serialization.modules.*
-import java.util.Date
-import java.text.SimpleDateFormat
-
-object DateAsLongSerializer : KSerializer<Date> {
- override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
- override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
- override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
+@Serializable(with = BoxSerializer::class)
+data class Box<T>(val contents: T)
+
+class BoxSerializer<T>(private val dataSerializer: KSerializer<T>) : KSerializer<Box<T>> {
+ override val descriptor: SerialDescriptor = dataSerializer.descriptor
+ override fun serialize(encoder: Encoder, value: Box<T>) = dataSerializer.serialize(encoder, value.contents)
+ override fun deserialize(decoder: Decoder) = Box(dataSerializer.deserialize(decoder))
}
-@Serializable
-class ProgrammingLanguage(
- val name: String,
- @Contextual
- val stableReleaseDate: Date
-)
-
-private val module = SerializersModule {
- contextual(DateAsLongSerializer)
-}
-
-val format = Json { serializersModule = module }
+@Serializable
+data class Project(val name: String)
fun main() {
- val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
- println(format.encodeToString(data))
+ val box = Box(Project("kotlinx.serialization"))
+ val string = Json.encodeToString(box)
+ println(string)
+ println(Json.decodeFromString<Box<Project>>(string))
}
diff --git a/guide/example/example-serializer-20.kt b/guide/example/example-serializer-20.kt
index 7b4e71c..38b72e7 100644
--- a/guide/example/example-serializer-20.kt
+++ b/guide/example/example-serializer-20.kt
@@ -6,13 +6,17 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
-// NOT @Serializable
-class Project(val name: String, val language: String)
-
-@Serializer(forClass = Project::class)
-object ProjectSerializer
+import java.util.Date
+import java.text.SimpleDateFormat
+
+@Serializable
+class ProgrammingLanguage(
+ val name: String,
+ @Contextual
+ val stableReleaseDate: Date
+)
fun main() {
- val data = Project("kotlinx.serialization", "Kotlin")
- println(Json.encodeToString(ProjectSerializer, data))
+ val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
+ println(Json.encodeToString(data))
}
diff --git a/guide/example/example-serializer-21.kt b/guide/example/example-serializer-21.kt
index 9587907..9a24b0a 100644
--- a/guide/example/example-serializer-21.kt
+++ b/guide/example/example-serializer-21.kt
@@ -6,23 +6,30 @@
import kotlinx.serialization.encoding.*
import kotlinx.serialization.descriptors.*
-// NOT @Serializable, will use external serializer
-class Project(
- // val in a primary constructor -- serialized
- val name: String
-) {
- var stars: Int = 0 // property with getter & setter -- serialized
-
- val path: String // getter only -- not serialized
- get() = "kotlin/$name"
+import kotlinx.serialization.modules.*
+import java.util.Date
+import java.text.SimpleDateFormat
+
+object DateAsLongSerializer : KSerializer<Date> {
+ override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG)
+ override fun serialize(encoder: Encoder, value: Date) = encoder.encodeLong(value.time)
+ override fun deserialize(decoder: Decoder): Date = Date(decoder.decodeLong())
+}
- private var locked: Boolean = false // private, not accessible -- not serialized
-}
+@Serializable
+class ProgrammingLanguage(
+ val name: String,
+ @Contextual
+ val stableReleaseDate: Date
+)
-@Serializer(forClass = Project::class)
-object ProjectSerializer
+private val module = SerializersModule {
+ contextual(DateAsLongSerializer)
+}
+
+val format = Json { serializersModule = module }
fun main() {
- val data = Project("kotlinx.serialization").apply { stars = 9000 }
- println(Json.encodeToString(ProjectSerializer, data))
+ val data = ProgrammingLanguage("Kotlin", SimpleDateFormat("yyyy-MM-ddX").parse("2016-02-15+00"))
+ println(format.encodeToString(data))
}
diff --git a/guide/example/example-serializer-22.kt b/guide/example/example-serializer-22.kt
new file mode 100644
index 0000000..4eba74b
--- /dev/null
+++ b/guide/example/example-serializer-22.kt
@@ -0,0 +1,18 @@
+// This file was automatically generated from serializers.md by Knit tool. Do not edit.
+package example.exampleSerializer22
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.descriptors.*
+
+// NOT @Serializable
+class Project(val name: String, val language: String)
+
+@Serializer(forClass = Project::class)
+object ProjectSerializer
+
+fun main() {
+ val data = Project("kotlinx.serialization", "Kotlin")
+ println(Json.encodeToString(ProjectSerializer, data))
+}
diff --git a/guide/example/example-serializer-23.kt b/guide/example/example-serializer-23.kt
new file mode 100644
index 0000000..4b7de25
--- /dev/null
+++ b/guide/example/example-serializer-23.kt
@@ -0,0 +1,28 @@
+// This file was automatically generated from serializers.md by Knit tool. Do not edit.
+package example.exampleSerializer23
+
+import kotlinx.serialization.*
+import kotlinx.serialization.json.*
+import kotlinx.serialization.encoding.*
+import kotlinx.serialization.descriptors.*
+
+// NOT @Serializable, will use external serializer
+class Project(
+ // val in a primary constructor -- serialized
+ val name: String
+) {
+ var stars: Int = 0 // property with getter & setter -- serialized
+
+ val path: String // getter only -- not serialized
+ get() = "kotlin/$name"
+
+ private var locked: Boolean = false // private, not accessible -- not serialized
+}
+
+@Serializer(forClass = Project::class)
+object ProjectSerializer
+
+fun main() {
+ val data = Project("kotlinx.serialization").apply { stars = 9000 }
+ println(Json.encodeToString(ProjectSerializer, data))
+}
diff --git a/guide/test/BasicSerializationTest.kt b/guide/test/BasicSerializationTest.kt
index 74c4433..11f9e9f 100644
--- a/guide/test/BasicSerializationTest.kt
+++ b/guide/test/BasicSerializationTest.kt
@@ -9,7 +9,7 @@
fun testExampleBasic01() {
captureOutput("ExampleBasic01") { example.exampleBasic01.main() }.verifyOutputLinesStart(
"Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Project' is not found.",
- "Mark the class as @Serializable or provide the serializer explicitly."
+ "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied."
)
}
@@ -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/BuiltinClassesTest.kt b/guide/test/BuiltinClassesTest.kt
index 2827332..3cc9629 100644
--- a/guide/test/BuiltinClassesTest.kt
+++ b/guide/test/BuiltinClassesTest.kt
@@ -82,4 +82,18 @@
"{}"
)
}
+
+ @Test
+ fun testExampleBuiltin12() {
+ captureOutput("ExampleBuiltin12") { example.exampleBuiltin12.main() }.verifyOutputLines(
+ "\"PT16M40S\""
+ )
+ }
+
+ @Test
+ fun testExampleBuiltin13() {
+ captureOutput("ExampleBuiltin13") { example.exampleBuiltin13.main() }.verifyOutputLines(
+ "{\"value\":42}"
+ )
+ }
}
diff --git a/guide/test/JsonTest.kt b/guide/test/JsonTest.kt
index c92f57b..0c5ed85 100644
--- a/guide/test/JsonTest.kt
+++ b/guide/test/JsonTest.kt
@@ -90,73 +90,129 @@
@Test
fun testExampleJson12() {
captureOutput("ExampleJson12") { example.exampleJson12.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
+ "{\"name\":\"kotlinx.coroutines\",\"owner\":\"kotlin\"}"
)
}
@Test
fun testExampleJson13() {
captureOutput("ExampleJson13") { example.exampleJson13.main() }.verifyOutputLines(
- "9042"
+ "CasesList(cases=[VALUE_A, VALUE_B])"
)
}
@Test
fun testExampleJson14() {
captureOutput("ExampleJson14") { example.exampleJson14.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}"
+ "{\"project_name\":\"kotlinx.serialization\",\"project_owner\":\"Kotlin\"}"
)
}
@Test
fun testExampleJson15() {
captureOutput("ExampleJson15") { example.exampleJson15.main() }.verifyOutputLines(
- "Project(name=kotlinx.serialization, language=Kotlin)"
+ "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
)
}
@Test
fun testExampleJson16() {
captureOutput("ExampleJson16") { example.exampleJson16.main() }.verifyOutputLines(
- "Project(name=kotlinx.serialization, users=[User(name=kotlin)])",
- "Project(name=kotlinx.serialization, users=[User(name=kotlin), User(name=jetbrains)])"
+ "9042"
)
}
@Test
fun testExampleJson17() {
captureOutput("ExampleJson17") { example.exampleJson17.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"users\":{\"name\":\"kotlin\"}}"
+ "{\"name\":\"kotlinx.serialization\",\"owner\":{\"name\":\"kotlin\"},\"forks\":[{\"votes\":42},{\"votes\":9000}]}"
)
}
@Test
fun testExampleJson18() {
captureOutput("ExampleJson18") { example.exampleJson18.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}",
- "{\"name\":\"kotlinx.serialization\"}"
+ "Project(name=kotlinx.serialization, language=Kotlin)"
)
}
@Test
fun testExampleJson19() {
captureOutput("ExampleJson19") { example.exampleJson19.main() }.verifyOutputLines(
- "[{\"name\":\"kotlinx.serialization\",\"owner\":\"kotlin\"},{\"name\":\"example\"}]",
- "[OwnedProject(name=kotlinx.serialization, owner=kotlin), BasicProject(name=example)]"
+ "{",
+ " \"pi_double\": 3.141592653589793,",
+ " \"pi_string\": \"3.141592653589793238462643383279\"",
+ "}"
)
}
@Test
fun testExampleJson20() {
captureOutput("ExampleJson20") { example.exampleJson20.main() }.verifyOutputLines(
- "[{\"name\":\"kotlinx.serialization\"},{\"error\":\"Not found\"}]",
- "[Ok(data=Project(name=kotlinx.serialization)), Error(message=Not found)]"
+ "{",
+ " \"pi_literal\": 3.141592653589793238462643383279,",
+ " \"pi_double\": 3.141592653589793,",
+ " \"pi_string\": \"3.141592653589793238462643383279\"",
+ "}"
)
}
@Test
fun testExampleJson21() {
captureOutput("ExampleJson21") { example.exampleJson21.main() }.verifyOutputLines(
+ "3.141592653589793238462643383279"
+ )
+ }
+
+ @Test
+ fun testExampleJson22() {
+ 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(
+ "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\",\"users\":{\"name\":\"kotlin\"}}"
+ )
+ }
+
+ @Test
+ fun testExampleJson25() {
+ captureOutput("ExampleJson25") { example.exampleJson25.main() }.verifyOutputLines(
+ "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}",
+ "{\"name\":\"kotlinx.serialization\"}"
+ )
+ }
+
+ @Test
+ fun testExampleJson26() {
+ captureOutput("ExampleJson26") { example.exampleJson26.main() }.verifyOutputLines(
+ "[{\"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/guide/test/PolymorphismTest.kt b/guide/test/PolymorphismTest.kt
index e82dd68..344ed24 100644
--- a/guide/test/PolymorphismTest.kt
+++ b/guide/test/PolymorphismTest.kt
@@ -16,15 +16,16 @@
fun testExamplePoly02() {
captureOutput("ExamplePoly02") { example.examplePoly02.main() }.verifyOutputLinesStart(
"Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'OwnedProject' is not found.",
- "Mark the class as @Serializable or provide the serializer explicitly."
+ "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied."
)
}
@Test
fun testExamplePoly03() {
captureOutput("ExamplePoly03") { example.examplePoly03.main() }.verifyOutputLinesStart(
- "Exception in thread \"main\" kotlinx.serialization.SerializationException: Class 'OwnedProject' is not registered for polymorphic serialization in the scope of 'Project'.",
- "Mark the base class as 'sealed' or register the serializer explicitly."
+ "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for subclass 'OwnedProject' is not found in the polymorphic scope of 'Project'.",
+ "Check if class with serial name 'OwnedProject' exists and serializer is registered in a corresponding SerializersModule.",
+ "To be registered automatically, class 'OwnedProject' has to be '@Serializable', and the base class 'Project' has to be sealed and '@Serializable'."
)
}
@@ -88,7 +89,7 @@
fun testExamplePoly12() {
captureOutput("ExamplePoly12") { example.examplePoly12.main() }.verifyOutputLinesStart(
"Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.",
- "Mark the class as @Serializable or provide the serializer explicitly."
+ "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied."
)
}
@@ -96,7 +97,7 @@
fun testExamplePoly13() {
captureOutput("ExamplePoly13") { example.examplePoly13.main() }.verifyOutputLinesStart(
"Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Any' is not found.",
- "Mark the class as @Serializable or provide the serializer explicitly."
+ "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied."
)
}
@@ -132,7 +133,8 @@
@Test
fun testExamplePoly18() {
captureOutput("ExamplePoly18") { example.examplePoly18.main() }.verifyOutputLinesStart(
- "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Polymorphic serializer was not found for class discriminator 'unknown'"
+ "Exception in thread \"main\" kotlinx.serialization.json.internal.JsonDecodingException: Unexpected JSON token at offset 0: Serializer for subclass 'unknown' is not found in the polymorphic scope of 'Project' at path: $",
+ "Check if class with serial name 'unknown' exists and serializer is registered in a corresponding SerializersModule."
)
}
diff --git a/guide/test/SerializersTest.kt b/guide/test/SerializersTest.kt
index 0871b82..bda3f7f 100644
--- a/guide/test/SerializersTest.kt
+++ b/guide/test/SerializersTest.kt
@@ -113,43 +113,57 @@
@Test
fun testExampleSerializer16() {
captureOutput("ExampleSerializer16") { example.exampleSerializer16.main() }.verifyOutputLines(
- "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}"
+ "{\"name\":\"Kotlin\",\"releaseDates\":[1688601600000,1682380800000,1672185600000]}"
)
}
@Test
fun testExampleSerializer17() {
captureOutput("ExampleSerializer17") { example.exampleSerializer17.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\"}",
- "Box(contents=Project(name=kotlinx.serialization))"
+ "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}"
)
}
@Test
fun testExampleSerializer18() {
- captureOutput("ExampleSerializer18") { example.exampleSerializer18.main() }.verifyOutputLinesStart(
- "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.",
- "Mark the class as @Serializable or provide the serializer explicitly."
+ captureOutput("ExampleSerializer18") { example.exampleSerializer18.main() }.verifyOutputLines(
+ "{\"stableReleaseDate\":\"2016-02-15\",\"lastReleaseTimestamp\":1657152000000}"
)
}
@Test
fun testExampleSerializer19() {
captureOutput("ExampleSerializer19") { example.exampleSerializer19.main() }.verifyOutputLines(
- "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}"
+ "{\"name\":\"kotlinx.serialization\"}",
+ "Box(contents=Project(name=kotlinx.serialization))"
)
}
@Test
fun testExampleSerializer20() {
- captureOutput("ExampleSerializer20") { example.exampleSerializer20.main() }.verifyOutputLines(
- "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
+ captureOutput("ExampleSerializer20") { example.exampleSerializer20.main() }.verifyOutputLinesStart(
+ "Exception in thread \"main\" kotlinx.serialization.SerializationException: Serializer for class 'Date' is not found.",
+ "Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied."
)
}
@Test
fun testExampleSerializer21() {
captureOutput("ExampleSerializer21") { example.exampleSerializer21.main() }.verifyOutputLines(
+ "{\"name\":\"Kotlin\",\"stableReleaseDate\":1455494400000}"
+ )
+ }
+
+ @Test
+ fun testExampleSerializer22() {
+ captureOutput("ExampleSerializer22") { example.exampleSerializer22.main() }.verifyOutputLines(
+ "{\"name\":\"kotlinx.serialization\",\"language\":\"Kotlin\"}"
+ )
+ }
+
+ @Test
+ fun testExampleSerializer23() {
+ captureOutput("ExampleSerializer23") { example.exampleSerializer23.main() }.verifyOutputLines(
"{\"name\":\"kotlinx.serialization\",\"stars\":9000}"
)
}
diff --git a/integration-test/build.gradle b/integration-test/build.gradle
index 911cee1..6c4e700 100644
--- a/integration-test/build.gradle
+++ b/integration-test/build.gradle
@@ -5,23 +5,31 @@
ext.serialization_version = mainLibVersion
repositories {
- mavenLocal()
mavenCentral()
maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
+ mavenLocal() {
+ mavenContent {
+ snapshotsOnly()
+ }
+ }
}
}
-// see ../settings.gradle so this plugins could be resolved
+// Versions substituted in settings.gradle
plugins {
- id 'kotlin-multiplatform'
- id 'kotlinx-serialization'
- id 'org.jetbrains.kotlin.kapt'
+ id 'org.jetbrains.kotlin.multiplatform' version '0'
+ id 'org.jetbrains.kotlin.plugin.serialization' version '0'
+ id 'org.jetbrains.kotlin.kapt' version '0'
}
repositories {
- mavenLocal()
mavenCentral()
maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
+ mavenLocal() {
+ mavenContent {
+ snapshotsOnly()
+ }
+ }
}
group 'com.example'
@@ -37,23 +45,33 @@
kotlinOptions {
sourceMap = true
moduleKind = "umd"
- metaInfo = true
}
}
}
+ wasmJs {
+ nodejs()
+ }
+ wasmWasi {
+ nodejs()
+ }
jvm {
withJava()
}
- // For ARM, should be changed to iosArm32 or iosArm64
- // For Linux, should be changed to e.g. linuxX64
- // For MacOS, should be changed to e.g. macosX64
- // For Windows, should be changed to e.g. mingwX64
- macosX64("macos")
- linuxX64("linux")
+ macosX64()
+ macosArm64()
+ linuxX64()
+ mingwX64()
+
sourceSets {
+ all {
+ languageSettings {
+ optIn('kotlinx.serialization.ExperimentalSerializationApi')
+ }
+ }
+
commonMain {
dependencies {
- implementation kotlin('stdlib-common')
+ implementation kotlin('stdlib')
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:$serialization_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:$serialization_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-protobuf:$serialization_version"
@@ -89,21 +107,38 @@
implementation kotlin('test-js')
}
}
- macosMain {
+ wasmJsMain {
dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib-wasm-js'
}
}
- macosTest {}
- linuxMain {
- kotlin.srcDirs = ["src/macosMain/kotlin"]
+ wasmJsTest {
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-test-wasm-js'
+ }
}
- linuxTest {
- kotlin.srcDirs = ["src/macosTest/kotlin"]
+ wasmWasiMain {
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-stdlib-wasm-wasi'
+ }
+ }
+ wasmWasiTest {
+ dependencies {
+ api 'org.jetbrains.kotlin:kotlin-test-wasm-wasi'
+ }
}
}
- sourceSets.all {
- languageSettings {
- useExperimentalAnnotation('kotlin.Experimental') // annotation FQ-name
+
+ targets.all {
+ compilations.all {
+ kotlinOptions {
+ freeCompilerArgs += "-Xexpect-actual-classes"
+ }
+ }
+ compilations.main {
+ kotlinOptions {
+ allWarningsAsErrors = true
+ }
}
}
}
@@ -113,3 +148,13 @@
}
task run dependsOn "check"
+
+rootProject.extensions.findByType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.class).with {
+ // canary nodejs that supports recent Wasm GC changes
+ it.nodeVersion = "21.0.0-v8-canary202309167e82ab1fa2"
+ it.nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary"
+}
+
+tasks.withType(org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask).configureEach {
+ args.add("--ignore-engines")
+}
diff --git a/integration-test/gradle.properties b/integration-test/gradle.properties
index 398917a..d29c5df 100644
--- a/integration-test/gradle.properties
+++ b/integration-test/gradle.properties
@@ -2,11 +2,11 @@
# Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
-mainKotlinVersion=1.6.21
-mainLibVersion=1.3.4-SNAPSHOT
+mainKotlinVersion=1.9.22
+mainLibVersion=1.6.4-SNAPSHOT
kotlin.code.style=official
-kotlin.js.compiler=both
+kotlin.js.compiler=ir
gradle_node_version = 1.2.0
node_version = 8.9.3
@@ -15,4 +15,7 @@
mocha_teamcity_reporter_version = 2.2.2
source_map_support_version = 0.5.3
+# Uncommend & insert path to local Native distribution if you want to test with SNAPSHOT compiler
+#kotlin.native.home=
+
#org.jetbrains.kotlin.native.jvmArgs=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5007
diff --git a/integration-test/gradle/wrapper/gradle-wrapper.properties b/integration-test/gradle/wrapper/gradle-wrapper.properties
index 669386b..31cca49 100644
--- a/integration-test/gradle/wrapper/gradle-wrapper.properties
+++ b/integration-test/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/integration-test/kotlin-js-store/yarn.lock b/integration-test/kotlin-js-store/yarn.lock
new file mode 100644
index 0000000..08c4983
--- /dev/null
+++ b/integration-test/kotlin-js-store/yarn.lock
@@ -0,0 +1,554 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+ansi-colors@4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
+ integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+anymatch@~3.1.2:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
+ integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+binary-extensions@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browser-stdout@1.3.1:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
+ integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
+
+buffer-from@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+ integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+camelcase@^6.0.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+ integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+chalk@^4.1.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chokidar@3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+cliui@^7.0.2:
+ version "7.0.4"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+ integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+debug@4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+decamelize@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837"
+ integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==
+
+diff@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
+ integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-string-regexp@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+find-up@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flat@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
+ integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
+
+format-util@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271"
+ integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+get-caller-file@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob@7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+ integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+he@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.1, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-plain-obj@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
+ integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
+
+is-unicode-supported@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
+ integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
+
+js-yaml@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+log-symbols@4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
+ integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
+ dependencies:
+ chalk "^4.1.0"
+ is-unicode-supported "^0.1.0"
+
+minimatch@5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+ integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^3.0.4:
+ version "3.1.2"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+mocha@10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8"
+ integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==
+ dependencies:
+ ansi-colors "4.1.1"
+ browser-stdout "1.3.1"
+ chokidar "3.5.3"
+ debug "4.3.4"
+ diff "5.0.0"
+ escape-string-regexp "4.0.0"
+ find-up "5.0.0"
+ glob "7.2.0"
+ he "1.2.0"
+ js-yaml "4.1.0"
+ log-symbols "4.1.0"
+ minimatch "5.0.1"
+ ms "2.1.3"
+ nanoid "3.3.3"
+ serialize-javascript "6.0.0"
+ strip-json-comments "3.1.1"
+ supports-color "8.1.1"
+ workerpool "6.2.1"
+ yargs "16.2.0"
+ yargs-parser "20.2.4"
+ yargs-unparser "2.0.0"
+
+ms@2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+nanoid@3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+ integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+picomatch@^2.0.4, picomatch@^2.2.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+randombytes@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
+ integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
+ dependencies:
+ safe-buffer "^5.1.0"
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+safe-buffer@^5.1.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+serialize-javascript@6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
+ integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+ dependencies:
+ randombytes "^2.1.0"
+
+source-map-support@0.5.21:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+ dependencies:
+ buffer-from "^1.0.0"
+ source-map "^0.6.0"
+
+source-map@^0.6.0:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+ integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+string-width@^4.1.0, string-width@^4.2.0:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-json-comments@3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@8.1.1:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+ integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+typescript@5.0.4:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
+ integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
+
+workerpool@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+ integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
+
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+y18n@^5.0.5:
+ version "5.0.8"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+ integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yargs-parser@20.2.4:
+ version "20.2.4"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
+ integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
+
+yargs-parser@^20.2.2:
+ version "20.2.9"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+yargs-unparser@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
+ integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
+ dependencies:
+ camelcase "^6.0.0"
+ decamelize "^4.0.0"
+ flat "^5.0.2"
+ is-plain-obj "^2.1.0"
+
+yargs@16.2.0:
+ version "16.2.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+ integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+ dependencies:
+ cliui "^7.0.2"
+ escalade "^3.1.1"
+ get-caller-file "^2.0.5"
+ require-directory "^2.1.1"
+ string-width "^4.2.0"
+ y18n "^5.0.5"
+ yargs-parser "^20.2.2"
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/integration-test/settings.gradle b/integration-test/settings.gradle
index 2360ae4..f8cb2d8 100644
--- a/integration-test/settings.gradle
+++ b/integration-test/settings.gradle
@@ -1,14 +1,14 @@
pluginManagement {
resolutionStrategy {
eachPlugin {
- if (requested.id.id == "kotlin-multiplatform") {
- useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$mainKotlinVersion")
+ if (requested.id.id == "org.jetbrains.kotlin.multiplatform") {
+ useVersion("$mainKotlinVersion")
}
if (requested.id.id == "org.jetbrains.kotlin.kapt") {
- useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$mainKotlinVersion")
+ useVersion("$mainKotlinVersion")
}
- if (requested.id.id == "kotlinx-serialization") {
- useModule("org.jetbrains.kotlin:kotlin-serialization:$mainKotlinVersion")
+ if (requested.id.id == "org.jetbrains.kotlin.plugin.serialization") {
+ useVersion("$mainKotlinVersion")
}
}
}
diff --git a/integration-test/src/commonMain/kotlin/sample/Data.kt b/integration-test/src/commonMain/kotlin/sample/Data.kt
index fc780b4..edd9654 100644
--- a/integration-test/src/commonMain/kotlin/sample/Data.kt
+++ b/integration-test/src/commonMain/kotlin/sample/Data.kt
@@ -2,12 +2,11 @@
* Copyright 2017-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+@file:OptIn(ExperimentalSerializationApi::class)
+
package sample
-import kotlinx.serialization.Polymorphic
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.Transient
+import kotlinx.serialization.*
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
diff --git a/integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt b/integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt
index 206348b..b9686ac 100644
--- a/integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt
@@ -124,7 +124,7 @@
@OptIn(ExperimentalSerializationApi::class)
class KeyValueOutput(val sb: StringBuilder) : AbstractEncoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
sb.append('{')
@@ -137,7 +137,7 @@
override fun encodeElement(descriptor: SerialDescriptor, index: Int): Boolean {
if (index > 0) sb.append(", ")
- sb.append(descriptor.getElementName(index));
+ sb.append(descriptor.getElementName(index))
sb.append(':')
return true
}
@@ -161,7 +161,7 @@
class KeyValueInput(val inp: Parser) : AbstractDecoder() {
- override val serializersModule: SerializersModule = EmptySerializersModule
+ override val serializersModule: SerializersModule = EmptySerializersModule()
override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
inp.expectAfterWhiteSpace('{')
@@ -187,7 +187,7 @@
override fun decodeNotNullMark(): Boolean {
inp.skipWhitespace()
- if (inp.cur != 'n'.toInt()) return true
+ if (inp.cur != 'n'.code) return true
return false
}
@@ -232,7 +232,7 @@
}
fun expect(c: Char) {
- check(cur == c.toInt()) { "Expected '$c'" }
+ check(cur == c.code) { "Expected '$c'" }
next()
}
@@ -256,7 +256,7 @@
private var position: Int = 0
fun read(): Int = when (position) {
str.length -> -1
- else -> str[position++].toInt()
+ else -> str[position++].code
}
}
diff --git a/integration-test/src/commonTest/kotlin/sample/JsonTest.kt b/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
index 6b70435..88a7a0d 100644
--- a/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
@@ -12,7 +12,7 @@
import kotlin.reflect.*
import kotlin.test.*
-public val jsonWithDefaults = Json { encodeDefaults = true }
+val jsonWithDefaults = Json { encodeDefaults = true }
class JsonTest {
@@ -129,10 +129,9 @@
assertEquals("""Derived2(state1='foo')""", restored2.toString())
}
- @Suppress("NAME_SHADOWING")
private fun checkNotRegisteredMessage(exception: SerializationException) {
val expectedText =
- "is not registered for polymorphic serialization in the scope of"
+ "is not found in the polymorphic scope of"
assertEquals(true, exception.message?.contains(expectedText))
}
diff --git a/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt b/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt
index 01d8ca6..0cf9efa 100644
--- a/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt
+++ b/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt
@@ -5,59 +5,58 @@
@Serializable
class EmptyClassB : EmptyBase()
-// TODO: Uncomment when https://youtrack.jetbrains.com/issue/KT-49865 is resolved
-//
-//@Serializable
-//open class Car : Vehicle() {
-// var maxSpeed: Int = 100
-//
-// override fun equals(other: Any?): Boolean {
-// if (this === other) return true
-// if (other !is Car) return false
-// if (name != other.name) return false
-// if (color != other.color) return false
-// if (maxSpeed != other.maxSpeed) return false
-//
-// return true
-// }
-//
-// override fun hashCode(): Int {
-// return maxSpeed.hashCode()
-// }
-//
-// override fun toString(): String {
-// return "Car(name=$name, color=$color, maxSpeed=$maxSpeed)"
-// }
-//}
-//
-//@Serializable
-//data class TestSnippet(
-// @SerialName("experiments") val experiments: List<String>
-//) : Snippet("test", "aaa")
-//
-//@Serializable
-//data class ScreenSnippet(
-// @SerialName("name") val name: String,
-// @SerialName("uuid") val uuid: String? = null,
-// @SerialName("source") val source: String? = null
-//) : Snippet("screen", "aaa")
-//
-//@Serializable
-//class NotInConstructorTest : NotInConstructorBase() {
-// val c = "val c"
-//
-// override fun equals(other: Any?): Boolean {
-// if (this === other) return true
-// if (other !is NotInConstructorTest) return false
-//
-// if (a != other.a) return false
-// if (b != other.b) return false
-// if (c != other.c) return false
-//
-// return true
-// }
-//
-// override fun hashCode(): Int {
-// return a.hashCode() * 31 + b.hashCode() * 31 + c.hashCode()
-// }
-//}
+
+@Serializable
+open class Car : Vehicle() {
+ var maxSpeed: Int = 100
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is Car) return false
+ if (name != other.name) return false
+ if (color != other.color) return false
+ if (maxSpeed != other.maxSpeed) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return maxSpeed.hashCode()
+ }
+
+ override fun toString(): String {
+ return "Car(name=$name, color=$color, maxSpeed=$maxSpeed)"
+ }
+}
+
+@Serializable
+data class TestSnippet(
+ @SerialName("experiments") val experiments: List<String>
+) : Snippet("test", "aaa")
+
+@Serializable
+data class ScreenSnippet(
+ @SerialName("name") val name: String,
+ @SerialName("uuid") val uuid: String? = null,
+ @SerialName("source") val source: String? = null
+) : Snippet("screen", "aaa")
+
+@Serializable
+class NotInConstructorTest : NotInConstructorBase() {
+ val c = "val c"
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is NotInConstructorTest) return false
+
+ if (a != other.a) return false
+ if (b != other.b) return false
+ if (c != other.c) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ return a.hashCode() * 31 + b.hashCode() * 31 + c.hashCode()
+ }
+}
diff --git a/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt b/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt
index 74750ca..d3bddfa 100644
--- a/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt
@@ -28,49 +28,49 @@
val parsed: EmptyClassB = Json.decodeFromString(EmptyClassB.serializer(), serialized)
}
-// @Test
-// fun testCrossModuleInheritance() {
-// val json = Json { allowStructuredMapKeys = true; encodeDefaults = true }
-//
-// val car = Car()
-// car.maxSpeed = 100
-// car.name = "ford"
-// val s = json.encodeToString(Car.serializer(), car)
-// assertEquals("""{"name":"ford","color":null,"maxSpeed":100}""", s)
-// val restoredCar = json.decodeFromString(Car.serializer(), s)
-// assertEquals(car, restoredCar)
-// }
-//
-// @Test
-// fun testCrossModuleAbstractInheritance() {
-// val snippetModule = SerializersModule {
-// polymorphic(Snippet::class) {
-// subclass(ScreenSnippet.serializer())
-// subclass(TestSnippet.serializer())
-// }
-// }
-//
-// val json = Json {
-// serializersModule = snippetModule
-// encodeDefaults = true
-// }
-//
-// val testSnippet = TestSnippet(emptyList())
-// val screenSnippet = ScreenSnippet("one", "two", "three")
-// val s = json.encodeToString(TestSnippet.serializer(), testSnippet)
-// assertEquals(testSnippet, json.decodeFromString(TestSnippet.serializer(), s))
-// assertEquals("""{"objectFieldName":"test","aaa":"aaa","experiments":[]}""",
-// json.encodeToString(TestSnippet.serializer(), testSnippet)
-// )
-// assertStringFormAndRestored("""{"objectFieldName":"screen","aaa":"aaa","name":"one","uuid":"two","source":"three"}""",
-// screenSnippet,
-// ScreenSnippet.serializer(),
-// json
-// )
-// }
-//
-// @Test
-// fun testPropertiesNotInConstructor() {
-// assertStringFormAndRestored("""{"b":"val b","a":"val a","c":"val c"}""", NotInConstructorTest(), NotInConstructorTest.serializer())
-// }
+ @Test
+ fun testCrossModuleInheritance() {
+ val json = Json { allowStructuredMapKeys = true; encodeDefaults = true }
+
+ val car = Car()
+ car.maxSpeed = 100
+ car.name = "ford"
+ val s = json.encodeToString(Car.serializer(), car)
+ assertEquals("""{"name":"ford","color":null,"maxSpeed":100}""", s)
+ val restoredCar = json.decodeFromString(Car.serializer(), s)
+ assertEquals(car, restoredCar)
+ }
+
+ @Test
+ fun testCrossModuleAbstractInheritance() {
+ val snippetModule = SerializersModule {
+ polymorphic(Snippet::class) {
+ subclass(ScreenSnippet.serializer())
+ subclass(TestSnippet.serializer())
+ }
+ }
+
+ val json = Json {
+ serializersModule = snippetModule
+ encodeDefaults = true
+ }
+
+ val testSnippet = TestSnippet(emptyList())
+ val screenSnippet = ScreenSnippet("one", "two", "three")
+ val s = json.encodeToString(TestSnippet.serializer(), testSnippet)
+ assertEquals(testSnippet, json.decodeFromString(TestSnippet.serializer(), s))
+ assertEquals("""{"objectFieldName":"test","aaa":"aaa","experiments":[]}""",
+ json.encodeToString(TestSnippet.serializer(), testSnippet)
+ )
+ assertStringFormAndRestored("""{"objectFieldName":"screen","aaa":"aaa","name":"one","uuid":"two","source":"three"}""",
+ screenSnippet,
+ ScreenSnippet.serializer(),
+ json
+ )
+ }
+
+ @Test
+ fun testPropertiesNotInConstructor() {
+ assertStringFormAndRestored("""{"b":"val b","a":"val a","c":"val c"}""", NotInConstructorTest(), NotInConstructorTest.serializer())
+ }
}
diff --git a/integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt b/integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt
index c16985c..5f7dc91 100644
--- a/integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt
+++ b/integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt
@@ -8,4 +8,4 @@
fun testHello() {
assertTrue("JS" in hello())
}
-}
\ No newline at end of file
+}
diff --git a/integration-test/src/macosMain/kotlin/sample/SampleMacos.kt b/integration-test/src/nativeMain/kotlin/sample/SampleMacos.kt
similarity index 100%
rename from integration-test/src/macosMain/kotlin/sample/SampleMacos.kt
rename to integration-test/src/nativeMain/kotlin/sample/SampleMacos.kt
diff --git a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt b/integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
similarity index 99%
rename from integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
rename to integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
index 5ea6727..f86bf96 100644
--- a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
+++ b/integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
@@ -8,4 +8,4 @@
fun testHello() {
assertTrue("Native" in hello())
}
-}
\ No newline at end of file
+}
diff --git a/integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt b/integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt
new file mode 100644
index 0000000..4d01cf4
--- /dev/null
+++ b/integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample
+
+actual object Platform {
+ actual val name: String = "WasmJs"
+}
diff --git a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt b/integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
similarity index 62%
copy from integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
copy to integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
index 5ea6727..c5fd1a2 100644
--- a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
+++ b/integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
@@ -3,9 +3,9 @@
import kotlin.test.Test
import kotlin.test.assertTrue
-class SampleTestsNative {
+class SampleTestsWasm {
@Test
fun testHello() {
- assertTrue("Native" in hello())
+ assertTrue("WasmJs" in hello())
}
-}
\ No newline at end of file
+}
diff --git a/integration-test/src/wasmWasiMain/kotlin/sample/SampleWasm.kt b/integration-test/src/wasmWasiMain/kotlin/sample/SampleWasm.kt
new file mode 100644
index 0000000..e461406
--- /dev/null
+++ b/integration-test/src/wasmWasiMain/kotlin/sample/SampleWasm.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package sample
+
+actual object Platform {
+ actual val name: String = "WasmWasi"
+}
diff --git a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt b/integration-test/src/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt
similarity index 61%
copy from integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
copy to integration-test/src/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt
index 5ea6727..0ba180b 100644
--- a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
+++ b/integration-test/src/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt
@@ -3,9 +3,9 @@
import kotlin.test.Test
import kotlin.test.assertTrue
-class SampleTestsNative {
+class SampleTestsWasm {
@Test
fun testHello() {
- assertTrue("Native" in hello())
+ assertTrue("WasmWasi" in hello())
}
-}
\ No newline at end of file
+}
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
index 0ccaae2..2ecec42 100644
--- a/kotlin-js-store/yarn.lock
+++ b/kotlin-js-store/yarn.lock
@@ -2,11 +2,6 @@
# yarn lockfile v1
-"@ungap/promise-all-settled@1.1.2":
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
- integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
-
ansi-colors@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@@ -55,6 +50,13 @@
balanced-match "^1.0.0"
concat-map "0.0.1"
+brace-expansion@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+ integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+ dependencies:
+ balanced-match "^1.0.0"
+
braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@@ -85,10 +87,10 @@
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chokidar@3.5.2:
- version "3.5.2"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
- integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
+chokidar@3.5.3:
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
@@ -126,10 +128,10 @@
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-debug@4.3.2:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
- integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
+debug@4.3.4:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
@@ -178,7 +180,7 @@
resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==
-format-util@1.0.5:
+format-util@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271"
integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==
@@ -205,10 +207,10 @@
dependencies:
is-glob "^4.0.1"
-glob@7.1.7:
- version "7.1.7"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
- integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+glob@7.2.0:
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+ integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
@@ -217,11 +219,6 @@
once "^1.3.0"
path-is-absolute "^1.0.0"
-growl@1.10.5:
- version "1.10.5"
- resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
- integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
-
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
@@ -284,11 +281,6 @@
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
-
js-yaml@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@@ -311,39 +303,43 @@
chalk "^4.1.0"
is-unicode-supported "^0.1.0"
-minimatch@3.0.4, minimatch@^3.0.4:
+minimatch@5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+ integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
-mocha@9.1.2:
- version "9.1.2"
- resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.2.tgz#93f53175b0f0dc4014bd2d612218fccfcf3534d3"
- integrity sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w==
+mocha@10.2.0:
+ version "10.2.0"
+ resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.2.0.tgz#1fd4a7c32ba5ac372e03a17eef435bd00e5c68b8"
+ integrity sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==
dependencies:
- "@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
- chokidar "3.5.2"
- debug "4.3.2"
+ chokidar "3.5.3"
+ debug "4.3.4"
diff "5.0.0"
escape-string-regexp "4.0.0"
find-up "5.0.0"
- glob "7.1.7"
- growl "1.10.5"
+ glob "7.2.0"
he "1.2.0"
js-yaml "4.1.0"
log-symbols "4.1.0"
- minimatch "3.0.4"
+ minimatch "5.0.1"
ms "2.1.3"
- nanoid "3.1.25"
+ nanoid "3.3.3"
serialize-javascript "6.0.0"
strip-json-comments "3.1.1"
supports-color "8.1.1"
- which "2.0.2"
- workerpool "6.1.5"
+ workerpool "6.2.1"
yargs "16.2.0"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
@@ -358,10 +354,10 @@
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-nanoid@3.1.25:
- version "3.1.25"
- resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
- integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==
+nanoid@3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+ integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
@@ -435,10 +431,10 @@
dependencies:
randombytes "^2.1.0"
-source-map-support@0.5.20:
- version "0.5.20"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9"
- integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==
+source-map-support@0.5.21:
+ version "0.5.21"
+ resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+ integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
@@ -490,17 +486,15 @@
dependencies:
is-number "^7.0.0"
-which@2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
- integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
- dependencies:
- isexe "^2.0.0"
+typescript@5.0.4:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b"
+ integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
-workerpool@6.1.5:
- version "6.1.5"
- resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581"
- integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==
+workerpool@6.2.1:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+ integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
wrap-ansi@^7.0.0:
version "7.0.0"
diff --git a/rules/common.pro b/rules/common.pro
new file mode 100644
index 0000000..f84d7b9
--- /dev/null
+++ b/rules/common.pro
@@ -0,0 +1,35 @@
+# Keep `Companion` object fields of serializable classes.
+# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
+-if @kotlinx.serialization.Serializable class **
+-keepclassmembers class <1> {
+ static <1>$Companion Companion;
+}
+
+# Keep `serializer()` on companion objects (both default and named) of serializable classes.
+-if @kotlinx.serialization.Serializable class ** {
+ static **$* *;
+}
+-keepclassmembers class <2>$<3> {
+ kotlinx.serialization.KSerializer serializer(...);
+}
+
+# Keep `INSTANCE.serializer()` of serializable objects.
+-if @kotlinx.serialization.Serializable class ** {
+ public static ** INSTANCE;
+}
+-keepclassmembers class <1> {
+ public static <1> INSTANCE;
+ kotlinx.serialization.KSerializer serializer(...);
+}
+
+# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
+-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
+
+# Don't print notes about potential mistakes or omissions in the configuration for kotlinx-serialization classes
+# See also https://github.com/Kotlin/kotlinx.serialization/issues/1900
+-dontnote kotlinx.serialization.**
+
+# Serialization core uses `java.lang.ClassValue` for caching inside these specified classes.
+# If there is no `java.lang.ClassValue` (for example, in Android), then R8/ProGuard will print a warning.
+# However, since in this case they will not be used, we can disable these warnings
+-dontwarn kotlinx.serialization.internal.ClassValueReferences
diff --git a/rules/r8.pro b/rules/r8.pro
new file mode 100644
index 0000000..ad5dd30
--- /dev/null
+++ b/rules/r8.pro
@@ -0,0 +1,12 @@
+# Rule to save runtime annotations on serializable class.
+# If the R8 full mode is used, annotations are removed from classes-files.
+#
+# For the annotation serializer, it is necessary to read the `Serializable` annotation inside the serializer<T>() function - if it is present,
+# then `SealedClassSerializer` is used, if absent, then `PolymorphicSerializer'.
+#
+# When using R8 full mode, all interfaces will be serialized using `PolymorphicSerializer`.
+#
+# see https://github.com/Kotlin/kotlinx.serialization/issues/2050
+
+ -if @kotlinx.serialization.Serializable class **
+ -keep, allowshrinking, allowoptimization, allowobfuscation, allowaccessmodification class <1>
diff --git a/settings.gradle b/settings.gradle
index 10251ef..ed5256e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -2,6 +2,10 @@
* Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+plugins {
+ id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0'
+}
+
rootProject.name = 'kotlinx-serialization'
include ':kotlinx-serialization-core'
@@ -13,6 +17,12 @@
include ':kotlinx-serialization-json'
project(':kotlinx-serialization-json').projectDir = file('./formats/json')
+include ':kotlinx-serialization-json-okio'
+project(':kotlinx-serialization-json-okio').projectDir = file('./formats/json-okio')
+
+include ':kotlinx-serialization-json-tests'
+project(':kotlinx-serialization-json-tests').projectDir = file('./formats/json-tests')
+
include ':kotlinx-serialization-protobuf'
project(':kotlinx-serialization-protobuf').projectDir = file('./formats/protobuf')
diff --git a/update_docs.sh b/update_docs.sh
deleted file mode 100755
index 8659828..0000000
--- a/update_docs.sh
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env bash
-
-# Abort on first error
-set -e
-
-# Directories
-ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
-DIST_DIR="$ROOT_DIR/build/dokka/htmlMultiModule"
-PAGES_DIR="$ROOT_DIR/build/pages"
-
-# Init options
-GRADLE_OPT=
-PUSH_OPT=
-
-# Set dry run if needed
-if [ "$2" == "push" ] ; then
- echo "--- Doing LIVE site deployment, so do clean build"
- GRADLE_OPT=clean
-else
- echo "--- Doing dry-run. To commit do 'update_docs.sh <version> push'"
- PUSH_OPT=--dry-run
-fi
-
-# Makes sure that site is built
-"$ROOT_DIR/gradlew" $GRADLE_OPT dokkaHtmlMultiModule
-
-# Cleanup dist directory (and ignore errors)
-rm -rf "$PAGES_DIR" || true
-
-# Prune worktrees to avoid errors from previous attempts
-git --work-tree "$ROOT_DIR" worktree prune
-
-# Create git worktree for gh-pages branch
-git --work-tree "$ROOT_DIR" worktree add -B gh-pages "$PAGES_DIR" origin/gh-pages
-
-# Now work in newly created workspace
-cd "$PAGES_DIR"
-
-# Fixup all the old documentation files
-# Remove non-.html files
-REMOVE_FILES=$(find . -type f -not -name '.git')
-if [ "$REMOVE_FILES" != "" ] ; then
- git rm $REMOVE_FILES > /dev/null
-fi
-
-# Copy manually new documentation and flat out kotlinx-serialization
-cp -r "$DIST_DIR"/* "$PAGES_DIR"
-
-# Add it all to git
-# git add *
-for file in $(find $PAGES_DIR -type f -name '*'); do git add $file; done
-
-
-# Commit docs for the new version
-if [ -z "$1" ] ; then
- echo "No argument with version supplied -- skipping commit"
-else
- git commit -m "Version $1 docs"
- git push $PUSH_OPT origin gh-pages:gh-pages
-fi