summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClara Fok <clarafok@google.com>2024-04-23 10:28:10 -0700
committerColin Cross <ccross@android.com>2024-04-23 14:03:42 -0700
commit3eabe70bae2e9e08a9325f57c4cc558cbb947257 (patch)
tree1e0665b0be3c1eff26b3b737cb3a17a3a3a13388
parent45d6ad7772c2ea15a55ef6cf30561583da352e99 (diff)
parent3efe324be422ead21ca44f2f6318e1791c166556 (diff)
downloadkotlinx.serialization-3eabe70bae2e9e08a9325f57c4cc558cbb947257.tar.gz
Upgrade kotlinx.serialization to v1.6.3main
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
-rw-r--r--.gitignore5
-rw-r--r--.idea/codeStyles/Project.xml15
-rw-r--r--.idea/codeStyles/codeStyleConfig.xml5
-rw-r--r--.idea/copyright/kotlinx_serialization.xml6
-rw-r--r--.idea/copyright/profiles_settings.xml3
-rw-r--r--.idea/vcs.xml7
-rw-r--r--CHANGELOG.md366
-rw-r--r--CONTRIBUTING.md8
-rw-r--r--README.md181
-rw-r--r--RELEASING.md26
-rw-r--r--benchmark/build.gradle42
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/ContextualOverheadBenchmark.kt72
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/JacksonComparisonBenchmark.kt28
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/LookupOverheadBenchmark.kt69
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/PolymorphismOverheadBenchmark.kt70
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterBenchmark.kt10
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/TwitterFeedBenchmark.kt25
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/json/UseSerializerOverheadBenchmark.kt123
-rw-r--r--benchmark/src/jmh/kotlin/kotlinx/benchmarks/model/MacroTwitterUntailored.kt170
-rw-r--r--build.gradle92
-rw-r--r--buildSrc/build.gradle.kts34
-rw-r--r--buildSrc/src/main/kotlin/Bom.kt22
-rw-r--r--buildSrc/src/main/kotlin/Java9Modularity.kt208
-rw-r--r--buildSrc/src/main/kotlin/KotlinVersion.kt14
-rw-r--r--buildSrc/src/main/kotlin/setupJavaPlugin.kt42
-rw-r--r--core/api/kotlinx-serialization-core.api116
-rw-r--r--core/build.gradle28
-rw-r--r--core/commonMain/src/kotlinx/serialization/Annotations.kt65
-rw-r--r--core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/KSerializer.kt23
-rw-r--r--core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/SealedSerializer.kt14
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerialFormat.kt62
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializationException.kt84
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializationExceptions.kt135
-rw-r--r--core/commonMain/src/kotlinx/serialization/Serializers.kt322
-rw-r--r--core/commonMain/src/kotlinx/serialization/SerializersCache.kt74
-rw-r--r--core/commonMain/src/kotlinx/serialization/builtins/BuiltinSerializers.kt60
-rw-r--r--core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptor.kt48
-rw-r--r--core/commonMain/src/kotlinx/serialization/descriptors/SerialDescriptors.kt18
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt7
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/ChunkedDecoder.kt51
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt58
-rw-r--r--core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt43
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt15
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/BuiltInSerializers.kt39
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/ElementMarker.kt10
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Enums.kt73
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt4
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/JsonInternalDependencies.kt12
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/NamedCompanion.kt15
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/NothingSerialDescriptor.kt33
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt3
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Platform.common.kt72
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/PluginGeneratedSerialDescriptor.kt2
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt1
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt220
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Primitives.kt22
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Tagged.kt28
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/Tuples.kt21
-rw-r--r--core/commonMain/src/kotlinx/serialization/internal/ValueClasses.kt (renamed from core/commonMain/src/kotlinx/serialization/internal/InlineClasses.kt)8
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt38
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt23
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt26
-rw-r--r--core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt41
-rw-r--r--core/commonTest/src/kotlinx/serialization/BasicTypesSerializationTest.kt45
-rw-r--r--core/commonTest/src/kotlinx/serialization/CachedSerializersTest.kt52
-rw-r--r--core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt3
-rw-r--r--core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt3
-rw-r--r--core/commonTest/src/kotlinx/serialization/EnumDescriptorsTest.kt101
-rw-r--r--core/commonTest/src/kotlinx/serialization/InheritableSerialInfoTest.kt2
-rw-r--r--core/commonTest/src/kotlinx/serialization/MetaSerializableTest.kt56
-rw-r--r--core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt1
-rw-r--r--core/commonTest/src/kotlinx/serialization/PrimitiveSerialDescriptorTest.kt29
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerialDescriptorAnnotationsTest.kt2
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt7
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt61
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupInterfaceTest.kt51
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupNamedCompanionTest.kt100
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt44
-rw-r--r--core/commonTest/src/kotlinx/serialization/SerializersModuleTest.kt135
-rw-r--r--core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt3
-rw-r--r--core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt14
-rw-r--r--core/commonTest/src/kotlinx/serialization/features/SealedInterfacesSerializationTest.kt100
-rw-r--r--core/commonTest/src/kotlinx/serialization/internal/DummySequentialDecoder.kt65
-rw-r--r--core/commonTest/src/kotlinx/serialization/internal/ObjectSerializerTest.kt18
-rw-r--r--core/commonTest/src/kotlinx/serialization/internal/TuplesTest.kt18
-rw-r--r--core/commonTest/src/kotlinx/serialization/test/CompilerVersions.kt192
-rw-r--r--core/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt7
-rw-r--r--core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt4
-rw-r--r--core/jsMain/src/kotlinx/serialization/SerializersJs.kt (renamed from core/jsMain/src/kotlinx/serialization/Serializers.kt)0
-rw-r--r--core/jsMain/src/kotlinx/serialization/internal/Platform.kt30
-rw-r--r--core/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt107
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/Caching.kt205
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/Platform.kt88
-rw-r--r--core/jvmMain/src/kotlinx/serialization/internal/SuppressAnimalSniffer.kt13
-rw-r--r--core/jvmTest/src/kotlinx/serialization/CachingTest.kt46
-rw-r--r--core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt94
-rw-r--r--core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt37
-rw-r--r--core/nativeMain/src/kotlinx/serialization/internal/Platform.kt33
-rw-r--r--core/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt2
-rw-r--r--core/wasmMain/src/kotlinx/serialization/Serializers.kt13
-rw-r--r--core/wasmMain/src/kotlinx/serialization/internal/Platform.kt62
-rw-r--r--core/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--docs/basic-serialization.md20
-rw-r--r--docs/building.md29
-rw-r--r--docs/builtin-classes.md66
-rw-r--r--docs/formats.md96
-rw-r--r--docs/inline-classes.md206
-rw-r--r--docs/json.md411
-rw-r--r--docs/polymorphism.md64
-rw-r--r--docs/serialization-guide.md20
-rw-r--r--docs/serializers.md199
-rw-r--r--docs/value-classes.md204
-rw-r--r--dokka-templates/README.md12
-rw-r--r--dokka/moduledoc.md14
-rw-r--r--formats/README.md161
-rw-r--r--formats/cbor/api/kotlinx-serialization-cbor.api2
-rw-r--r--formats/cbor/commonMain/src/kotlinx/serialization/cbor/Cbor.kt2
-rw-r--r--formats/cbor/commonMain/src/kotlinx/serialization/cbor/internal/Encoding.kt8
-rw-r--r--formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt1
-rw-r--r--formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborReaderTest.kt28
-rw-r--r--formats/cbor/commonTest/src/kotlinx/serialization/cbor/CborWriterTest.kt24
-rw-r--r--formats/cbor/commonTest/src/kotlinx/serialization/cbor/SampleClasses.kt19
-rw-r--r--formats/hocon/api/kotlinx-serialization-hocon.api26
-rw-r--r--formats/hocon/build.gradle15
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/Hocon.kt106
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconDecoder.kt47
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoder.kt175
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconEncoders.kt147
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/HoconExceptions.kt3
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/HoconDuration.kt62
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/internal/SuppressAnimalSniffer.kt10
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/ConfigMemorySizeSerializer.kt70
-rw-r--r--formats/hocon/src/main/kotlin/kotlinx/serialization/hocon/serializers/JavaDurationSerializer.kt52
-rw-r--r--formats/hocon/src/mainModule/kotlin/module-info.java1
-rw-r--r--formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconDurationTest.kt196
-rw-r--r--formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconJavaDurationTest.kt177
-rw-r--r--formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconMemorySizeTest.kt175
-rw-r--r--formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconObjectsTest.kt10
-rw-r--r--formats/hocon/src/test/kotlin/kotlinx/serialization/hocon/HoconPolymorphismTest.kt48
-rw-r--r--formats/json-okio/api/kotlinx-serialization-json-okio.api7
-rw-r--r--formats/json-okio/build.gradle.kts62
-rw-r--r--formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/OkioStreams.kt124
-rw-r--r--formats/json-okio/commonMain/src/kotlinx/serialization/json/okio/internal/OkioJsonStreams.kt124
-rw-r--r--formats/json-okio/dokka/okio.package.list550
-rw-r--r--formats/json-tests/build.gradle.kts60
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt)1
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt)3
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/JsonPathTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/JsonPathTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt)7
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt)1
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/SerializableClasses.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/SerializableClasses.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/SerializableOnPropertyTypeAndTypealiasTest.kt93
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt)2
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt)5
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/SerializersLookupTest.kt)90
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/TuplesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/TuplesTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt)22
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt)2
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/BinaryPayloadExampleTest.kt)4
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/CollectionSerializerTest.kt)2
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/ContextAndPolymorphicTest.kt)3
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/DefaultPolymorphicSerializerTest.kt36
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/DurationTest.kt25
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/EmojiTest.kt22
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/GenericCustomSerializerTest.kt)60
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/JsonClassDiscriminatorTest.kt)13
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonEnumsCaseInsensitiveTest.kt170
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/JsonNamesTest.kt)8
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyExclusionTest.kt60
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/JsonNamingStrategyTest.kt242
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/LocalClassesTest.kt)35
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/MetaSerializableJsonTest.kt72
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/PartiallyCustomSerializerTest.kt)2
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicDeserializationErrorMessagesTest.kt60
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/PolymorphicOnClassesTest.kt)2
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismTest.kt)38
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/PolymorphismWithAnyTest.kt)53
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/SealedClassesSerializationTest.kt)1
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/SkipDefaults.kt)3
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/inline/EncodeInlineElementTest.kt)15
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesCompleteTest.kt)18
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/inline/InlineClassesTest.kt)57
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/InlineMapQuotedTest.kt66
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt140
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/inline/ValueClassesInSealedHierarchyTest.kt78
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedDiamondTest.kt49
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedInterfacesJsonSerializationTest.kt40
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt)29
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt147
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonChunkedStringDecoderTest.kt74
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonCoerceInputValuesTest.kt)50
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonCustomSerializersTest.kt)12
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt110
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonErrorMessagesTest.kt159
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonExponentTest.kt79
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonHugeDataSerializationTest.kt40
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonMapKeysTest.kt)57
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonModesTest.kt)10
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt)63
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonParserTest.kt)16
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonPrettyPrintTest.kt75
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonTestBase.kt)51
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonTransientTest.kt)3
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeTest.kt)1
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/LenientTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/LenientTest.kt)11
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/MapLikeSerializerTest.kt)1
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/SpecialFloatingPointValuesTest.kt)6
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/TrailingCommaTest.kt128
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeBaseTest.kt153
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonClassDiscriminatorModeTest.kt84
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphismExceptionTest.kt)5
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/JsonTreeDecoderPolymorphicTest.kt43
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/PolymorphicClasses.kt)3
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonArraySerializerTest.kt)23
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNullSerializerTest.kt)19
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt125
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt204
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonTreeTest.kt)2
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/JsonUnquotedLiteralTest.kt140
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/ContextualTest.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/ContextualTest.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt)6
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/TestHelpers.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/TestHelpers.kt)9
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/TestId.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/TestId.kt)0
-rw-r--r--formats/json-tests/commonTest/src/kotlinx/serialization/test/TestingFramework.kt (renamed from formats/json/commonTest/src/kotlinx/serialization/test/TestingFramework.kt)37
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt)0
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt)0
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/DynamicPolymorphismTest.kt)3
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/DynamicToLongTest.kt)6
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicSpecialCasesTest.kt)5
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/EncodeToDynamicTest.kt)7
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt)0
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt)0
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/json/JsonNamesDynamicTest.kt)8
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/json/JsonNamingStrategyDynamicTest.kt39
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--formats/json-tests/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt (renamed from formats/json/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt)0
-rw-r--r--formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$$serializer.classbin0 -> 4896 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo$Companion.classbin0 -> 1208 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/class_loaders/classes/example/Foo.classbin0 -> 2778 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/listing.txt (renamed from formats/json/jvmTest/resources/corner_cases/listing.txt)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/number_1.0.json (renamed from formats/json/jvmTest/resources/corner_cases/number_1.0.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/number_1.000000000000000005.json (renamed from formats/json/jvmTest/resources/corner_cases/number_1.000000000000000005.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/number_1000000000000000.json (renamed from formats/json/jvmTest/resources/corner_cases/number_1000000000000000.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/number_10000000000000000999.json (renamed from formats/json/jvmTest/resources/corner_cases/number_10000000000000000999.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/number_1e-999.json (renamed from formats/json/jvmTest/resources/corner_cases/number_1e-999.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/number_1e6.json (renamed from formats/json/jvmTest/resources/corner_cases/number_1e6.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/object_key_nfc_nfd.json (renamed from formats/json/jvmTest/resources/corner_cases/object_key_nfc_nfd.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/object_key_nfd_nfc.json (renamed from formats/json/jvmTest/resources/corner_cases/object_key_nfd_nfc.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/object_same_key_different_values.json (renamed from formats/json/jvmTest/resources/corner_cases/object_same_key_different_values.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/object_same_key_same_value.json (renamed from formats/json/jvmTest/resources/corner_cases/object_same_key_same_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/object_same_key_unclear_values.json (renamed from formats/json/jvmTest/resources/corner_cases/object_same_key_unclear_values.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/corner_cases/string_1_escaped_invalid_codepoint.json (renamed from formats/json/jvmTest/resources/corner_cases/string_1_escaped_invalid_codepoint.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/corner_cases/string_1_invalid_codepoint.json (renamed from formats/json/jvmTest/resources/corner_cases/string_1_invalid_codepoint.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/corner_cases/string_2_escaped_invalid_codepoints.json (renamed from formats/json/jvmTest/resources/corner_cases/string_2_escaped_invalid_codepoints.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/corner_cases/string_2_invalid_codepoints.json (renamed from formats/json/jvmTest/resources/corner_cases/string_2_invalid_codepoints.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/corner_cases/string_3_escaped_invalid_codepoints.json (renamed from formats/json/jvmTest/resources/corner_cases/string_3_escaped_invalid_codepoints.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/corner_cases/string_3_invalid_codepoints.json (renamed from formats/json/jvmTest/resources/corner_cases/string_3_invalid_codepoints.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corner_cases/string_with_escaped_NULL.json (renamed from formats/json/jvmTest/resources/corner_cases/string_with_escaped_NULL.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/corpus.zip (renamed from formats/json/jvmTest/resources/corpus.zip)bin427009 -> 427009 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/listing.txt (renamed from formats/json/jvmTest/resources/spec_cases/listing.txt)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_1_true_without_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_1_true_without_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_a_invalid_utf8.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_a_invalid_utf8.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_colon_instead_of_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_colon_instead_of_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_comma_after_close.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_comma_after_close.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_comma_and_number.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_comma_and_number.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_double_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_double_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_double_extra_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_double_extra_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_extra_close.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_extra_close.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_extra_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_extra_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_incomplete.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_incomplete.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_incomplete_invalid_value.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_incomplete_invalid_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_inner_array_no_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_inner_array_no_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_invalid_utf8.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_invalid_utf8.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_items_separated_by_semicolon.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_items_separated_by_semicolon.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_just_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_just_comma.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_just_minus.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_just_minus.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_missing_value.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_missing_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_newlines_unclosed.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_newlines_unclosed.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_number_and_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_number_and_comma.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_number_and_several_commas.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_number_and_several_commas.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_spaces_vertical_tab_formfeed.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_spaces_vertical_tab_formfeed.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_array_star_inside.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_star_inside.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_unclosed.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_trailing_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_unclosed_trailing_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_with_new_lines.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_unclosed_with_new_lines.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_array_unclosed_with_object_inside.json (renamed from formats/json/jvmTest/resources/spec_cases/n_array_unclosed_with_object_inside.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_false.json (renamed from formats/json/jvmTest/resources/spec_cases/n_incomplete_false.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_null.json (renamed from formats/json/jvmTest/resources/spec_cases/n_incomplete_null.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_incomplete_true.json (renamed from formats/json/jvmTest/resources/spec_cases/n_incomplete_true.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_multidigit_number_then_00.json (renamed from formats/json/jvmTest/resources/spec_cases/n_multidigit_number_then_00.json)bin4 -> 4 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_bad_value.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_bad_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_bracket_key.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_bracket_key.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_comma_instead_of_colon.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_comma_instead_of_colon.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_double_colon.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_double_colon.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_emoji.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_emoji.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_garbage_at_end.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_garbage_at_end.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_key_with_single_quotes.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_key_with_single_quotes.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_lone_continuation_byte_in_key_and_trailing_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_lone_continuation_byte_in_key_and_trailing_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_colon.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_missing_colon.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_missing_key.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_missing_key.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_missing_semicolon.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_missing_semicolon.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_missing_value.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_missing_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_no-colon.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_no-colon.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_non_string_key.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_non_string_key.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_non_string_key_but_huge_number_instead.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_non_string_key_but_huge_number_instead.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_repeated_null_null.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_repeated_null_null.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_several_trailing_commas.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_several_trailing_commas.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_single_quote.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_single_quote.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_open.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_open.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open_incomplete.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_trailing_comment_slash_open_incomplete.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_object_two_commas_in_a_row.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_two_commas_in_a_row.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_unquoted_key.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_unquoted_key.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_unterminated-value.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_unterminated-value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_with_single_string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_with_single_string.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_object_with_trailing_garbage.json (renamed from formats/json/jvmTest/resources/spec_cases/n_object_with_trailing_garbage.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_single_space.json (renamed from formats/json/jvmTest/resources/spec_cases/n_single_space.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1x.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_1_surrogate_then_escape_u1x.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_accentuated_char_no_quotes.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_accentuated_char_no_quotes.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_backslash_00.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_backslash_00.json)bin6 -> 6 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_escape_x.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_escape_x.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_backslash_bad.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_escaped_backslash_bad.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_ctrl_char_tab.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_escaped_ctrl_char_tab.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_escaped_emoji.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_escaped_emoji.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_escape.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_escape.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_escaped_character.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_escaped_character.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_surrogate.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_surrogate.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_incomplete_surrogate_escape_invalid.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_incomplete_surrogate_escape_invalid.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid-utf-8-in-escape.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_invalid-utf-8-in-escape.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_backslash_esc.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_invalid_backslash_esc.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_unicode_escape.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_invalid_unicode_escape.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_invalid_utf8_after_escape.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_invalid_utf8_after_escape.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_leading_uescaped_thinspace.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_leading_uescaped_thinspace.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_no_quotes_with_bad_escape.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_no_quotes_with_bad_escape.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_single_doublequote.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_single_doublequote.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_single_quote.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_single_quote.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_single_string_no_double_quotes.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_single_string_no_double_quotes.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_start_escape_unclosed.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_start_escape_unclosed.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_crtl_char.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_unescaped_crtl_char.json)bin7 -> 7 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_newline.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_unescaped_newline.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_unescaped_tab.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_unescaped_tab.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_unicode_CapitalU.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_unicode_CapitalU.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_string_with_trailing_garbage.json (renamed from formats/json/jvmTest/resources/spec_cases/n_string_with_trailing_garbage.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_100000_opening_arrays.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_100000_opening_arrays.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_U+2060_word_joined.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_U+2060_word_joined.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_UTF8_BOM_no_data.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_UTF8_BOM_no_data.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_angle_bracket_..json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_angle_bracket_..json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_angle_bracket_null.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_angle_bracket_null.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_array_trailing_garbage.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_array_trailing_garbage.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_array_with_extra_array_close.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_array_with_extra_array_close.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_array_with_unclosed_string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_array_with_unclosed_string.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_ascii-unicode-identifier.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_ascii-unicode-identifier.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_capitalized_True.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_capitalized_True.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_close_unopened_array.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_close_unopened_array.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_comma_instead_of_closing_brace.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_comma_instead_of_closing_brace.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_double_array.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_double_array.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_end_array.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_end_array.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_incomplete_UTF8_BOM.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_incomplete_UTF8_BOM.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_lone-invalid-utf-8.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_lone-invalid-utf-8.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_lone-open-bracket.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_lone-open-bracket.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_no_data.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_no_data.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_null-byte-outside-string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_null-byte-outside-string.json)bin3 -> 3 bytes
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_number_with_trailing_garbage.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_number_with_trailing_garbage.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_followed_by_closing_object.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_object_followed_by_closing_object.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_unclosed_no_value.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_object_unclosed_no_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_with_comment.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_object_with_comment.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_object_with_trailing_garbage.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_object_with_trailing_garbage.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_apostrophe.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_apostrophe.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_object.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_object.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_open_object.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_open_object.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_open_string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_open_string.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_array_string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_array_string.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_object.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_close_array.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_close_array.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_comma.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_comma.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_open_array.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_open_array.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_open_string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_open_string.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_object_string_with_apostrophes.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_object_string_with_apostrophes.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_open_open.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_open_open.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_single_star.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_single_star.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_trailing_#.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_trailing_#.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_uescaped_LF_before_string.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_uescaped_LF_before_string.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_partial_null.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_partial_null.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_false.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_false.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_true.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_array_unfinished_true.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_unclosed_object.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_unclosed_object.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/n_structure_unicode-identifier.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_unicode-identifier.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_whitespace_U+2060_word_joiner.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_whitespace_U+2060_word_joiner.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/n_structure_whitespace_formfeed.json (renamed from formats/json/jvmTest/resources/spec_cases/n_structure_whitespace_formfeed.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_array_empty-string.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_empty-string.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_empty.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_empty.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_ending_with_newline.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_ending_with_newline.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_array_false.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_false.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_heterogeneous.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_heterogeneous.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_array_null.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_null.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_array_with_1_and_newline.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_with_1_and_newline.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_with_leading_space.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_with_leading_space.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_with_several_null.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_with_several_null.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_array_with_trailing_space.json (renamed from formats/json/jvmTest/resources/spec_cases/y_array_with_trailing_space.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_number_0e+1.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_0e+1.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_number_0e1.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_0e1.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_after_space.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_after_space.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_number_double_close_to_zero.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_double_close_to_zero.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_number_int_with_exp.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_int_with_exp.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_number_minus_zero.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_minus_zero.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_int.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_negative_int.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_one.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_negative_one.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_negative_zero.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_negative_zero.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e_neg_exp.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e_neg_exp.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_capital_e_pos_exp.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_capital_e_pos_exp.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_exponent.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_exponent.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_fraction_exponent.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_fraction_exponent.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_neg_exp.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_neg_exp.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_real_pos_exponent.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_real_pos_exponent.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_simple_int.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_simple_int.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_number_simple_real.json (renamed from formats/json/jvmTest/resources/spec_cases/y_number_simple_real.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_object.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_object_basic.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_basic.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_object_duplicated_key.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_duplicated_key.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_object_duplicated_key_and_value.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_duplicated_key_and_value.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_empty.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_empty.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_object_empty_key.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_empty_key.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_escaped_null_in_key.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_escaped_null_in_key.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_extreme_numbers.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_extreme_numbers.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_long_strings.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_long_strings.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_simple.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_simple.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_string_unicode.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_string_unicode.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_object_with_newlines.json (renamed from formats/json/jvmTest/resources/spec_cases/y_object_with_newlines.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_1_2_3_bytes_UTF-8_sequences.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_1_2_3_bytes_UTF-8_sequences.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pair.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pair.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pairs.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_accepted_surrogate_pairs.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_allowed_escapes.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_allowed_escapes.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_backslash_and_u_escaped_zero.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_backslash_and_u_escaped_zero.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_backslash_doublequotes.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_backslash_doublequotes.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_comments.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_comments.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_double_escape_a.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_double_escape_a.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_double_escape_n.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_double_escape_n.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_escaped_control_character.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_escaped_control_character.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_escaped_noncharacter.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_escaped_noncharacter.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_in_array.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_in_array.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_in_array_with_leading_space.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_in_array_with_leading_space.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_last_surrogates_1_and_2.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_last_surrogates_1_and_2.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_nbsp_uescaped.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_nbsp_uescaped.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+10FFFF.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+10FFFF.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+FFFF.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_nonCharacterInUTF-8_U+FFFF.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_null_escape.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_null_escape.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_one-byte-utf-8.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_one-byte-utf-8.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_pi.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_pi.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_reservedCharacterInUTF-8_U+1BFFF.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_reservedCharacterInUTF-8_U+1BFFF.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_simple_ascii.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_simple_ascii.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_space.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_space.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_three-byte-utf-8.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_three-byte-utf-8.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_two-byte-utf-8.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_two-byte-utf-8.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_u+2028_line_sep.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_u+2028_line_sep.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_u+2029_par_sep.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_u+2029_par_sep.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_uEscape.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_uEscape.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_uescaped_newline.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_uescaped_newline.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_unescaped_char_delete.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unescaped_char_delete.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_2.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_2.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+10FFFE_nonchar.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+10FFFE_nonchar.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+1FFFE_nonchar.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+1FFFE_nonchar.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+2064_invisible_plus.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+2064_invisible_plus.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+FDD0_nonchar.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+FDD0_nonchar.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_U+FFFE_nonchar.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_U+FFFE_nonchar.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_unicode_escaped_double_quote.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_unicode_escaped_double_quote.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_string_utf8.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_utf8.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_string_with_del_character.json (renamed from formats/json/jvmTest/resources/spec_cases/y_string_with_del_character.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_false.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_false.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_int.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_int.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_negative_real.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_negative_real.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_null.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_null.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_string.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_string.json)0
-rwxr-xr-xformats/json-tests/jvmTest/resources/spec_cases/y_structure_lonely_true.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_lonely_true.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_structure_string_empty.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_string_empty.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_structure_trailing_newline.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_trailing_newline.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_structure_true_in_array.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_true_in_array.json)0
-rw-r--r--formats/json-tests/jvmTest/resources/spec_cases/y_structure_whitespace_array.json (renamed from formats/json/jvmTest/resources/spec_cases/y_structure_whitespace_array.json)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/BigDecimalTest.kt193
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt112
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/JvmMissingFieldsExceptionTest.kt)20
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/SerializeJavaClassTest.kt)2
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerByTypeCacheTest.kt119
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt)25
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/features/JsonJvmStreamsTest.kt)49
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/features/JsonLazySequenceTest.kt)20
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/features/JsonSequencePathTest.kt)10
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/features/SerializerByTypeTest.kt)30
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonChunkedBase64DecoderTest.kt85
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/JsonConcurrentStressTest.kt78
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/MissingFieldExceptionWithPathTest.kt40
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt)bin5565 -> 5565 bytes
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt)0
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt (renamed from formats/json/jvmTest/src/kotlinx/serialization/test/JsonHelpers.kt)2
-rw-r--r--formats/json-tests/jvmTest/src/kotlinx/serialization/test/TypeToken.kt17
-rw-r--r--formats/json-tests/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt (renamed from formats/json/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt)0
-rw-r--r--formats/json-tests/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt (renamed from formats/json/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt)3
-rw-r--r--formats/json-tests/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt (renamed from formats/json/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt)0
-rw-r--r--formats/json-tests/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--formats/json-tests/wasmTest/src/kotlinx/serialization/test/JsonHelpers.kt19
-rw-r--r--formats/json/api/kotlinx-serialization-json.api80
-rw-r--r--formats/json/build.gradle48
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/Json.kt190
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonAnnotations.kt8
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonConfiguration.kt100
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonContentPolymorphicSerializer.kt9
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonElement.kt135
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonElementBuilders.kt76
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonElementSerializers.kt31
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonNamingStrategy.kt177
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/JsonTransformingSerializer.kt2
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/CharArrayPool.common.kt9
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/Composers.kt64
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt45
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonConfiguration.kt0
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonElementMarker.kt1
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt11
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonIterator.kt (renamed from formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonIterator.kt)9
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonNamesMap.kt89
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonPath.kt2
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStreams.kt70
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt10
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt10
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonTreeReader.kt11
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/Polymorphic.kt56
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/PolymorphismValidator.kt2
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/SchemaCache.kt6
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonDecoder.kt139
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/StreamingJsonEncoder.kt41
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/StringOps.kt2
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/SuppressAnimalSniffer.kt14
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonDecoder.kt99
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/TreeJsonEncoder.kt39
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/AbstractJsonLexer.kt185
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/ReaderJsonLexer.kt (renamed from formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonLexerJvm.kt)85
-rw-r--r--formats/json/commonMain/src/kotlinx/serialization/json/internal/lexer/StringJsonLexer.kt37
-rw-r--r--formats/json/commonTest/src/kotlinx/serialization/features/inline/UnsignedIntegersTest.kt59
-rw-r--r--formats/json/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt44
-rw-r--r--formats/json/commonTest/src/kotlinx/serialization/json/JsonElementDecodingTest.kt54
-rw-r--r--formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt125
-rw-r--r--formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonPrimitiveSerializerTest.kt102
-rw-r--r--formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicDecoders.kt44
-rw-r--r--formats/json/jsMain/src/kotlinx/serialization/json/internal/DynamicEncoders.kt4
-rw-r--r--formats/json/jsMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt28
-rw-r--r--formats/json/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt12
-rw-r--r--formats/json/jsWasmMain/src/kotlinx/serialization/json/JsonSchemaCache.kt (renamed from formats/json/jsMain/src/kotlinx/serialization/json/JsonSchemaCache.kt)0
-rw-r--r--formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt12
-rw-r--r--formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/FormatLanguageJsWasm.kt25
-rw-r--r--formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/JsonToStringWriterJsWasm.kt29
-rw-r--r--formats/json/jsWasmMain/src/kotlinx/serialization/json/internal/createMapForCache.kt (renamed from formats/json/jsMain/src/kotlinx/serialization/json/internal/createMapForCache.kt)0
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/JvmStreams.kt96
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/ArrayPools.kt89
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt32
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/CharsetReader.kt125
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt10
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt (renamed from formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt)42
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/JsonToWriterStringBuilder.kt45
-rw-r--r--formats/json/jvmMain/src/kotlinx/serialization/json/internal/JvmJsonStreams.kt267
-rw-r--r--formats/json/jvmTest/src/kotlinx/serialization/JavaCollectionsTest.kt52
-rw-r--r--formats/json/jvmTest/src/kotlinx/serialization/json/ParallelJsonStressTest.kt22
-rw-r--r--formats/json/nativeMain/src/kotlinx/serialization/json/JsonSchemaCache.kt3
-rw-r--r--formats/json/nativeMain/src/kotlinx/serialization/json/internal/CharArrayPool.kt9
-rw-r--r--formats/json/nativeMain/src/kotlinx/serialization/json/internal/FormatLanguage.kt25
-rw-r--r--formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonStringBuilder.kt28
-rw-r--r--formats/json/nativeMain/src/kotlinx/serialization/json/internal/JsonToStringWriter.kt29
-rw-r--r--formats/properties/commonMain/src/kotlinx/serialization/properties/Properties.kt27
-rw-r--r--formats/properties/commonTest/src/kotlinx/serialization/properties/SealedClassSerializationFromPropertiesTest.kt118
-rw-r--r--formats/protobuf/api/kotlinx-serialization-protobuf.api7
-rw-r--r--formats/protobuf/build.gradle5
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoBuf.kt43
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/ProtoTypes.kt9
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufDecoding.kt6
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedDecoder.kt4
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/ProtobufTaggedEncoder.kt4
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/internal/SuppressAnimalSniffer.kt14
-rw-r--r--formats/protobuf/commonMain/src/kotlinx/serialization/protobuf/schema/ProtoBufSchemaGenerator.kt42
-rw-r--r--formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt1
-rw-r--r--formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNothingTest.kt29
-rw-r--r--formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/ProtobufNullAndDefaultTest.kt3
-rw-r--r--formats/protobuf/commonTest/src/kotlinx/serialization/protobuf/schema/SchemaValidationsTest.kt28
-rw-r--r--formats/protobuf/commonTest/src/kotlinx/serialization/test/CurrentPlatform.common.kt7
-rw-r--r--formats/protobuf/jsTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--formats/protobuf/jvmTest/resources/EnumWithProtoNumber.proto11
-rw-r--r--formats/protobuf/jvmTest/resources/OptionalClass.proto18
-rw-r--r--formats/protobuf/jvmTest/resources/common/schema.proto26
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/PolymorphicWithJvmClassTest.kt1
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/RandomTests.kt4
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3EnumTest.kt90
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MapTest.kt154
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3MessageTest.kt65
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3OneofTest.kt131
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PackedTest.kt99
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3PrimitiveTest.kt77
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3RepeatedTest.kt133
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/conformance/Proto3UnpackedTest.kt84
-rw-r--r--formats/protobuf/jvmTest/src/kotlinx/serialization/protobuf/schema/GenerationTest.kt27
-rw-r--r--formats/protobuf/nativeTest/src/kotlinx/serialization/test/CurrentPlatform.kt4
-rw-r--r--formats/protobuf/testProto/test_data.proto1
-rw-r--r--formats/protobuf/testProto/test_messages_proto3.proto289
-rw-r--r--formats/protobuf/wasmMain/src/kotlinx/serialization/protobuf/internal/Bytes.kt14
-rw-r--r--formats/protobuf/wasmTest/src/kotlinx/serialization/test/CurrentPlatform.kt7
-rw-r--r--gradle.properties27
-rw-r--r--gradle/configure-source-sets.gradle99
-rw-r--r--gradle/dokka.gradle23
-rw-r--r--gradle/kover.gradle2
-rw-r--r--gradle/native-targets.gradle175
-rw-r--r--gradle/publishing.gradle7
-rw-r--r--gradle/teamcity.gradle2
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--guide/build.gradle15
-rw-r--r--guide/example/example-builtin-12.kt12
-rw-r--r--guide/example/example-builtin-13.kt15
-rw-r--r--guide/example/example-formats-10.kt2
-rw-r--r--guide/example/example-formats-11.kt4
-rw-r--r--guide/example/example-formats-12.kt4
-rw-r--r--guide/example/example-formats-13.kt4
-rw-r--r--guide/example/example-formats-14.kt4
-rw-r--r--guide/example/example-formats-15.kt4
-rw-r--r--guide/example/example-formats-16.kt4
-rw-r--r--guide/example/example-json-12.kt16
-rw-r--r--guide/example/example-json-13.kt18
-rw-r--r--guide/example/example-json-14.kt22
-rw-r--r--guide/example/example-json-15.kt13
-rw-r--r--guide/example/example-json-16.kt34
-rw-r--r--guide/example/example-json-17.kt37
-rw-r--r--guide/example/example-json-18.kt19
-rw-r--r--guide/example/example-json-19.kt37
-rw-r--r--guide/example/example-json-20.kt64
-rw-r--r--guide/example/example-json-21.kt42
-rw-r--r--guide/example/example-json-22.kt10
-rw-r--r--guide/example/example-json-23.kt32
-rw-r--r--guide/example/example-json-24.kt30
-rw-r--r--guide/example/example-json-25.kt22
-rw-r--r--guide/example/example-json-26.kt36
-rw-r--r--guide/example/example-json-27.kt59
-rw-r--r--guide/example/example-json-28.kt37
-rw-r--r--guide/example/example-serializer-16.kt9
-rw-r--r--guide/example/example-serializer-17.kt25
-rw-r--r--guide/example/example-serializer-18.kt26
-rw-r--r--guide/example/example-serializer-19.kt33
-rw-r--r--guide/example/example-serializer-20.kt18
-rw-r--r--guide/example/example-serializer-21.kt37
-rw-r--r--guide/example/example-serializer-22.kt18
-rw-r--r--guide/example/example-serializer-23.kt28
-rw-r--r--guide/test/BasicSerializationTest.kt4
-rw-r--r--guide/test/BuiltinClassesTest.kt14
-rw-r--r--guide/test/JsonTest.kt82
-rw-r--r--guide/test/PolymorphismTest.kt14
-rw-r--r--guide/test/SerializersTest.kt32
-rw-r--r--integration-test/build.gradle91
-rw-r--r--integration-test/gradle.properties9
-rw-r--r--integration-test/gradle/wrapper/gradle-wrapper.properties2
-rw-r--r--integration-test/kotlin-js-store/yarn.lock554
-rw-r--r--integration-test/settings.gradle10
-rw-r--r--integration-test/src/commonMain/kotlin/sample/Data.kt7
-rw-r--r--integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt12
-rw-r--r--integration-test/src/commonTest/kotlin/sample/JsonTest.kt5
-rw-r--r--integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt111
-rw-r--r--integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt90
-rw-r--r--integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt2
-rw-r--r--integration-test/src/nativeMain/kotlin/sample/SampleMacos.kt (renamed from integration-test/src/macosMain/kotlin/sample/SampleMacos.kt)0
-rw-r--r--integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt (renamed from integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt)2
-rw-r--r--integration-test/src/wasmJsMain/kotlin/sample/SampleWasm.kt21
-rw-r--r--integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt11
-rw-r--r--integration-test/src/wasmWasiMain/kotlin/sample/SampleWasm.kt21
-rw-r--r--integration-test/src/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt11
-rw-r--r--kotlin-js-store/yarn.lock114
-rw-r--r--rules/common.pro35
-rw-r--r--rules/r8.pro12
-rw-r--r--settings.gradle10
-rwxr-xr-xupdate_docs.sh60
747 files changed, 17297 insertions, 3795 deletions
diff --git a/.gitignore b/.gitignore
index c56b7b91..054cf973 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 @@ target
# 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 00000000..5662cc49
--- /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 00000000..127f8069
--- /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 00000000..52e9052b
--- /dev/null
+++ b/.idea/copyright/kotlinx_serialization.xml
@@ -0,0 +1,6 @@
+ <component name="CopyrightManager">
+ <copyright>
+ <option name="notice" value="Copyright 2017-&amp;#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 00000000..caad99f9
--- /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 cc23b035..9dcab700 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 621bf09e..22d392c3 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 @@ Many thanks to [Paul de Vrieze](https://github.com/pdvrieze) for his valuable co
* 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 @@ Using 1.1.0-RC, you can mark inline classes as `@Serializable` and use them in o
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 @@ This patch release contains several feature improvements as well as bugfixes and
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 6cd235d3..34621f7b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -22,7 +22,7 @@ Submit issues [here](https://github.com/Kotlin/kotlinx.serialization/issues).
* Use a 'feature request' template when creating a new issue.
* Explain why you need the feature &mdash; 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 @@ so do familiarize yourself with the following guidelines.
* 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 @@ so do familiarize yourself with the following guidelines.
* 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 @@ so do familiarize yourself with the following guidelines.
* 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 31a198a5..d69420a9 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 @@ Kotlin serialization consists of a compiler plugin, that generates visitor code
* [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 @@ Project(name=kotlinx.serialization, language=Kotlin)
**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 @@ Groovy DSL:
```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 @@ buildscript {
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 @@ Groovy DSL:
```gradle
buildscript {
- ext.kotlin_version = '1.6.10'
+ ext.kotlin_version = '1.9.22'
repositories { mavenCentral() }
dependencies {
@@ -141,10 +149,11 @@ Then you can `apply plugin` (example in Groovy):
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 @@ repositories {
}
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 @@ repositories {
}
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.
+
+**However, these rules do not affect serializable classes if they have named companion objects.**
+
+If you want to serialize classes with named companion objects, you need to add and edit rules below to your `proguard-rules.pro` configuration.
-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.
+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 named companion rules for ProGuard and R8 compatibility mode</summary>
```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;
-}
+# 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 `serializer()` on companion objects (both default and named) of serializable classes.
--if @kotlinx.serialization.Serializable class ** {
+-if @kotlinx.serialization.Serializable class
+com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
+com.example.myapplication.HasNamedCompanion2
+{
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(...);
+-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
+ static <1>$$serializer INSTANCE;
}
-
-# @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;
-#}
```
+</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).
<details>
-<summary>Example of custom rules</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(...);
-}
+<summary>Example of named companion rules for R8 full mode</summary>
-# Application rules
+```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`.
-# 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(...);
+-if @kotlinx.serialization.Serializable class
+com.example.myapplication.HasNamedCompanion, # <-- List serializable classes with named companions.
+com.example.myapplication.HasNamedCompanion2
+{
+ static **$* *;
}
-# 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(...);
+-keepnames class <1>$$serializer { # -keepnames suffices; class is kept when serializer() is kept.
+ static <1>$$serializer INSTANCE;
}
-# 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.
--keepattributes InnerClasses # Needed for `getDeclaredClasses`.
--keep class com.yourcompany.yourpackage.SerializableClassWithNamedCompanion$$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 @@ Ensure the proper version of Kotlin and serialization version:
```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 @@ Add dependency on serialization runtime library:
<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 5b95f0dc..f8b90e9b 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -41,8 +41,8 @@ If review is not required, commit directly to `dev`.
* 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 @@ If review is not required, commit directly to `dev`.
```
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 8e0e4927..751ad78c 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: 'java'
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 00000000..2773cfc2
--- /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 b8125001..d162418c 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.databind.*
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 @@ open class JacksonComparisonBenchmark {
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 @@ open class JacksonComparisonBenchmark {
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 00000000..ddfc5792
--- /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 00000000..9af856d3
--- /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 5c930ec6..90889fe8 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 @@ package kotlinx.benchmarks.json
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 @@ open class TwitterBenchmark {
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 @@ open class TwitterBenchmark {
@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 e015ad96..837a8ba3 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 @@ package kotlinx.benchmarks.json
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 @@ open class TwitterFeedBenchmark {
*/
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 @@ open class TwitterFeedBenchmark {
@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 00000000..bc3c89d7
--- /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 00000000..fca69cc0
--- /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 69aa68dd..34765ef2 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 @@ buildscript {
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 @@ buildscript {
// 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 @@ apiValidation {
}
knit {
- siteRoot = "https://kotlin.github.io/kotlinx.serialization"
+ siteRoot = "https://kotlinlang.org/api/kotlinx.serialization"
moduleDocs = "build/dokka/htmlMultiModule"
}
@@ -125,6 +142,13 @@ allprojects {
}
}
+ 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 @@ allprojects {
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 @@ subprojects {
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'
}
+
+ // 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
- // Kover setup
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 994e674d..c999bcd2 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 @@ plugins {
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 00000000..7f93ed38
--- /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 05052972..2743b00f 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 @@ object Java9Modularity {
}
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()
-
- doFirst {
- // 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"
+ 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)
}
- 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)
+ 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.
+ "--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 00000000..5ac051ec
--- /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 00000000..40db563d
--- /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 ede49f69..720e5847 100644
--- a/core/api/kotlinx-serialization-core.api
+++ b/core/api/kotlinx-serialization-core.api
@@ -26,6 +26,7 @@ public abstract interface annotation class kotlinx/serialization/EncodeDefault :
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 interface class kotlinx/serialization/KSerializer : kotlinx/seri
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 abstract interface annotation class kotlinx/serialization/Serializer : ja
}
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 final class kotlinx/serialization/builtins/BuiltinSerializersKt {
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 final class kotlinx/serialization/builtins/BuiltinSerializersKt {
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 abstract class kotlinx/serialization/encoding/AbstractEncoder : kotlinx/s
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 final class kotlinx/serialization/internal/DoubleSerializer : kotlinx/ser
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 final class kotlinx/serialization/internal/InlineClassDescriptor : kotlin
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 abstract class kotlinx/serialization/internal/MapLikeSerializer : kotlinx
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 @@ public abstract class kotlinx/serialization/internal/NamedValueEncoder : kotlinx
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 abstract class kotlinx/serialization/internal/TaggedDecoder : kotlinx/ser
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 abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser
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 @@ public abstract class kotlinx/serialization/internal/TaggedEncoder : kotlinx/ser
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 final class kotlinx/serialization/internal/TripleSerializer : kotlinx/ser
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 final class kotlinx/serialization/internal/UByteSerializer : kotlinx/seri
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 final class kotlinx/serialization/internal/UIntSerializer : kotlinx/seria
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 final class kotlinx/serialization/internal/ULongSerializer : kotlinx/seri
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/internal/UnitSerializer : kotlinx/seria
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/SerializersModuleBuilder : kotl
}
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 900c494f..f52837ac 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 @@ kotlin {
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 @@ kotlin {
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 @@ tasks.withType(Jar).named(kotlin.jvm().artifactsTaskName) {
}
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 45b5c1c0..081ee827 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 @@ import kotlin.reflect.*
* @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 @@ public annotation class Serializable(
)
/**
+ * 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 @@ public annotation class Serializable(
* 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 @@ public annotation class Serializer(
* // 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 @@ public annotation class SerialName(val value: String)
/**
* 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 @@ public annotation class Transient
* @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 @@ public annotation class EncodeDefault(val mode: Mode = Mode.ALWAYS) {
* 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 @@ public annotation class SerialInfo
* 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 UseSerializers(vararg val serializerClasses: KClass<out
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 53fd4c30..20e9ce1c 100644
--- a/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/ContextualSerializer.kt
@@ -29,7 +29,7 @@ import kotlin.reflect.*
* @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 3b6c8697..89107bba 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 @@ import kotlinx.serialization.encoding.*
* ```
*
* 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 @@ public interface SerializationStrategy<in T> {
* // 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 @@ public interface SerializationStrategy<in T> {
*
* 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 @@ public interface DeserializationStrategy<T> {
* 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 311f809d..6ee70717 100644
--- a/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/PolymorphicSerializer.kt
@@ -98,7 +98,7 @@ public class PolymorphicSerializer<T : Any>(override val baseClass: KClass<T>) :
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 354229ff..52b7c054 100644
--- a/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/SealedSerializer.kt
@@ -105,9 +105,9 @@ public class SealedClassSerializer<T : Any>(
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 @@ public class SealedClassSerializer<T : Any>(
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 @@ public class SealedClassSerializer<T : Any>(
}.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 e4801a0a..11234b2a 100644
--- a/core/commonMain/src/kotlinx/serialization/SerialFormat.kt
+++ b/core/commonMain/src/kotlinx/serialization/SerialFormat.kt
@@ -19,6 +19,16 @@ import kotlinx.serialization.modules.*
* 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 @@ public interface BinaryFormat : SerialFormat {
/**
* 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 @@ public interface StringFormat : SerialFormat {
/**
* 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 @@ public inline fun <reified T> StringFormat.decodeFromString(string: String): T =
* 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 @@ public fun <T> BinaryFormat.encodeToHexString(serializer: SerializationStrategy<
* 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 @@ public fun <T> BinaryFormat.decodeFromHexString(deserializer: DeserializationStr
* 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 @@ public inline fun <reified T> BinaryFormat.encodeToHexString(value: T): String =
* 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 41631f50..00000000
--- 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 00000000..99f7d0a7
--- /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 a5211877..2489be27 100644
--- a/core/commonMain/src/kotlinx/serialization/Serializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/Serializers.kt
@@ -18,15 +18,37 @@ import kotlin.reflect.*
/**
* 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 @@ public inline fun <reified T> SerializersModule.serializer(): KSerializer<T> {
/**
* 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 @@ private fun SerializersModule.serializerByKTypeImpl(
): 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>.serializer(): KSerializer<T> = serializerOrNull()
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 00000000..cc86e435
--- /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 147f25d9..4bd81012 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.*
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 @@ public fun <K, V> PairSerializer(
* 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 Byte.Companion.serializer(): KSerializer<Byte> = ByteSerializer
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 Short.Companion.serializer(): KSerializer<Short> = ShortSerializer
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 Int.Companion.serializer(): KSerializer<Int> = IntSerializer
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 Long.Companion.serializer(): KSerializer<Long> = LongSerializer
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 @@ public fun <K, V> MapSerializer(
/**
* 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 136df8c3..17fdbfe0 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 @@ import kotlinx.serialization.encoding.*
* 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 @@ import kotlinx.serialization.encoding.*
* 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 @@ import kotlinx.serialization.encoding.*
* ### 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 @@ import kotlinx.serialization.encoding.*
* [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 @@ import kotlinx.serialization.encoding.*
* )
*
* // 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 interface SerialDescriptor {
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 interface SerialDescriptor {
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 f9e51737..cb380aaf 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 @@ import kotlin.reflect.*
* 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 @@ public fun buildClassSerialDescriptor(
* ```
* 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 @@ internal class WrappedSerialDescriptor(override val serialName: String, original
* 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 @@ public class ClassSerialDescriptorBuilder internal constructor(
* 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 @@ public class ClassSerialDescriptorBuilder internal constructor(
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 8e2799c9..ffe6dd37 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractDecoder.kt
@@ -34,7 +34,7 @@ public abstract class AbstractDecoder : Decoder, CompositeDecoder {
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 @@ public abstract class AbstractDecoder : Decoder, CompositeDecoder {
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 f197c40f..384cb8a0 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/AbstractEncoder.kt
@@ -51,7 +51,7 @@ public abstract class AbstractEncoder : Encoder, CompositeEncoder {
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 00000000..016e07e2
--- /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 3e93e3db..dc4aa2ab 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/Decoding.kt
@@ -108,7 +108,7 @@ import kotlinx.serialization.modules.*
*
* ### 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 interface Decoder {
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 @@ public interface Decoder {
* 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 interface CompositeDecoder {
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 @@ public inline fun <T> Decoder.decodeStructure(
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 1113b1c7..2b1dd09c 100644
--- a/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
+++ b/core/commonMain/src/kotlinx/serialization/encoding/Encoding.kt
@@ -207,27 +207,24 @@ public interface Encoder {
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 interface CompositeEncoder {
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 a85b4659..26d3b5e2 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/AbstractPolymorphicSerializer.kt
@@ -58,8 +58,8 @@ public abstract class AbstractPolymorphicSerializer<T : Any> internal constructo
}
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 abstract class AbstractPolymorphicSerializer<T : Any> internal constructo
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 @@ public abstract class AbstractPolymorphicSerializer<T : Any> internal constructo
@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 00000000..2e64a770
--- /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 fca90262..5e6736d3 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 @@ internal class ElementMarker(
}
}
- 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 @@ internal class ElementMarker(
}
}
- 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 1e620154..90800d7b 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Enums.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Enums.kt
@@ -49,20 +49,79 @@ internal class EnumDescriptor(
}
}
-// 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 05fd92be..ec9edc96 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/InlineClassDescriptor.kt
@@ -10,7 +10,6 @@ import kotlinx.serialization.encoding.*
@Suppress("Unused")
@PublishedApi
-@OptIn(ExperimentalSerializationApi::class)
internal class InlineClassDescriptor(
name: String,
generatedSerializer: GeneratedSerializer<*>
@@ -26,7 +25,8 @@ internal class InlineClassDescriptor(
}
}
-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 d79cb8b9..e733827f 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 00000000..0756dc62
--- /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 463e4863..ce366bdc 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/NoOpEncoder.kt
@@ -14,7 +14,7 @@ import kotlinx.serialization.modules.*
*/
@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 00000000..aab492a0
--- /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 ebb9e2e4..ac9ee8e3 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/ObjectSerializer.kt
@@ -41,6 +41,9 @@ internal class ObjectSerializer<T : Any>(serialName: String, private val objectI
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 fde2c36a..ef313ccd 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 @@ internal object InternalHexConverter {
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 @@ internal fun SerialDescriptor.cachedSerialNames(): Set<String> {
return result
}
-@SharedImmutable
private val EMPTY_DESCRIPTOR_ARRAY: Array<SerialDescriptor> = arrayOf()
/**
@@ -85,28 +84,40 @@ internal inline fun <T> SerializationStrategy<*>.cast(): SerializationStrategy<T
@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 BooleanArray.getChecked(index: Int): Boolean
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 7b6efe7e..a954bdab 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 f613c2ad..61272d29 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PluginHelperInterfaces.kt
@@ -8,7 +8,6 @@ import kotlinx.serialization.*
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 7427b160..1e6172f0 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/PrimitiveArraysSerializers.kt
@@ -408,3 +408,223 @@ internal class BooleanArrayBuilder internal constructor(
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 ab127ffa..2d9c5285 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.descriptors.*
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 @@ private val BUILTIN_SERIALIZERS = mapOf(
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 @@ internal class PrimitiveSerialDescriptor(
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 e2e8d8f9..cf71388c 100644
--- a/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
+++ b/core/commonMain/src/kotlinx/serialization/internal/Tagged.kt
@@ -24,12 +24,13 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
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 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
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 @@ public abstract class TaggedEncoder<Tag : Any?> : Encoder, CompositeEncoder {
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 @@ public abstract class NamedValueEncoder : TaggedEncoder<String>() {
@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 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
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 @@ public abstract class TaggedDecoder<Tag : Any?> : Decoder, CompositeDecoder {
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 @@ public abstract class NamedValueDecoder : TaggedDecoder<String>() {
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 1d30047e..1bd21cbe 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.descriptors.*
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 @@ internal sealed class KeyValueSerializer<K, V, R>(
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
index b9738908..90c1f28d 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.descriptors.*
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 @@ internal object UIntSerializer : KSerializer<UInt> {
}
@PublishedApi
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
internal object ULongSerializer : KSerializer<ULong> {
override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.ULong", Long.serializer())
@@ -40,8 +36,6 @@ internal object ULongSerializer : KSerializer<ULong> {
}
@PublishedApi
-@ExperimentalSerializationApi
-@ExperimentalUnsignedTypes
internal object UByteSerializer : KSerializer<UByte> {
override val descriptor: SerialDescriptor = InlinePrimitiveDescriptor("kotlin.UByte", Byte.serializer())
@@ -55,8 +49,6 @@ internal object UByteSerializer : KSerializer<UByte> {
}
@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 31ce4574..1b8d431e 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/PolymorphicModuleBuilder.kt
@@ -21,7 +21,7 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
) {
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 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
* 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 @@ public class PolymorphicModuleBuilder<in Base : Any> @PublishedApi internal cons
/**
* 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 86f66d7a..8a9126d7 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModule.kt
@@ -6,8 +6,8 @@ package kotlinx.serialization.modules
import kotlinx.serialization.*
import kotlinx.serialization.internal.*
+import kotlin.js.*
import kotlin.jvm.*
-import kotlin.native.concurrent.*
import kotlin.reflect.*
/**
@@ -18,6 +18,9 @@ import kotlin.reflect.*
* 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 @@ public sealed class SerializersModule {
"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 @@ public sealed class SerializersModule {
* 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 @@ public sealed class SerializersModule {
/**
* 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 @@ public infix fun SerializersModule.overwriteWith(other: SerializersModule): Seri
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 @@ internal class SerialModuleImpl(
) : 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 @@ internal class SerialModuleImpl(
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 class SerialModuleImpl(
}
}
-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 4c14f4b9..dfb9d819 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleBuilders.kt
@@ -34,6 +34,12 @@ public inline fun SerializersModule(builderAction: SerializersModuleBuilder.() -
}
/**
+ * 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 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
/**
* 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.
+ *
+ * Default serializers provider affects only serialization process. To affect deserialization process, use
+ * [SerializersModuleBuilder.polymorphicDefaultDeserializer].
*
- * This will not affect deserialization.
+ * [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 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
/**
* 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 @@ public class SerializersModuleBuilder @PublishedApi internal constructor() : Ser
@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 c4af77f8..c33d45a4 100644
--- a/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
+++ b/core/commonMain/src/kotlinx/serialization/modules/SerializersModuleCollector.kt
@@ -47,13 +47,13 @@ public interface SerializersModuleCollector {
/**
* 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 @@ public interface SerializersModuleCollector {
/**
* 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 fab9702f..caa2768f 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 @@ class BasicTypesSerializationTest {
// 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 BasicTypesSerializationTest {
}
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 @@ class BasicTypesSerializationTest {
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 d04390af..212169e6 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 @@ class CachedSerializersTest {
@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 714c04c0..12721ddd 100644
--- a/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/CustomPropertyAccessorsTest.kt
@@ -108,9 +108,8 @@ class CustomPropertyAccessorsTest {
*/
-
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 a22be3ff..3fee5873 100644
--- a/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/ElementMarkerTest.kt
@@ -2,10 +2,11 @@ package kotlinx.serialization
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 00000000..5a3d452c
--- /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 a117ad48..cd5b7a0d 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 InheritableSerialInfoTest {
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 00000000..071045a8
--- /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 393e2b59..22de3a4d 100644
--- a/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/core/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -30,7 +30,6 @@ open class PolyBase(val id: Int) {
@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 00000000..deafc085
--- /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 f2010a2d..770ac50b 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 SerialDescriptorAnnotationsTest {
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 78b015b5..1ff2a7ba 100644
--- a/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerialDescriptorBuilderTest.kt
@@ -87,4 +87,11 @@ class SerialDescriptorBuilderTest {
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 4fb61b04..5f5a6f7b 100644
--- a/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupEnumTest.kt
@@ -6,6 +6,7 @@ package kotlinx.serialization
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.encoding.*
+import kotlinx.serialization.internal.EnumSerializer
import kotlinx.serialization.test.*
import kotlin.test.*
@@ -15,8 +16,7 @@ class SerializersLookupEnumTest {
@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 @@ class SerializersLookupEnumTest {
}
}
- @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,45 +43,47 @@ class SerializersLookupEnumTest {
}
}
- @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 testEnumExternalObject() {
- assertSame(EnumExternalObjectSerializer, EnumExternalObject.serializer())
- assertSame(EnumExternalObjectSerializer, serializer<EnumExternalObject>())
+ fun testSerializableEnumSerializer() {
+ assertIs<EnumSerializer<SerializableEnum>>(SerializableEnum.serializer())
+
+ assertSame(SerializableEnum.serializer(), serializer<SerializableEnum>())
}
@Test
- fun testEnumExternalClass() {
- assertIs<EnumExternalClassSerializer>(EnumExternalClass.serializer())
+ fun testSerializableMarkedEnumSerializer() {
+ assertIs<EnumSerializer<SerializableMarkedEnum>>(SerializableMarkedEnum.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>() }
- }
+ assertSame(SerializableMarkedEnum.serializer(), serializer<SerializableMarkedEnum>())
}
@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>() }
- }
+ fun testEnumExternalObject() {
+ assertSame(EnumExternalObjectSerializer, EnumExternalObject.serializer())
+ assertSame(EnumExternalObjectSerializer, serializer<EnumExternalObject>())
+ }
+
+ @Test
+ fun testEnumExternalClass() {
+ 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 00000000..fc770575
--- /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 00000000..65324c4c
--- /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 49efb912..38d2fbf8 100644
--- a/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/SerializersLookupObjectTest.kt
@@ -11,11 +11,10 @@ import kotlin.test.*
@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 @@ class SerializersLookupObjectTest {
}
}
- @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 @@ class SerializersLookupObjectTest {
}
}
- @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 00000000..9e255f27
--- /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 61e42f61..ce9de637 100644
--- a/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
+++ b/core/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
@@ -67,7 +67,6 @@ data class ArraysUmbrella(
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 @@ val umbrellaInstance = TypesUmbrella(
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 dece1704..c3d6ac00 100644
--- a/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt
+++ b/core/commonTest/src/kotlinx/serialization/features/SchemaTest.kt
@@ -7,6 +7,8 @@ package kotlinx.serialization.features
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 @@ class SchemaTest {
@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 00000000..433f9baf
--- /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 00000000..696883c4
--- /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 00000000..2234b8f5
--- /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 00000000..ea0804c5
--- /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 00000000..7bd35c17
--- /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 c47252d7..594ec0b4 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 660d1ef1..86974506 100644
--- a/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
+++ b/core/commonTest/src/kotlinx/serialization/test/TestHelpers.kt
@@ -34,10 +34,6 @@ inline fun noJs(test: () -> Unit) {
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
index 6f7547db..6f7547db 100644
--- a/core/jsMain/src/kotlinx/serialization/Serializers.kt
+++ b/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 25c48146..6bd63391 100644
--- a/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
+++ b/core/jsMain/src/kotlinx/serialization/internal/Platform.kt
@@ -16,19 +16,35 @@ internal actual fun BooleanArray.getChecked(index: Int): Boolean {
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 : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KClass<T>): Array<E> = toTypedArray()
+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 Any.isInstanceOf(kclass: KClass<*>): Boolean = kclass.isInstance(this)
+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 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 b87276e8..23627d17 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 b110f121..b2d8da7c 100644
--- a/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
+++ b/core/jvmMain/src/kotlinx/serialization/SerializersJvm.kt
@@ -17,65 +17,90 @@ import java.lang.reflect.*
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 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing
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 @@ private fun SerializersModule.serializerByJavaTypeImpl(type: Type, failOnMissing
) 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 @@ private fun SerializersModule.typeSerializer(type: Class<*>, failOnMissingTypeAr
}
@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 @@ private fun Type.prettyClass(): Class<*> = when (val it = this) {
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 00000000..191b30c1
--- /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 9dbb5a06..72ec9ea9 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 @@ internal actual inline fun BooleanArray.getChecked(index: Int): Boolean {
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 <T : Any, E : T?> ArrayList<E>.toNativeArrayImpl(eClass: KCl
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 @@ private fun <T: Any> Class<T>.isNotAnnotated(): Boolean {
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 @@ private fun <T: Any> Class<T>.interfaceSerializer(): KSerializer<T>? {
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 <T : Any> invokeSerializerOnCompanion(jClass: Class<*>, vararg args:
}
}
-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 @@ private fun Class<*>.companionOrNull() =
}
@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 @@ private fun <T : Any> Class<T>.findObjectSerializer(): KSerializer<T>? {
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 00000000..7b3cc310
--- /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 00000000..b146c920
--- /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 31eda2fa..332886d4 100644
--- a/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt
+++ b/core/jvmTest/src/kotlinx/serialization/SerializationMethodInvocationOrderTest.kt
@@ -43,15 +43,17 @@ class SerializationMethodInvocationOrderTest {
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 @@ class SerializationMethodInvocationOrderTest {
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 @@ class SerializationMethodInvocationOrderTest {
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 SerializationMethodInvocationOrderTest {
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 @@ class SerializationMethodInvocationOrderTest {
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 @@ class SerializationMethodInvocationOrderTest {
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 356e9bd6..7e92041e 100644
--- a/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt
+++ b/core/jvmTest/src/kotlinx/serialization/SerializeFlatTest.kt
@@ -198,7 +198,7 @@ class SerializeFlatTest() {
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 @@ class SerializeFlatTest() {
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 @@ class SerializeFlatTest() {
override fun encodeString(value: String) {
when (step) {
2 -> if (value == "s1") {
- step++; return
+ step++
+ return
}
}
fail("@$step: encodeString($value)")
@@ -232,7 +235,10 @@ class SerializeFlatTest() {
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 SerializeFlatTest() {
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 @@ class SerializeFlatTest() {
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 @@ class SerializeFlatTest() {
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 e24c1820..2c91769a 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 inline fun BooleanArray.getChecked(index: Int): Boolean {
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 @@ internal actual fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vara
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 9e51d7f4..2691ce04 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 00000000..a4993925
--- /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 00000000..310df028
--- /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 00000000..fd359b72
--- /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 ce959f80..96e70981 100644
--- a/docs/basic-serialization.md
+++ b/docs/basic-serialization.md
@@ -79,7 +79,7 @@ When we run this code we get the exception.
```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 @@ the `null` value to it.
```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 @@ The next chapter covers [Builtin classes](builtin-classes.md).
<!--- 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 533cdcc8..e9d00a07 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.
+
+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 it into the local Maven repository, run `./gradlew publishToMavenLocal`.
+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 @@ dependencies {
}
```
-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 01831154..8671dc70 100644
--- a/docs/builtin-classes.md
+++ b/docs/builtin-classes.md
@@ -23,6 +23,8 @@ including the standard collections, is built into Kotlin Serialization. This cha
* [Deserializing collections](#deserializing-collections)
* [Maps](#maps)
* [Unit and singleton objects](#unit-and-singleton-objects)
+ * [Duration](#duration)
+* [Nothing](#nothing)
<!--- END -->
@@ -68,8 +70,6 @@ Their natural representation in JSON is used.
<!--- TEST -->
-> Experimental unsigned numbers as well as other experimental inline classes are not supported by Kotlin Serialization yet.
-
### Long numbers
@@ -166,6 +166,8 @@ In JSON an enum gets encoded as a string.
{"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 @@ which is explained in the [Polymorphism. Objects](polymorphism.md#objects) secti
> 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 @@ The next chapter covers [Serializers](serializers.md).
[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 790ce5f5..3fcbf9c8 100644
--- a/docs/formats.md
+++ b/docs/formats.md
@@ -557,7 +557,7 @@ import kotlinx.serialization.modules.*
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 @@ import kotlinx.serialization.modules.*
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 @@ A decoder needs to implement more substance.
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 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
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 @@ import kotlinx.serialization.modules.*
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 @@ in addition to the previous code.
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 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
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 @@ import java.io.*
```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 @@ The decoder implementation mirrors encoder's implementation overriding all the p
```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 @@ a size of up to 254 bytes.
<!--- 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 @@ inline fun <reified T> encodeTo(output: DataOutput, value: T) = encodeTo(output,
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 @@ This chapter concludes [Kotlin Serialization Guide](serialization-guide.md).
<!--- 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 a28f51c0..fda81420 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 606d4aca..c637eb1b 100644
--- a/docs/json.md
+++ b/docs/json.md
@@ -20,11 +20,17 @@ In this chapter, we'll walk through features of [JSON](https://www.json.org/json
* [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 @@ It gives the following nice result:
### 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 @@ Project(name=kotlinx.serialization, language=Kotlin)
### 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 @@ As you can see, discriminator from the `Base` class is used:
<!--- 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ Note that the execution will fail if the structure of the data is otherwise diff
### 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ Project(name=kotlinx.serialization, language=Kotlin)
<!--- 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ sealed class Response<out T> {
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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ The next chapter covers [Alternative and custom formats (experimental)](formats.
<!-- 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 @@ The next chapter covers [Alternative and custom formats (experimental)](formats.
<!--- 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 c41228aa..2661e04d 100644
--- a/docs/polymorphism.md
+++ b/docs/polymorphism.md
@@ -41,11 +41,11 @@ Let us start with basic introduction to polymorphism.
### 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 @@ We get an error, because the `OwnedProject` class is not serializable.
```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 @@ fun main() {
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 @@ discriminator property is not emitted into the resulting JSON.
<!--- 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 @@ fun main() {
{"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 @@ We get the exception.
```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 @@ fun main() {
> 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 @@ With the explicit serializer it works as before.
### 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 @@ abstract class Response<out T>
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 @@ fun main() {
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 @@ The next chapter covers [JSON features](json.md).
<!--- 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 e3f28eae..50cb1306 100644
--- a/docs/serialization-guide.md
+++ b/docs/serialization-guide.md
@@ -49,6 +49,8 @@ Once the project is set up, we can start serializing some classes.
* <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 @@ Once the project is set up, we can start serializing some classes.
* <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 @@ Once the project is set up, we can start serializing some classes.
* <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 @@ Once the project is set up, we can start serializing some classes.
* <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 fc4e1a60..e6bf78e3 100644
--- a/docs/serializers.md
+++ b/docs/serializers.md
@@ -24,7 +24,9 @@ In this chapter we'll take a look at serializers in more detail, and we'll see h
* [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 @@ because we don't control the `Date` source code. There are several ways to work
### 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 @@ The `stableReleaseDate` property is serialized with the serialization strategy t
<!--- 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 @@ fun main() {
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 @@ fun main() {
<!--- 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ fun main() {
}
```
-> 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 @@ The next chapter covers [Polymorphism](polymorphism.md).
<!--- 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 00000000..fed90f6a
--- /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 00000000..2b16f5bf
--- /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 e3c12610..cd0462d4 100644
--- a/dokka/moduledoc.md
+++ b/dokka/moduledoc.md
@@ -5,6 +5,10 @@ format implementation.
# 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 @@ You can learn about "Human-Optimized Config Object Notation" or HOCON from libra
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 @@ HOCON serialization format implementation for converting Kotlin classes from and
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 eb29d61f..724b06ad 100644
--- a/formats/README.md
+++ b/formats/README.md
@@ -5,141 +5,32 @@ were not included in the core library.
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 825c55f7..cc75fab8 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 a9bb4763..9e76a8fb 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 @@ public sealed class Cbor(
/**
* 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 0b7a0e0c..b77a18c5 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 @@ internal open class CborWriter(private val cbor: Cbor, protected val encoder: Cb
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 @@ internal open class CborReader(private val cbor: Cbor, protected val decoder: Cb
@Suppress("UNCHECKED_CAST")
decoder.nextByteString() as T
} else {
+ decodeByteArrayAsByteString = decodeByteArrayAsByteString || deserializer.descriptor.isInlineByteString()
super.decodeSerializableValue(deserializer)
}
}
@@ -636,6 +639,11 @@ private fun SerialDescriptor.isByteString(index: Int): Boolean {
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 86aafe42..b4d3c53a 100644
--- a/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/cbor/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -19,7 +19,6 @@ data class SimpleStringInheritor(val s: String, val i: Int) : SimpleAbstract()
@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 edbe5e62..f615d5ed 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 @@ class CborReaderTest {
}
@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 c546bdf6..da7b1287 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 @@ class CbrWriterTest {
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 ad55d042..e4418f47 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.*
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 @@ class CustomByteStringSerializer : KSerializer<CustomByteString> {
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 a29292d0..4afe9d3c 100644
--- a/formats/hocon/api/kotlinx-serialization-hocon.api
+++ b/formats/hocon/api/kotlinx-serialization-hocon.api
@@ -22,8 +22,34 @@ public final class kotlinx/serialization/hocon/HoconBuilder {
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 0da4b083..c5fae36e 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 @@ compileKotlin {
}
}
+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 @@ java {
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 e8728352..163e5627 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 @@ package kotlinx.serialization.hocon
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 @@ import kotlinx.serialization.modules.*
* [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 @@ public sealed class Hocon(
* 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 @@ public sealed class Hocon(
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 @@ public sealed class Hocon(
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 @@ public sealed class Hocon(
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 @@ public sealed class Hocon(
}
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 @@ public sealed class Hocon(
}
}
+ 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 @@ public sealed class Hocon(
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 00000000..a6006ff1
--- /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 e7533198..750b5449 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.*
-
-@ExperimentalSerializationApi
-internal abstract class AbstractHoconEncoder(
- private val hocon: Hocon,
- private val valueConsumer: (ConfigValue) -> Unit,
-) : NamedValueEncoder() {
-
- 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)
-}
-
+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 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()
+sealed interface HoconEncoder {
+
+ /**
+ * 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 00000000..f8c113fc
--- /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 52e711a1..9e103f03 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 @@ internal fun InvalidKeyKindException(value: ConfigValue) = SerializationExceptio
"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 00000000..5fcf443b
--- /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 00000000..fa348bb6
--- /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 00000000..804a3a6c
--- /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 00000000..c5075e3f
--- /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 b828065c..a21583cc 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 00000000..32fc1858
--- /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 00000000..fda78d70
--- /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 00000000..da8b00e6
--- /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 a52974f7..8726ea38 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 @@ package kotlinx.serialization.hocon
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 db038e70..1dbc1f90 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 @@ class HoconPolymorphismTest {
}
@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 @@ class HoconPolymorphismTest {
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 00000000..75effa13
--- /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 00000000..a51fff03
--- /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 00000000..968f5339
--- /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 00000000..1de89713
--- /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 00000000..96713f52
--- /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 00000000..6be0a3a7
--- /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
index cc0158c1..cc0158c1 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/ClassWithMultipleMasksTest.kt
+++ b/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
index cd077e04..cd077e04 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/EncodingCollectionsTest.kt
+++ b/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
index e8a0c487..73d3319a 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/EncodingExtensionsTest.kt
@@ -10,7 +10,6 @@ class EncodingExtensionsTest {
@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
index c7350cee..d361bbb6 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/EnumSerializationTest.kt
@@ -45,7 +45,6 @@ class EnumSerializationTest : JsonTestBase() {
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 @@ class EnumSerializationTest : JsonTestBase() {
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
index c3003ca9..c3003ca9 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/GenericSerializersOnFileTest.kt
+++ b/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
index b2425a1f..b2425a1f 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/JsonOverwriteKeyTest.kt
+++ b/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
index 8d31ba22..8d31ba22 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/JsonPathTest.kt
+++ b/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
index b44a37f3..e0903470 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/NotNullSerializersCompatibilityOnFileTest.kt
@@ -53,7 +53,6 @@ class NotNullSerializersCompatibilityOnFileTest {
@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 @@ class NotNullSerializersCompatibilityOnFileTest {
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
index 6d4d42f2..a185ccb7 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -37,7 +37,6 @@ class PolyDefaultWithId(id: Int) : PolyBase(id)
@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
index 16fdd2fd..16fdd2fd 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/SerializableClasses.kt
+++ b/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 00000000..7c7133c7
--- /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
index 9f838b11..42f2a850 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializationForNullableTypeOnFileTest.kt
@@ -16,7 +16,6 @@ class SerializationForNullableTypeOnFileTest {
@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 @@ class SerializationForNullableTypeOnFileTest {
}
}
- @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
index 64461678..98f3f5e0 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/SerializerForNullableTypeTest.kt
@@ -16,7 +16,6 @@ public class SerializerForNullableTypeTest : JsonTestBase() {
@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 @@ public class SerializerForNullableTypeTest : JsonTestBase() {
@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 @@ public class SerializerForNullableTypeTest : JsonTestBase() {
@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 @@ public class SerializerForNullableTypeTest : JsonTestBase() {
@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
index 18a69404..4b4aebfd 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.modules.*
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 @@ class SerializersLookupTest : JsonTestBase() {
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 @@ class SerializersLookupTest : JsonTestBase() {
}
@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 @@ class SerializersLookupTest : JsonTestBase() {
}
@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 @@ class SerializersLookupTest : JsonTestBase() {
}
@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 @@ class SerializersLookupTest : JsonTestBase() {
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 @@ class SerializersLookupTest : JsonTestBase() {
}
@Test
- fun testSerializableObject() = noLegacyJs {
+ fun testSerializableObject() {
assertSerializedWithType("{}", SampleObject)
}
@@ -174,6 +209,24 @@ class SerializersLookupTest : JsonTestBase() {
}
}
+ 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 @@ class SerializersLookupTest : JsonTestBase() {
}
@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
index 9fd2d5ea..9fd2d5ea 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/TuplesTest.kt
+++ b/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
index 5e6432ea..f878c633 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/UmbrellaTypes.kt
@@ -64,7 +64,6 @@ data class ArraysUmbrella(
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 @@ val umbrellaInstance = TypesUmbrella(
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
index 81a44ef2..3fb4bc04 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/UnknownElementIndexTest.kt
@@ -15,7 +15,7 @@ class UnknownElementIndexTest {
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
index c6a1bbe3..c6a1bbe3 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/builtins/KeyValueSerializersTest.kt
+++ b/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
index c1a98735..047f1a36 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.Test
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 @@ class BinaryPayloadExampleTest {
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
index aa1ad2d0..aa1ad2d0 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/ByteArraySerializerTest.kt
+++ b/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
index ca8116a0..022ef0eb 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
index 8f00ad97..ac24cf04 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 @@ class ContextAndPolymorphicTest {
@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 00000000..9d35a290
--- /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
index 7da16fb8..7da16fb8 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/DerivedContextualSerializerTest.kt
+++ b/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 00000000..0dbb3f9e
--- /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 00000000..1e3904ab
--- /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
index 804dca6c..9c623912 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 @@ class CheckedData<T : Any>(val data: T, val checkSum: ByteArray) {
}
}
-@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 DataWithInt(@Serializable(with = CheckedDataSerializer::class) val da
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 @@ class GenericCustomSerializerTest {
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 @@ class GenericCustomSerializerTest {
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
index ea11f9b3..ea11f9b3 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/InheritanceTest.kt
+++ b/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
index 93719c31..5eebe218 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.*
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 @@ class JsonClassDiscriminatorTest : JsonTestBase() {
@Test
- fun testSealedClassesHaveCustomDiscriminator() = noLegacyJs {
+ fun testSealedClassesHaveCustomDiscriminator() {
val messages = listOf(
SealedMessage.StringMessage("string message", "foo"),
SealedMessage.EOF
@@ -53,7 +52,7 @@ class JsonClassDiscriminatorTest : JsonTestBase() {
}
@Test
- fun testAbstractClassesHaveCustomDiscriminator() = noLegacyJs {
+ fun testAbstractClassesHaveCustomDiscriminator() {
val messages = listOf(
AbstractMessage.StringMessage("string message", "foo"),
AbstractMessage.IntMessage("int message", 42),
@@ -67,7 +66,11 @@ class JsonClassDiscriminatorTest : JsonTestBase() {
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 @@ class JsonClassDiscriminatorTest : JsonTestBase() {
@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 00000000..0e802c19
--- /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
index 6a4e33ad..34044191 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 @@ class JsonNamesTest : JsonTestBase() {
}
@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 @@ class JsonNamesTest : JsonTestBase() {
}
@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 @@ class JsonNamesTest : JsonTestBase() {
}
@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 00000000..b9974543
--- /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 00000000..9e2cf1d4
--- /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
index b9ba6ff4..82a5ca64 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 @@ class LocalClassesTest {
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 @@ class LocalClassesTest {
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 @@ class LocalClassesTest {
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
index c459b6a3..c459b6a3 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/LongAsStringTest.kt
+++ b/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 00000000..af9ef6c2
--- /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
index da5919c1..da5919c1 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/ObjectSerialization.kt
+++ b/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
index 71379d2d..9ad4afec 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 kotlinx.serialization.json.Json
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 00000000..2b2f1f70
--- /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
index 8e859ee2..77004dbc 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 @@ class PolymorphicOnClassesTest {
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
index d05403b8..c4938871 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 @@ class PolymorphismTest : JsonTestBase() {
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
index 9ce0ea9b..07b6e31a 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 @@ class PolymorphismWithAnyTest {
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 @@ class PolymorphismWithAnyTest {
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 @@ class PolymorphismWithAnyTest {
* 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 @@ class PolymorphismWithAnyTest {
@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 @@ class PolymorphismWithAnyTest {
* 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 @@ class PolymorphismWithAnyTest {
MyPolyDataWithPolyBase(
mapOf("a" to PolyDerived("foo")),
PolyDerived("foo")
- )
+ ),
+ mode
)
}
)
@@ -118,17 +120,30 @@ class PolymorphismWithAnyTest {
@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
index 2397c177..2397c177 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/PrimitiveArraySerializersTest.kt
+++ b/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
index f33069b8..f33069b8 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/PropertyInitializerTest.kt
+++ b/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
index 8af90553..a99c2a1d 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.features.sealed.SealedParent
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
index 4cc289a9..4cc289a9 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/SealedPolymorphismTest.kt
+++ b/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
index e991a0db..e991a0db 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/SerializableOnTypeUsageTest.kt
+++ b/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
index a9cf9638..a9cf9638 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/SerializableWithTest.kt
+++ b/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
index ee37b4b8..a7fea75f 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 @@ package kotlinx.serialization.features
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 @@ class SkipDefaultsTest {
}
@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
index b5f332d7..b5f332d7 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/UseSerializersTest.kt
+++ b/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
index 037d7858..aa4866f4 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 @@ object WithUnsignedSerializer : KSerializer<WithUnsigned> {
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
index 363bdaf9..96972f92 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 @@ data class WithGenerics(
class InlineClassesCompleteTest {
@Test
- fun testAllVariantsWithoutNull() = noLegacyJs {
+ fun testAllVariantsWithoutNull() {
val withAll = WithAll(
MyInt(1),
MyInt(2),
@@ -72,15 +72,15 @@ class InlineClassesCompleteTest {
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 @@ class InlineClassesCompleteTest {
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 @@ class InlineClassesCompleteTest {
}
@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
index 1eedafae..f3eb9511 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 @@ data class SimpleContainerForUInt(val i: UInt)
@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 @@ value class ResourceKind(val kind: SampleEnum)
@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 @@ class InlineClassesTest : JsonTestBase() {
}
@Test
- fun testTopLevelOverEnum() = noLegacyJs {
+ fun testTopLevelOverEnum() {
assertJsonFormAndRestored(
ResourceKind.serializer(),
ResourceKind(SampleEnum.OptionC),
@@ -104,7 +131,7 @@ class InlineClassesTest : JsonTestBase() {
}
@Test
- fun testTopLevelWrapper() = noLegacyJs {
+ fun testTopLevelWrapper() {
assertJsonFormAndRestored(
ValueWrapper.serializer(),
ValueWrapper(ResourceType("foo")),
@@ -113,7 +140,7 @@ class InlineClassesTest : JsonTestBase() {
}
@Test
- fun testTopLevelContextual() = noLegacyJs {
+ fun testTopLevelContextual() {
val module = SerializersModule {
contextual<ResourceType>(ResourceType.serializer())
}
@@ -128,7 +155,7 @@ class InlineClassesTest : JsonTestBase() {
@Test
- fun testSimpleContainer() = noLegacyJs {
+ fun testSimpleContainer() {
assertJsonFormAndRestored(
SimpleContainerForUInt.serializer(),
SimpleContainerForUInt(precedent),
@@ -144,7 +171,7 @@ class InlineClassesTest : JsonTestBase() {
)
@Test
- fun testSimpleContainerForList() = noLegacyJs {
+ fun testSimpleContainerForList() {
assertJsonFormAndRestored(
ContainerForList.serializer(UInt.serializer()),
ContainerForList(MyList(listOf(precedent))),
@@ -153,7 +180,7 @@ class InlineClassesTest : JsonTestBase() {
}
@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 00000000..63157d12
--- /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 00000000..5e24c7fa
--- /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 00000000..ed968298
--- /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
index 1bb2b02b..1bb2b02b 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedChild.kt
+++ b/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 00000000..6ba4713b
--- /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 00000000..a2e6bb67
--- /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
index b096c120..b096c120 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/features/sealed/SealedParent.kt
+++ b/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
index a2f4a9df..a2f4a9df 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/AbstractJsonImplicitNullsTest.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
index dee87fd6..4959b7e2 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/BasicTypesSerializationTest.kt
@@ -5,24 +5,37 @@
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}]}}
+ {"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)
+ 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(umbrellaInstance, instance)
- assertNotSame(umbrellaInstance, instance)
+ 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)
@@ -30,8 +43,8 @@ class BasicTypesSerializationTest : JsonTestBase() {
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(2.5f, "2.5", jsonTestingMode)
+ testPrimitive(3.5, "3.5", jsonTestingMode)
testPrimitive('c', "\"c\"", jsonTestingMode)
testPrimitive("string", "\"string\"", jsonTestingMode)
}
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt
index 5db3d773..5db3d773 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/DecodeFromJsonElementTest.kt
+++ b/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 00000000..c2dab089
--- /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 00000000..fca258fb
--- /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
index 1e6b20de..3d7c3322 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 @@ class JsonCoerceInputValuesTest : JsonTestBase() {
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 @@ class JsonCoerceInputValuesTest : JsonTestBase() {
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 @@ class JsonCoerceInputValuesTest : JsonTestBase() {
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
index 2a4dc2c1..2a4dc2c1 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonConfigurationTest.kt
+++ b/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
index 91b65d9b..351866df 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 @@ class JsonCustomSerializersTest : JsonTestBase() {
@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 @@ class JsonCustomSerializersTest : JsonTestBase() {
@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 @@ class JsonCustomSerializersTest : JsonTestBase() {
}
}
- @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 @@ class JsonCustomSerializersTest : JsonTestBase() {
}
}
- @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 @@ class JsonCustomSerializersTest : JsonTestBase() {
}
}
- @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 @@ class JsonCustomSerializersTest : JsonTestBase() {
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
index af54c31b..af54c31b 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonDefaultContextTest.kt
+++ b/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 00000000..3cdfa082
--- /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
index b4f7c716..b4f7c716 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonEncoderDecoderRecursiveTest.kt
+++ b/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 00000000..08d1eefd
--- /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 00000000..0f31ac50
--- /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
index c4618086..c4618086 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonGenericTest.kt
+++ b/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 00000000..0a633268
--- /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
index c06c058c..c06c058c 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonImplicitNullsTest.kt
+++ b/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
index e958cca7..560e51fe 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 ComplexCarrier(val c: IntData)
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 @@ class JsonMapKeysTest : JsonTestBase() {
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 @@ class JsonMapKeysTest : JsonTestBase() {
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 @@ class JsonMapKeysTest : JsonTestBase() {
)
@Test
- fun testStructuredValueMapKeysAllowedWithFlag() = noLegacyJs {
+ fun testStructuredValueMapKeysAllowedWithFlag() {
assertJsonFormAndRestored(
WithComplexValueKey.serializer(),
WithComplexValueKey(mapOf(ComplexCarrier(IntData(42)) to "42")),
@@ -114,7 +141,7 @@ class JsonMapKeysTest : JsonTestBase() {
)
@Test
- fun testPrimitivesAreAllowedAsValueMapKeys() = noLegacyJs {
+ fun testPrimitivesAreAllowedAsValueMapKeys() {
assertJsonFormAndRestored(
WithValueKeyMap.serializer(),
WithValueKeyMap(mapOf(PrimitiveCarrier("fooKey") to 1)),
@@ -124,7 +151,7 @@ class JsonMapKeysTest : JsonTestBase() {
}
@Test
- fun testContextualValuePrimitivesAreAllowedAsValueMapKeys() = noLegacyJs {
+ fun testContextualValuePrimitivesAreAllowedAsValueMapKeys() {
assertJsonFormAndRestored(
WithContextualValueKey.serializer(),
WithContextualValueKey(mapOf(PrimitiveCarrier("fooKey") to 1)),
@@ -143,7 +170,7 @@ class JsonMapKeysTest : JsonTestBase() {
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
index 97993802..e7f107c8 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 @@ class JsonModesTest : JsonTestBase() {
@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
index ee3e8f15..ee3e8f15 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonNumericKeysTest.kt
+++ b/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
index 679a972b..679a972b 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonOptionalTests.kt
diff --git a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
index 87b0f358..892696b8 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
+++ b/formats/json-tests/commonTest/src/kotlinx/serialization/json/JsonParserFailureModesTest.kt
@@ -5,7 +5,6 @@
package kotlinx.serialization.json
import kotlinx.serialization.*
-import kotlinx.serialization.json.internal.*
import kotlinx.serialization.test.*
import kotlin.test.*
@@ -18,35 +17,35 @@ class JsonParserFailureModesTest : JsonTestBase() {
@Test
fun testFailureModes() = parametrizedTest {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
Holder.serializer(),
"""{"id": "}""",
it
)
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
Holder.serializer(),
"""{"id": ""}""",
it
)
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
Holder.serializer(),
"""{"id":a}""",
it
)
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
Holder.serializer(),
"""{"id":2.0}""",
it
)
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
Holder.serializer(),
"""{"id2":2}""",
@@ -54,7 +53,7 @@ class JsonParserFailureModesTest : JsonTestBase() {
)
}
// 9223372036854775807 is Long.MAX_VALUE
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
Holder.serializer(),
"""{"id":${Long.MAX_VALUE}""" + "00" + "}",
@@ -62,21 +61,21 @@ class JsonParserFailureModesTest : JsonTestBase() {
)
}
// -9223372036854775808 is Long.MIN_VALUE
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("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) }
+ 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) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """{""", it) }
- assertFailsWith<JsonDecodingException> { default.decodeFromString(Holder.serializer(), """}""", it) }
- assertFailsWith<JsonDecodingException> { 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
@@ -84,14 +83,14 @@ class JsonParserFailureModesTest : JsonTestBase() {
@Test
fun testBoolean() = parametrizedTest {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
BooleanHolder.serializer(),
"""{"b": fals}""",
it
)
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString(
BooleanHolder.serializer(),
"""{"b": 123}""",
@@ -108,11 +107,11 @@ class JsonParserFailureModesTest : JsonTestBase() {
@Test
fun testOverflow() = parametrizedTest {
// Byte overflow
- assertFailsWith<JsonDecodingException> { default.decodeFromString<PrimitiveHolder>("""{"b": 128}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<PrimitiveHolder>("""{"b": 128}""", it) }
// Short overflow
- assertFailsWith<JsonDecodingException> { default.decodeFromString<PrimitiveHolder>("""{"s": 32768}""", it) }
+ assertFailsWithSerial("JsonDecodingException") { default.decodeFromString<PrimitiveHolder>("""{"s": 32768}""", it) }
// Int overflow
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
default.decodeFromString<PrimitiveHolder>(
"""{"i": 2147483648}""",
it
@@ -134,14 +133,14 @@ class JsonParserFailureModesTest : JsonTestBase() {
@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) }
+ 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) }
}
@@ -153,9 +152,9 @@ class JsonParserFailureModesTest : JsonTestBase() {
@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) }
+ 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
index 123214e2..94f7052c 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 @@ class JsonParserTest : JsonTestBase() {
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 @@ class JsonParserTest : JsonTestBase() {
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 @@ class JsonParserTest : JsonTestBase() {
}
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 00000000..8283e25b
--- /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
index 3fc62489..3fc62489 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonReifiedCollectionsTest.kt
+++ b/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
index 69397764..69397764 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonRootLevelNullTest.kt
+++ b/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
index 5fac878d..5fac878d 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonSealedSubclassTest.kt
+++ b/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
index 4b39308d..6f3b132e 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 @@ abstract class JsonTestBase {
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 @@ abstract class JsonTestBase {
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 @@ abstract class JsonTestBase {
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 @@ abstract class JsonTestBase {
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 @@ abstract class JsonTestBase {
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 @@ abstract class JsonTestBase {
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
index 516587f5..516587f5 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTransformingSerializerTest.kt
+++ b/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
index c46a11d5..ebe06313 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 @@ package kotlinx.serialization.json
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 @@ class JsonTransientTest : JsonTestBase() {
@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
index 336f630e..336f630e 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeAndMapperTest.kt
+++ b/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
index 995459e3..995459e3 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonTreeImplicitNullsTest.kt
+++ b/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
index 3506e1f8..6e600386 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 @@ package kotlinx.serialization.json
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
index 1f6f814f..1f6f814f 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonUnicodeTest.kt
+++ b/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
index 76634bbc..76634bbc 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonUnionEnumTest.kt
+++ b/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
index eccef39d..eccef39d 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/JsonUpdateModeTest.kt
+++ b/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
index b89e853f..d24c7d7c 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 @@ package kotlinx.serialization.json
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 @@ class LenientTest : JsonTestBase() {
@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 @@ class LenientTest : JsonTestBase() {
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
index 37abd954..bd8f1045 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 @@ class MapLikeSerializerTest : JsonTestBase() {
@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
index 745b0747..fad07e62 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 @@ class SpecialFloatingPointValuesTest : JsonTestBase() {
}
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 00000000..0916de57
--- /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 00000000..8fcd5499
--- /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 00000000..b2f47137
--- /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
index d58e26b6..d58e26b6 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonContentPolymorphicSerializerTest.kt
+++ b/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
index f0229046..f0229046 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonDeserializePolymorphicTwiceTest.kt
+++ b/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
index 5722e8df..5722e8df 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonListPolymorphismTest.kt
+++ b/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
index b2adaa71..b2adaa71 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonMapPolymorphismTest.kt
+++ b/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
index 0caa99dd..0caa99dd 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNestedPolymorphismTest.kt
+++ b/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
index ba8d0dfe..ba8d0dfe 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonNullablePolymorphicTest.kt
+++ b/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
index b11e9dad..b11e9dad 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicClassDescriptorTest.kt
+++ b/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
index e47f5790..e47f5790 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPolymorphicObjectTest.kt
+++ b/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
index 13e92316..b7d4f122 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.*
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 @@ class JsonPolymorphismExceptionTest : JsonTestBase() {
}
}
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
Json { serializersModule = serialModule }.decodeFromString(Base.serializer(), """{"type":"derived","nested":null}""", jsonTestingMode)
}
}
@@ -43,7 +44,7 @@ class JsonPolymorphismExceptionTest : JsonTestBase() {
}
}
- 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
index 7a825395..7a825395 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonProhibitedPolymorphicKindsTest.kt
+++ b/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
index e2e10e24..e2e10e24 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/polymorphic/JsonPropertyPolymorphicTest.kt
+++ b/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 00000000..9d8e861d
--- /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
index e46de17a..e70d89c3 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 @@ internal data class OuterBox(@Polymorphic val outerBase: OuterBase, @Polymorphic
@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 @@ internal val polymorphicTestModule = SerializersModule {
}
}
-@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
index ba4672a1..c571ec9e 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 @@ class JsonArraySerializerTest : JsonTestBase() {
@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 @@ class JsonArraySerializerTest : JsonTestBase() {
@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 @@ class JsonArraySerializerTest : JsonTestBase() {
}
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
index 0afbc052..0afbc052 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonNativePrimitivesTest.kt
+++ b/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
index 83de5928..934afcac 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 @@ class JsonNullSerializerTest : JsonTestBase() {
@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 @@ class JsonNullSerializerTest : JsonTestBase() {
@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 00000000..9a65effe
--- /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 00000000..72f8a4fb
--- /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
index 530fc16f..530fc16f 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonSerializerInGenericsTest.kt
+++ b/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
index 00a78a21..dd4d51ef 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 @@ import kotlin.test.*
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 00000000..e8090044
--- /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
index 34c6dc88..34c6dc88 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/json/serializers/Primitives.kt
+++ b/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
index 18a49a52..18a49a52 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionInSealedClassesTest.kt
+++ b/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
index 4a88cb12..4a88cb12 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/modules/SerialNameCollisionTest.kt
+++ b/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
index 0b34f1c7..0b34f1c7 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/ContextualTest.kt
+++ b/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
index c4a6b986..8c3633b4 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
index 349eb43c..349eb43c 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/InternalHexConverter.kt
+++ b/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
index f3b742e3..f3b742e3 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/JsonHelpers.kt
+++ b/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
index d178c871..27ac19f1 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 @@ inline fun noJs(test: () -> Unit) {
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
index c4af25e5..c4af25e5 100644
--- a/formats/json/commonTest/src/kotlinx/serialization/test/TestId.kt
+++ b/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
index 064828a8..3ec07149 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 @@ fun <T : Any> assertSerializedAndRestored(
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 @@ inline fun <reified T : Throwable> assertFailsWithMessage(
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
index 748a2dce..748a2dce 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicSpecialCasesTest.kt
+++ b/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
index 1a0c29c2..1a0c29c2 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/DecodeFromDynamicTest.kt
+++ b/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
index 0af00c66..3ff05ba0 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.*
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 @@ class DynamicPolymorphismTest {
}
@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
index 9c5011a8..2daf0bb2 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
index ead18969..e4190644 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
index 1c3c24c7..74196b7b 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 @@ class EncodeToDynamicTest {
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 @@ class EncodeToDynamicTest {
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
index 00053297..00053297 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/JsonCoerceInputValuesDynamicTest.kt
+++ b/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
index 1191e3c9..1191e3c9 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/json/JsonDynamicImplicitNullsTest.kt
+++ b/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
index 09cf3d4a..0c519fc2 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 @@ class JsonNamesDynamicTest {
}
@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 @@ class JsonNamesDynamicTest {
}
@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 @@ class JsonNamesDynamicTest {
}
@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 00000000..a1f7b0e6
--- /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 00000000..23627d17
--- /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
index 3f98c43d..3f98c43d 100644
--- a/formats/json/jsTest/src/kotlinx/serialization/test/JsonHelpers.kt
+++ b/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 00000000..3d27cb99
--- /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 00000000..9d74dadb
--- /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 00000000..e22f4862
--- /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
index caa82819..caa82819 100644
--- a/formats/json/jvmTest/resources/corner_cases/listing.txt
+++ b/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
index e7a19a6e..e7a19a6e 100644
--- a/formats/json/jvmTest/resources/corner_cases/number_1.0.json
+++ b/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
index c73b7cfc..c73b7cfc 100644
--- a/formats/json/jvmTest/resources/corner_cases/number_1.000000000000000005.json
+++ b/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
index cd38afa7..cd38afa7 100644
--- a/formats/json/jvmTest/resources/corner_cases/number_1000000000000000.json
+++ b/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
index 946d13d3..946d13d3 100644
--- a/formats/json/jvmTest/resources/corner_cases/number_10000000000000000999.json
+++ b/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
index c8ed222f..c8ed222f 100644
--- a/formats/json/jvmTest/resources/corner_cases/number_1e-999.json
+++ b/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
index 1a8b0f78..1a8b0f78 100644
--- a/formats/json/jvmTest/resources/corner_cases/number_1e6.json
+++ b/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
index e4cbc1dc..e4cbc1dc 100644
--- 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
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
index b04ece18..b04ece18 100644
--- 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
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
index 0c4547df..0c4547df 100644
--- 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
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
index e1070184..e1070184 100644
--- 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
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
index 8a76bd4f..8a76bd4f 100644
--- 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
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
index 8e624731..8e624731 100755
--- 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
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
index 916bff92..916bff92 100755
--- 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
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
index 93568e2c..93568e2c 100755
--- 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
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
index 043a72e8..043a72e8 100755
--- 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
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
index 407dc657..407dc657 100755
--- 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
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
index 2fcb0927..2fcb0927 100755
--- 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
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
index 8ca2be59..8ca2be59 100644
--- 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
diff --git a/formats/json/jvmTest/resources/corpus.zip b/formats/json-tests/jvmTest/resources/corpus.zip
index a43f2952..a43f2952 100644
--- a/formats/json/jvmTest/resources/corpus.zip
+++ b/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
index c2f347cf..c2f347cf 100644
--- a/formats/json/jvmTest/resources/spec_cases/listing.txt
+++ b/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
index c14e3f6b..c14e3f6b 100644
--- 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
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
index 38a86e2e..38a86e2e 100644
--- 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
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
index 0d02ad44..0d02ad44 100644
--- 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
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
index 2ccba8d9..2ccba8d9 100644
--- 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
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
index d2c84e37..d2c84e37 100755
--- 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
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
index 0431712b..0431712b 100755
--- 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
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
index 3f01d312..3f01d312 100644
--- 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
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
index c12f9fae..c12f9fae 100644
--- 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
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
index 5f8ce18e..5f8ce18e 100644
--- 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
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
index cc65b0b5..cc65b0b5 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_array_incomplete.json
+++ b/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
index c21a8f6c..c21a8f6c 100644
--- 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
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
index c70b7164..c70b7164 100644
--- 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
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
index 6099d344..6099d344 100644
--- 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
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
index d4bd7314..d4bd7314 100755
--- 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
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
index 9d7077c6..9d7077c6 100755
--- 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
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
index 29501c6c..29501c6c 100755
--- 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
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
index 3a6ba86f..3a6ba86f 100644
--- 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
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
index 64668006..64668006 100644
--- 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
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
index 13f6f1d1..13f6f1d1 100755
--- 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
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
index 0ac408cb..0ac408cb 100755
--- 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
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
index 6cd7cf58..6cd7cf58 100755
--- 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
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
index 5a519464..5a519464 100755
--- 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
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
index 06073305..06073305 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_array_unclosed.json
+++ b/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
index 6604698f..6604698f 100644
--- 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
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
index 4f61de3f..4f61de3f 100644
--- 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
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
index 043a87e2..043a87e2 100644
--- 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
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
index eb18c6a1..eb18c6a1 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_incomplete_false.json
+++ b/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
index c18ef538..c18ef538 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_incomplete_null.json
+++ b/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
index f451ac6d..f451ac6d 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_incomplete_true.json
+++ b/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
index c22507b8..c22507b8 100644
--- 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
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
index a03a8c03..a03a8c03 100644
--- 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
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
index cc443b48..cc443b48 100644
--- 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
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
index 8d563770..8d563770 100644
--- 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
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
index 80e8c7b8..80e8c7b8 100644
--- 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
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
index cb4078ea..cb4078ea 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_object_emoji.json
+++ b/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
index 80c42cba..80c42cba 100644
--- 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
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
index 77c32759..77c32759 100755
--- 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
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
index aa2cb637..aa2cb637 100644
--- 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
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
index b98eff62..b98eff62 100644
--- 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
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
index b4fb0f52..b4fb0f52 100755
--- 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
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
index e3451384..e3451384 100755
--- 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
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
index 3ef538a6..3ef538a6 100644
--- 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
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
index f3797b35..f3797b35 100644
--- 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
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
index b9945b34..b9945b34 100755
--- 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
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
index b37fa86c..b37fa86c 100755
--- 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
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
index f7d2959d..f7d2959d 100755
--- 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
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
index 3c9afe8d..3c9afe8d 100755
--- 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
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
index e5cdf976..e5cdf976 100644
--- 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
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
index a4b02509..a4b02509 100755
--- 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
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
index a372c655..a372c655 100644
--- 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
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
index d557f41c..d557f41c 100644
--- 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
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
index e335136c..e335136c 100644
--- 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
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
index d892e49f..d892e49f 100644
--- 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
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
index 7c639ae6..7c639ae6 100755
--- 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
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
index 8ba13729..8ba13729 100644
--- 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
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
index 7fe699a6..7fe699a6 100644
--- 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
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
index d63f7fbb..d63f7fbb 100644
--- 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
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
index 787c8f0a..787c8f0a 100644
--- 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
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
index 0519ecba..0519ecba 100755
--- a/formats/json/jvmTest/resources/spec_cases/n_single_space.json
+++ b/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
index acec66d8..acec66d8 100644
--- 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
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
index e834b05e..e834b05e 100644
--- 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
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
index a04cd348..a04cd348 100644
--- 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
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
index bfbd2340..bfbd2340 100644
--- 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
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
index fd689569..fd689569 100644
--- 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
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
index b5bf267b..b5bf267b 100644
--- 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
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
index fae29193..fae29193 100644
--- 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
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
index 016fcb47..016fcb47 100755
--- 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
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
index f35ea382..f35ea382 100644
--- 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
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
index a2777542..a2777542 100644
--- 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
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
index 3415c33c..3415c33c 100755
--- 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
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
index 0f2197ea..0f2197ea 100755
--- 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
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
index 75504a65..75504a65 100755
--- 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
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
index bd965606..bd965606 100755
--- 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
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
index 0c430064..0c430064 100644
--- 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
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
index d1eb6092..d1eb6092 100755
--- 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
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
index 7608cb6b..7608cb6b 100644
--- 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
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
index 2f757a25..2f757a25 100644
--- 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
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
index 7b297c63..7b297c63 100755
--- 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
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
index 01bc70ab..01bc70ab 100644
--- 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
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
index 9d68933c..9d68933c 100755
--- 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
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
index caff239b..caff239b 100644
--- 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
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
index f2ba8f84..f2ba8f84 100755
--- 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
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
index db62a46f..db62a46f 100644
--- 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
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
index 9f213480..9f213480 100755
--- 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
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
index 700d3608..700d3608 100644
--- 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
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
index 160264a2..160264a2 100644
--- 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
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
index 17332bb1..17332bb1 100644
--- 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
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
index efe3bd27..efe3bd27 100644
--- 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
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
index a4823eec..a4823eec 100644
--- 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
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
index 81156a69..81156a69 100644
--- 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
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
index 5f282702..5f282702 100755
--- 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
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
index a56fef0b..a56fef0b 100755
--- 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
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
index 617f2625..617f2625 100755
--- 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
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
index 5a745e6f..5a745e6f 100644
--- 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
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
index 6cfb1398..6cfb1398 100755
--- 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
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
index ba6b1788..ba6b1788 100755
--- 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
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
index ef2ab62f..ef2ab62f 100644
--- 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
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
index 7cd88469..7cd88469 100755
--- 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
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
index d2af0c64..d2af0c64 100755
--- 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
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
index ac61b820..ac61b820 100644
--- 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
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
index 058d1626..058d1626 100755
--- 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
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
index 54caf60b..54caf60b 100644
--- 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
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
index bfcdd514..bfcdd514 100755
--- 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
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
index 8b1296ca..8b1296ca 100644
--- 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
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
index 8e2f0bef..8e2f0bef 100644
--- 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
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
index e69de29b..e69de29b 100644
--- 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
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
index 326db144..326db144 100644
--- 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
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
index 0746539d..0746539d 100644
--- 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
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
index aa9ebaec..aa9ebaec 100644
--- 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
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
index 17d04514..17d04514 100644
--- 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
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
index ed1b569b..ed1b569b 100644
--- 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
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
index 9ca2336d..9ca2336d 100644
--- 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
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
index 8bebe3af..8bebe3af 100644
--- 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
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
index 6295fdc3..6295fdc3 100644
--- 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
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
index e870445b..e870445b 100644
--- 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
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
index 7a63c8c5..7a63c8c5 100644
--- 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
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
index 9822a6ba..9822a6ba 100644
--- 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
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
index 42a61936..42a61936 100644
--- 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
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
index 81750b96..81750b96 100644
--- 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
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
index eebc700a..eebc700a 100755
--- 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
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
index 47bc9106..47bc9106 100644
--- 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
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
index 381ede5d..381ede5d 100644
--- 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
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
index 328c30cd..328c30cd 100644
--- 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
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
index 9dba1709..9dba1709 100644
--- 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
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
index 841fd5f8..841fd5f8 100644
--- 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
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
index f59ec20a..f59ec20a 100755
--- 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
diff --git a/formats/json/jvmTest/resources/spec_cases/n_structure_trailing_#.json b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_trailing_#.json
index 89861108..89861108 100644
--- a/formats/json/jvmTest/resources/spec_cases/n_structure_trailing_#.json
+++ b/formats/json-tests/jvmTest/resources/spec_cases/n_structure_trailing_#.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
index df2f0f24..df2f0f24 100755
--- 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
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
index 11209515..11209515 100755
--- 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
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
index 0d591762..0d591762 100644
--- 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
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
index a2ff8504..a2ff8504 100644
--- 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
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
index 3149e8f5..3149e8f5 100644
--- 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
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
index 694d69db..694d69db 100755
--- 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
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
index 7284aea3..7284aea3 100644
--- 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
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
index 81156a69..81156a69 100755
--- 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
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
index a9ea535d..a9ea535d 100755
--- 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
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
index 58229079..58229079 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_array_arraysWithSpaces.json
+++ b/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
index 93b6be2b..93b6be2b 100644
--- 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
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
index 0637a088..0637a088 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_array_empty.json
+++ b/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
index eac5f7b4..eac5f7b4 100755
--- 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
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
index 67b2f076..67b2f076 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_array_false.json
+++ b/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
index d3c1e264..d3c1e264 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_array_heterogeneous.json
+++ b/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
index 500db4a8..500db4a8 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_array_null.json
+++ b/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
index 99482550..99482550 100644
--- 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
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
index 18bfe642..18bfe642 100755
--- 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
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
index 99f6c5d1..99f6c5d1 100755
--- 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
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
index de9e7a94..de9e7a94 100755
--- 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
diff --git a/formats/json/jvmTest/resources/spec_cases/y_number.json b/formats/json-tests/jvmTest/resources/spec_cases/y_number.json
index e5f5cc33..e5f5cc33 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_number.json
+++ b/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
index d1d39670..d1d39670 100755
--- 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
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
index 3283a793..3283a793 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_number_0e1.json
+++ b/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
index 623570d9..623570d9 100644
--- 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
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
index 96555ff7..96555ff7 100755
--- 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
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
index a4ca9e75..a4ca9e75 100755
--- 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
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
index 37af1312..37af1312 100755
--- 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
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
index 8e30f8bd..8e30f8bd 100644
--- 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
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
index 99d21a2a..99d21a2a 100644
--- 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
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
index 37af1312..37af1312 100644
--- 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
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
index 6edbdfcb..6edbdfcb 100644
--- 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
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
index 0a01bd3e..0a01bd3e 100644
--- 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
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
index 5a8fc097..5a8fc097 100644
--- 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
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
index da2522d6..da2522d6 100644
--- 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
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
index 3944a7a4..3944a7a4 100644
--- 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
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
index ca40d3c2..ca40d3c2 100644
--- 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
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
index 343601d5..343601d5 100644
--- 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
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
index e47f69af..e47f69af 100644
--- 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
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
index b02878e5..b02878e5 100644
--- 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
diff --git a/formats/json/jvmTest/resources/spec_cases/y_object.json b/formats/json-tests/jvmTest/resources/spec_cases/y_object.json
index 78262eda..78262eda 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_object.json
+++ b/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
index 646bbe7b..646bbe7b 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_object_basic.json
+++ b/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
index bbc2e1ce..bbc2e1ce 100755
--- 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
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
index 211581c2..211581c2 100755
--- 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
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
index 9e26dfee..9e26dfee 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_object_empty.json
+++ b/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
index c0013d3b..c0013d3b 100755
--- 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
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
index 593f0f67..593f0f67 100644
--- 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
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
index a0d3531c..a0d3531c 100644
--- 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
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
index bdc4a087..bdc4a087 100644
--- 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
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
index dacac917..dacac917 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_object_simple.json
+++ b/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
index 8effdb29..8effdb29 100644
--- 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
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
index 246ec6b3..246ec6b3 100644
--- 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
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
index 9967ddeb..9967ddeb 100755
--- 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
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
index 996875cc..996875cc 100755
--- 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
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
index 3401021e..3401021e 100755
--- 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
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
index 7f495532..7f495532 100644
--- 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
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
index d4439eda..d4439eda 100755
--- 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
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
index ae03243b..ae03243b 100644
--- 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
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
index 2260c20c..2260c20c 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_string_comments.json
+++ b/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
index 6715d6f4..6715d6f4 100644
--- 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
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
index 44ca56c4..44ca56c4 100644
--- 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
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
index 5b014a9c..5b014a9c 100644
--- 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
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
index 2ff52e2c..2ff52e2c 100755
--- 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
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
index 21d7ae4c..21d7ae4c 100755
--- 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
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
index 9e1887c1..9e1887c1 100755
--- 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
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
index 3919cef7..3919cef7 100644
--- 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
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
index 2085ab1a..2085ab1a 100644
--- 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
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
index 059e4d9d..059e4d9d 100755
--- 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
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
index 4c913bd4..4c913bd4 100755
--- 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
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
index c1ad8440..c1ad8440 100644
--- 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
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
index 15718592..15718592 100644
--- 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
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
index 9df11ae8..9df11ae8 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_string_pi.json
+++ b/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
index 10a33a17..10a33a17 100755
--- 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
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
index 8cadf7d0..8cadf7d0 100644
--- 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
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
index efd782cc..efd782cc 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_string_space.json
+++ b/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
index 7620b665..7620b665 100755
--- 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
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
index 108f1d67..108f1d67 100644
--- 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
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
index 461503c3..461503c3 100644
--- 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
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
index 897b6021..897b6021 100755
--- 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
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
index 8cd998c8..8cd998c8 100755
--- 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
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
index f7b41a02..f7b41a02 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_string_uEscape.json
+++ b/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
index 3a5a220b..3a5a220b 100644
--- 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
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
index 7d064f49..7d064f49 100755
--- 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
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
index 3598095b..3598095b 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_string_unicode.json
+++ b/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
index 0bb3b51e..0bb3b51e 100755
--- a/formats/json/jvmTest/resources/spec_cases/y_string_unicodeEscapedBackslash.json
+++ b/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
index a7dcb976..a7dcb976 100644
--- 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
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
index 9a8370b9..9a8370b9 100644
--- 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
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
index c51f8ae4..c51f8ae4 100644
--- 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
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
index 626d5f81..626d5f81 100644
--- 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
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
index 1e23972c..1e23972c 100644
--- 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
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
index 18ef151b..18ef151b 100644
--- 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
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
index 13d261fd..13d261fd 100644
--- 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
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
index 4e625785..4e625785 100755
--- 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
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
index 40878435..40878435 100644
--- a/formats/json/jvmTest/resources/spec_cases/y_string_utf8.json
+++ b/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
index 8bd24907..8bd24907 100755
--- 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
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
index 02e4a84d..02e4a84d 100644
--- 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
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
index f70d7bba..f70d7bba 100755
--- 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
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
index b5135a20..b5135a20 100755
--- 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
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
index ec747fa4..ec747fa4 100644
--- 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
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
index b6e982ca..b6e982ca 100755
--- 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
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
index f32a5804..f32a5804 100755
--- 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
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
index 3cc762b5..3cc762b5 100644
--- 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
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
index 0c3426d4..0c3426d4 100644
--- 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
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
index de601e30..de601e30 100644
--- 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
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
index 2bedf7f2..2bedf7f2 100644
--- 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
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 00000000..a0c4c73a
--- /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 00000000..1f4958a6
--- /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
index b932b5ae..a423bc84 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.json.Json
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 @@ class JvmMissingFieldsExceptionTest {
@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 @@ class JvmMissingFieldsExceptionTest {
assertFailsWithMessages(listOf("p2", "c3")) {
Json {
serializersModule = module
- useArrayPolymorphism = false
}.decodeFromString<PolymorphicWrapper>("""{"nested": {"type": "a", "p1": 1, "c1": 11}}""")
}
}
@@ -112,16 +110,14 @@ class JvmMissingFieldsExceptionTest {
@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 @@ class JvmMissingFieldsExceptionTest {
}
- 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
index cbef36f3..cbef36f3 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/SerializationCasesTest.kt
+++ b/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
index db79b477..a2f900d0 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.text.SimpleDateFormat
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 00000000..b8dd35d1
--- /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
index ebed6f37..ebed6f37 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/SerializerForNullableJavaTypeTest.kt
+++ b/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
index e6fbecb9..dcf3e959 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
+++ b/formats/json-tests/jvmTest/src/kotlinx/serialization/StacktraceRecoveryTest.kt
@@ -17,17 +17,17 @@ class StacktraceRecoveryTest {
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 @@ class StacktraceRecoveryTest {
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 @@ class StacktraceRecoveryTest {
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
index a190a483..a190a483 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/features/ContextualSerializationOnFileTest.kt
+++ b/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
index 6a1f722e..6a1f722e 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/features/InternalInheritanceTest.kt
+++ b/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
index b576a2c1..7019edae 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 @@ class JsonJvmStreamsTest {
}
}
+ 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
index aad9a0f5..23887660 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.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 kotlinx.serialization.test.assertFailsWithSerial
import org.junit.Test
import java.io.*
import kotlin.test.*
@@ -83,6 +83,7 @@ class JsonLazySequenceTest {
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 @@ class JsonLazySequenceTest {
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 @@ class JsonLazySequenceTest {
@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 @@ class JsonLazySequenceTest {
@Test
fun testStrictArrayCheck() {
- assertFailsWith<JsonDecodingException> {
+ assertFailsWithSerial("JsonDecodingException") {
json.decodeToSequence<StringData>(inputStringWsSeparated.asInputStream(), DecodeSequenceMode.ARRAY_WRAPPED)
}
}
@@ -186,4 +187,11 @@ class JsonLazySequenceTest {
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
index 287e4438..554deab1 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 @@ class JsonSequencePathTest {
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
index 5227ca43..a600b9d7 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 org.junit.Test
import java.lang.reflect.*
import kotlin.reflect.*
import kotlin.test.*
+import kotlin.time.*
class SerializerByTypeTest {
@@ -26,10 +27,10 @@ class SerializerByTypeTest {
@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 @@ class SerializerByTypeTest {
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 @@ class SerializerByTypeTest {
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
index 99bc23f9..99bc23f9 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/json/GsonCompatibilityTest.kt
+++ b/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 00000000..35dc16fc
--- /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 00000000..cdcbab79
--- /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 00000000..e56f173e
--- /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
index 96401f72..96401f72 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/json/SpecConformanceTest.kt
+++ b/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
index 91aa17f0..91aa17f0 100644
--- a/formats/json/jvmTest/src/kotlinx/serialization/test/CurrentPlatform.kt
+++ b/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
index ebb49c35..9220bbd3 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 @@ actual fun <T> Json.encodeViaStream(
): 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 00000000..2b04274c
--- /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
index 2ea063db..2ea063db 100644
--- a/formats/json/nativeTest/src/kotlinx/serialization/json/MultiWorkerJsonTest.kt
+++ b/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
index 32806c1f..58249044 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
index 3f98c43d..3f98c43d 100644
--- a/formats/json/nativeTest/src/kotlinx/serialization/test/JsonHelpers.kt
+++ b/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 00000000..fd359b72
--- /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 00000000..6102e586
--- /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 1fe1440e..6874d744 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/JsonArray$Companion {
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/JsonArraySerializer : kotlinx/seri
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 class kotlinx/serialization/json/JsonBuilder {
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 interface annotation class kotlinx/serialization/json/JsonClassD
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 final class kotlinx/serialization/json/JsonConfiguration {
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 final class kotlinx/serialization/json/JsonElementBuildersKt {
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 final class kotlinx/serialization/json/JsonElementBuildersKt {
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 final class kotlinx/serialization/json/JsonElementKt {
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 interface annotation class kotlinx/serialization/json/JsonNames
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 final class kotlinx/serialization/json/JvmStreamsKt {
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 22944459..d35bcb5d 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 plugin: 'kotlinx-serialization'
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 3cdcf109..2a144fef 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 @@ package kotlinx.serialization.json
import kotlinx.serialization.*
import kotlinx.serialization.json.internal.*
import kotlinx.serialization.modules.*
+import kotlinx.serialization.descriptors.*
import kotlin.native.concurrent.*
/**
@@ -50,7 +51,6 @@ import kotlin.native.concurrent.*
* 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 @@ public sealed class Json(
* 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 @@ public sealed class Json(
* @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 @@ public sealed class Json(
}
/**
+ * 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 @@ public sealed class Json(
/**
* 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 @@ public inline fun <reified T> Json.encodeToJsonElement(value: T): JsonElement {
* 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 @@ public class JsonBuilder internal constructor(json: Json) {
/**
* 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 class JsonBuilder internal constructor(json: Json) {
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 @@ public class JsonBuilder internal constructor(json: Json) {
* 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 class JsonBuilder internal constructor(json: Json) {
*/
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 class JsonBuilder internal constructor(json: Json) {
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 @@ public class JsonBuilder internal constructor(json: Json) {
encodeDefaults, ignoreUnknownKeys, isLenient,
allowStructuredMapKeys, prettyPrint, explicitNulls, prettyPrintIndent,
coerceInputValues, useArrayPolymorphism,
- classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames
+ classDiscriminator, allowSpecialFloatingPointValues, useAlternativeNames,
+ namingStrategy, decodeEnumsCaseInsensitive, allowTrailingComma, classDiscriminatorMode
)
}
}
@@ -303,7 +435,7 @@ private class JsonImpl(configuration: JsonConfiguration, module: SerializersModu
}
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 aae69889..4ec5a2b5 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 @@ import kotlin.native.concurrent.*
* 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 612cfc7c..1fa1644e 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 @@ import kotlinx.serialization.*
* 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 class JsonConfiguration internal constructor(
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 @@ public class JsonConfiguration internal constructor(
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 7255a59f..e11b98f5 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 @@ import kotlin.reflect.*
*
* // 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 @@ public abstract class JsonContentPolymorphicSerializer<T : Any>(private val base
/**
* 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 7d213304..74abf34a 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 sealed class JsonPrimitive : JsonElement() {
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 @@ internal class JsonLiteral internal constructor(
return true
}
+ @SuppressAnimalSniffer // Boolean.hashCode(boolean)
public override fun hashCode(): Int {
var result = isString.hashCode()
result = 31 * result + content.hashCode()
@@ -113,7 +187,9 @@ public object JsonNull : JsonPrimitive() {
* 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 @@ public val JsonElement.jsonNull: JsonNull
* 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 @@ public val JsonPrimitive.floatOrNull: Float? get() = content.toFloatOrNull()
* 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 @@ public val JsonPrimitive.contentOrNull: String? get() = if (this is JsonNull) nu
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 c4b925f2..7a25efdf 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 @@ public class JsonObjectBuilder @PublishedApi internal constructor() {
}
/**
- * 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 @@ public fun JsonObjectBuilder.putJsonObject(key: String, builderAction: JsonObjec
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: Number?): JsonElement? = pu
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 @@ public class JsonArrayBuilder @PublishedApi internal constructor() {
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 @@ public class JsonArrayBuilder @PublishedApi internal constructor() {
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 @@ public fun JsonArrayBuilder.addJsonObject(builderAction: JsonObjectBuilder.() ->
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 a6d0d0f4..269f68b4 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.encoding.*
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 @@ import kotlinx.serialization.json.internal.JsonDecodingException
* 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 @@ internal object JsonElementSerializer : KSerializer<JsonElement> {
}
/**
- * 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 @@ internal object JsonPrimitiveSerializer : KSerializer<JsonPrimitive> {
}
/**
- * 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 @@ private object JsonLiteralSerializer : KSerializer<JsonLiteral> {
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 @@ private object JsonLiteralSerializer : KSerializer<JsonLiteral> {
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 @@ private object JsonLiteralSerializer : KSerializer<JsonLiteral> {
}
/**
- * 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 @@ internal object JsonObjectSerializer : KSerializer<JsonObject> {
}
/**
- * 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 00000000..b737fd69
--- /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 8d1485d6..22d87a72 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 @@ public abstract class JsonTransformingSerializer<T : Any>(
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 00000000..920b65b1
--- /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 2bc080f9..abdd1c44 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 @@ internal open class Composer(@JvmField internal val sb: JsonStringBuilder) {
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 @@ internal class ComposerWithPrettyPrint(
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 00000000..275aa71a
--- /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>
+ * &#64;Language(value = "JAVA", prefix = "class X{", suffix = "}")
+ * &#64;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 e69de29b..00000000
--- 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 2535739c..85a4624b 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 d1698db2..c6098dd4 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 @@ internal fun AbstractJsonLexer.throwInvalidFloatingPointDecoded(result: Number):
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 @@ private fun unexpectedFpErrorMessage(value: Number, key: String, output: String)
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
index 79003082..00f36b2b 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 class JsonIteratorWsSeparated<T>(
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 class JsonIteratorArrayWrapped<T>(
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 @@ private class JsonIteratorArrayWrapped<T>(
} 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 @@ private class JsonIteratorArrayWrapped<T>(
* 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 @@ private class JsonIteratorArrayWrapped<T>(
}
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 93e604a7..9128f3a2 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.encoding.*
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.ifEmpty { emptyMap() }
+}
+
+/**
+ * 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)
}
}
- return builder ?: emptyMap()
+
+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.useAlternativeNames] state.
+ * 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 @@ internal fun SerialDescriptor.getJsonNameIndex(json: Json, name: String): Int {
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 @@ internal fun SerialDescriptor.getJsonNameIndexOrThrow(json: Json, name: String,
@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 4e055b23..14e70a42 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 @@ internal class JsonPath {
// 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 00000000..05f025ce
--- /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 f9245d58..00000000
--- 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 00000000..a6a123cc
--- /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 7c01daa8..9cb9bb3c 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 @@ internal class JsonTreeReader(
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 @@ internal class JsonTreeReader(
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 @@ internal class JsonTreeReader(
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 @@ internal class JsonTreeReader(
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 ea65c48a..636f340d 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.*
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 @@ internal inline fun <T> JsonEncoder.encodePolymorphically(
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 checkKind(kind: SerialKind) {
}
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 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 jsonTree = cast<JsonObject>(decodeJsonElement(), deserializer.descriptor)
+ 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 01994f75..e4606fae 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 @@ internal class PolymorphismValidator(
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 de65fb68..b514f6e6 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 @@ private typealias DescriptorData<T> = MutableMap<DescriptorSchemaCache.Key<T>, T
* 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 bf229044..caa1f4a5 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.descriptors.*
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 @@ import kotlin.jvm.*
/**
* [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 @@ internal open class StreamingJsonDecoder(
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 @@ internal open class StreamingJsonDecoder(
}
override fun decodeNotNullMark(): Boolean {
- return !(elementMarker?.isUnmarkedNull ?: false) && lexer.tryConsumeNotNull()
+ return !(elementMarker?.isUnmarkedNull ?: false) && !lexer.tryConsumeNull()
}
override fun decodeNull(): Nothing? {
@@ -142,12 +199,12 @@ internal open class StreamingJsonDecoder(
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 @@ internal open class StreamingJsonDecoder(
* 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 @@ internal open class StreamingJsonDecoder(
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 @@ internal open class StreamingJsonDecoder(
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 @@ internal open class StreamingJsonDecoder(
}
}
- 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 cdaeeb03..cf562de5 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.descriptors.*
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 @@ private val unsignedNumberDescriptors = setOf(
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 @@ internal class StreamingJsonEncoder(
) : 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 @@ internal class StreamingJsonEncoder(
override fun endStructure(descriptor: SerialDescriptor) {
if (mode.end != INVALID) {
composer.unIndent()
- composer.nextItem()
+ composer.nextItemIfNotFirst()
composer.print(mode.end)
}
}
@@ -139,7 +137,7 @@ internal class StreamingJsonEncoder(
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 @@ internal class StreamingJsonEncoder(
}
}
- 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 @@ internal class StreamingJsonEncoder(
// 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 @@ internal class StreamingJsonEncoder(
// 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 99aed338..ed76ba04 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 @@ private fun toHexChar(i: Int) : Char {
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 @@ internal val ESCAPE_STRINGS: Array<String?> = arrayOfNulls<String>(93).apply {
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 00000000..1ca90e71
--- /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 55e23a13..690b35e1 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.json.*
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 @@ private sealed class AbstractJsonTreeDecoder(
@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 @@ private sealed class AbstractJsonTreeDecoder(
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 @@ private sealed class AbstractJsonTreeDecoder(
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 sealed class AbstractJsonTreeDecoder(
}
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 sealed class AbstractJsonTreeDecoder(
}
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 open class JsonTreeDecoder(
*/
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 @@ private open class JsonTreeDecoder(
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 class JsonTreeMapDecoder(json: Json, override val value: JsonObject) : J
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 class JsonTreeListDecoder(json: Json, override val value: JsonArray) : A
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 74f02bf4..5e3c8086 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 kotlinx.serialization.modules.*
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 @@ internal fun <T> Json.writeJson(value: T, serializer: SerializationStrategy<T>):
@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 sealed class AbstractJsonTreeEncoder(
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 @@ private sealed class AbstractJsonTreeEncoder(
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 @@ private sealed class AbstractJsonTreeEncoder(
encodePolymorphically(serializer, value) { polymorphicDiscriminator = it }
} else JsonPrimitiveEncoder(json, nodeConsumer).apply {
encodeSerializableValue(serializer, value)
- endEncode(serializer.descriptor)
}
}
@@ -98,9 +104,20 @@ private sealed class AbstractJsonTreeEncoder(
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 @@ private sealed class AbstractJsonTreeEncoder(
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 @@ private class JsonPrimitiveEncoder(
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 5e1eee7c..f90ee1a0 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 @@ private const val ESC2C_MAX = 0x75
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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
}
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 @@ internal abstract class AbstractJsonLexer {
/**
* 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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
*/
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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
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 @@ internal abstract class AbstractJsonLexer {
++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 @@ internal abstract class AbstractJsonLexer {
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
index eabfd088..24e5b472 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 @@ internal class ReaderJsonLexer(
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 @@ internal class ReaderJsonLexer(
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 @@ internal class ReaderJsonLexer(
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 @@ internal class ReaderJsonLexer(
}
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 0ff980a2..9f2e5190 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 @@ internal class StringJsonLexer(override val source: String) : AbstractJsonLexer(
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 @@ internal class StringJsonLexer(override val source: String) : AbstractJsonLexer(
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 26d67288..00000000
--- 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/JsonBuildersTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/JsonBuildersTest.kt
deleted file mode 100644
index 26248210..00000000
--- 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 d0b105a0..00000000
--- 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/serializers/JsonObjectSerializerTest.kt b/formats/json/commonTest/src/kotlinx/serialization/json/serializers/JsonObjectSerializerTest.kt
deleted file mode 100644
index 7a45c8dd..00000000
--- 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 789fb2ca..00000000
--- 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 a6658c7c..1ff1e40d 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 open class DynamicInput(
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 @@ private open class DynamicInput(
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 @@ private open class DynamicInput(
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 class DynamicMapInput(
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 @@ private class DynamicListInput(
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 4bd46d59..4c4841d4 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 @@ private class DynamicObjectEncoder(
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 @@ private class DynamicPrimitiveEncoder(
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 1b79e27e..00000000
--- 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 b87276e8..00000000
--- 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
index a0820ef4..a0820ef4 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/JsonSchemaCache.kt
+++ b/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 00000000..3f27896f
--- /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 00000000..176771fd
--- /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 00000000..387ed7e9
--- /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
index b51ff401..b51ff401 100644
--- a/formats/json/jsMain/src/kotlinx/serialization/json/internal/createMapForCache.kt
+++ b/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 3b83299c..329e309d 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 @@ import java.io.*
* 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 @@ public fun <T> Json.encodeToStream(
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 @@ public fun <T> Json.encodeToStream(
* 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 @@ public inline fun <reified T> Json.encodeToStream(
* 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 @@ public fun <T> Json.decodeFromStream(
* 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 @@ public enum class DecodeSequenceMode {
* 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 @@ public fun <T> Json.decodeToSequence(
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 @@ public fun <T> Json.decodeToSequence(
* 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 00000000..0d36c6c0
--- /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 e51b3de3..00000000
--- 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 00000000..0f8a566c
--- /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 00000000..9eedf1b8
--- /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
index 37766d99..56667248 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 @@ package kotlinx.serialization.json.internal
* 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 @@ internal actual open class JsonStringBuilder(@JvmField protected var array: Char
size = sz
}
+ actual override fun release() {
+ CharArrayPool.release(array)
+ }
+
actual override fun toString(): String {
return String(array, 0, size)
}
@@ -122,15 +126,11 @@ internal actual open class JsonStringBuilder(@JvmField protected var array: Char
}
// 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 c4069068..00000000
--- 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 00000000..e8e0d9a5
--- /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 a0ff55a2..00000000
--- 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 3901aabe..00000000
--- 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 16e223a0..49619979 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 @@ private val jsonToCache: MutableMap<WeakJson, DescriptorSchemaCache> = mutableMa
/**
* 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 00000000..03c99262
--- /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 00000000..4f2fff0c
--- /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 1b79e27e..00000000
--- 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 00000000..137f3bc7
--- /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 9d411ad6..8760950c 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 @@ public sealed class Properties(
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 @@ public sealed class Properties(
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 @@ public sealed class Properties(
* 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 00000000..f9332324
--- /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 65093b2c..c0d61b9f 100644
--- a/formats/protobuf/api/kotlinx-serialization-protobuf.api
+++ b/formats/protobuf/api/kotlinx-serialization-protobuf.api
@@ -25,6 +25,7 @@ public final class kotlinx/serialization/protobuf/ProtoIntegerType : java/lang/E
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 interface annotation class kotlinx/serialization/protobuf/ProtoN
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 final class kotlinx/serialization/protobuf/ProtoNumber$Impl : kotlinx/ser
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 interface annotation class kotlinx/serialization/protobuf/ProtoT
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 c2183b28..9f93b18f 100644
--- a/formats/protobuf/build.gradle
+++ b/formats/protobuf/build.gradle
@@ -21,8 +21,11 @@ clean {
}
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 8f447ef3..92bb2f5e 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 @@ import kotlin.js.*
/**
* 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 @@ import kotlin.js.*
* 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 @@ import kotlin.js.*
* @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 @@ public sealed class ProtoBuf(
/**
* 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 3b62d4dc..109ffb83 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 @@ import kotlinx.serialization.descriptors.*
* 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 @@ import kotlinx.serialization.descriptors.*
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 @@ public annotation class ProtoType(public val type: ProtoIntegerType)
/**
- * 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 09773919..861e2bf3 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 @@ internal open class ProtobufDecoder(
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 @@ internal open class ProtobufDecoder(
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 8a5a3827..953c1b3c 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 @@ internal abstract class ProtobufTaggedDecoder : ProtobufTaggedBase(), Decoder, C
}
}
- 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 01532df3..84e58399 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 @@ internal abstract class ProtobufTaggedEncoder : ProtobufTaggedBase(), Encoder, C
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 00000000..35607ec9
--- /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 e54370fb..4f4ca9c4 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 @@ public object ProtoBufSchemaGenerator {
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 @@ public object ProtoBufSchemaGenerator {
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 @@ public object ProtoBufSchemaGenerator {
}
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 @@ public object ProtoBufSchemaGenerator {
}
}
+ @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 7ef97293..eb6ebe7e 100644
--- a/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
+++ b/formats/protobuf/commonTest/src/kotlinx/serialization/PolymorphismTestData.kt
@@ -46,7 +46,6 @@ data class SimpleStringInheritor(val s: String, val i: Int) : SimpleAbstract()
@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 00000000..e90ff2be
--- /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 88450aad..da7521ce 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 @@ class ProtobufNullAndDefaultTest {
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 @@ class ProtobufNullAndDefaultTest {
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 b7332312..03302502 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 @@ package kotlinx.serialization.protobuf.schema
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 @@ class SchemaValidationsTest {
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 c4a6b986..e7466790 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 abbac9d0..0cde6998 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 00000000..21528036
--- /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 41fdba71..68fda00a 100644
--- a/formats/protobuf/jvmTest/resources/OptionalClass.proto
+++ b/formats/protobuf/jvmTest/resources/OptionalClass.proto
@@ -5,9 +5,21 @@ package kotlinx.serialization.protobuf.schema.generator;
// 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 e8a0b4cd..44b5a181 100644
--- a/formats/protobuf/jvmTest/resources/common/schema.proto
+++ b/formats/protobuf/jvmTest/resources/common/schema.proto
@@ -63,11 +63,23 @@ message MapClass {
// 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 @@ message OptionalCollections {
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 df1a3fd1..ae2d5931 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 @@ class PolymorphicWithJvmClassTest {
@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 0c94c6e3..2a12424d 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 @@ class RandomTest : ShouldSpec() {
}
}
- 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 00000000..7b2dda22
--- /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 00000000..a9614249
--- /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 00000000..c369d6eb
--- /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 00000000..fda811ea
--- /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 00000000..e0da0bb8
--- /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 00000000..a7363f8f
--- /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 00000000..b3dab8c7
--- /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 00000000..dad773d9
--- /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 7b075a4e..f2a44234 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 @@ internal val commonClasses = listOf(
GenerationTest.LegacyMapHolder::class,
GenerationTest.NullableNestedCollections::class,
GenerationTest.OptionalCollections::class,
+ GenerationTest.EnumWithProtoNumber::class,
)
class GenerationTest {
@@ -60,7 +61,7 @@ class GenerationTest {
@ProtoNumber(5)
val b: Int,
@ProtoNumber(3)
- val c: Int
+ val c: UInt,
)
@Serializable
@@ -83,6 +84,10 @@ class GenerationTest {
@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 @@ class GenerationTest {
@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 @@ class GenerationTest {
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 36221960..badc7b03 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 f4b1f5f0..2b50c600 100644
--- a/formats/protobuf/testProto/test_data.proto
+++ b/formats/protobuf/testProto/test_data.proto
@@ -59,6 +59,7 @@ message TestEnum {
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 00000000..6b279957
--- /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 00000000..72fbfd01
--- /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 00000000..fd359b72
--- /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 0c58f546..099a38f6 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 e7888eea..f744b171 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 @@ kotlin {
}
}
+ 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 @@ kotlin {
}
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 @@ kotlin {
}
}
- 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 @@ kotlin {
}
}
}
+
+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 157ab82e..5a208f2b 100644
--- a/gradle/dokka.gradle
+++ b/gradle/dokka.gradle
@@ -7,10 +7,12 @@ apply plugin: 'org.jetbrains.dokka'
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 @@ subprojects {
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 @@ subprojects {
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 @@ subprojects {
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 214520d0..31b03eba 100644
--- a/gradle/kover.gradle
+++ b/gradle/kover.gradle
@@ -12,7 +12,7 @@ tasks.withType(Test) { task ->
}
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 909fcec1..8ef7f48d 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)
-
- def ideaPreset = presets.linuxX64
- if (macosEnabled) ideaPreset = presets.macosX64
- if (winEnabled) ideaPreset = presets.mingwX64
- project.ext.ideaPreset = ideaPreset
- }
-
- 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())
- }
-
- 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)
-
- // 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
- }
+ applyDefaultHierarchyTemplate {
+
+ // According to https://kotlinlang.org/docs/native-target-support.html
+ // Tier 1
+ macosX64()
+ macosArm64()
+ iosSimulatorArm64()
+ iosX64()
+
+ // Tier 2
+ linuxX64()
+ linuxArm64()
+ watchosSimulatorArm64()
+ watchosX64()
+ watchosArm32()
+ watchosArm64()
+ tvosSimulatorArm64()
+ tvosX64()
+ tvosArm64()
+ iosArm64()
+
+ // Tier 3
+ mingwX64()
+ // https://github.com/square/okio/issues/1242#issuecomment-1759357336
+ if (doesNotDependOnOkio(project)) {
+ androidNativeArm32()
+ androidNativeArm64()
+ androidNativeX86()
+ androidNativeX64()
+ watchosDeviceArm64()
+
+ // Deprecated, but not removed
+ linuxArm32Hfp()
}
}
}
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
index c16999ad..2c3518e2 100644
--- a/gradle/publishing.gradle
+++ b/gradle/publishing.gradle
@@ -9,8 +9,9 @@ apply plugin: 'signing'
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 @@ afterEvaluate {
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 bb4cb575..950494d9 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 669386b8..31cca491 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 e01f660e..b4261e81 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 00000000..4bd1da05
--- /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 00000000..d1a34186
--- /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 9f83804d..0fc318bd 100644
--- a/guide/example/example-formats-10.kt
+++ b/guide/example/example-formats-10.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.modules.*
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 44bceb05..942febb1 100644
--- a/guide/example/example-formats-11.kt
+++ b/guide/example/example-formats-11.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
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 ebedb302..1e83b9bf 100644
--- a/guide/example/example-formats-12.kt
+++ b/guide/example/example-formats-12.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
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 e63c9b96..62ecdc66 100644
--- a/guide/example/example-formats-13.kt
+++ b/guide/example/example-formats-13.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
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 0224916c..cd823e8d 100644
--- a/guide/example/example-formats-14.kt
+++ b/guide/example/example-formats-14.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeToList(value: T) = encodeToList(serializer(), value
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 207daadf..81928d49 100644
--- a/guide/example/example-formats-15.kt
+++ b/guide/example/example-formats-15.kt
@@ -9,7 +9,7 @@ import kotlinx.serialization.modules.*
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 @@ inline fun <reified T> encodeTo(output: DataOutput, value: T) = encodeTo(output,
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 25d8662e..24604902 100644
--- a/guide/example/example-formats-16.kt
+++ b/guide/example/example-formats-16.kt
@@ -10,7 +10,7 @@ import java.io.*
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 @@ inline fun <reified T> encodeTo(output: DataOutput, value: T) = encodeTo(output,
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 cc98bf5c..99a872b7 100644
--- a/guide/example/example-json-12.kt
+++ b/guide/example/example-json-12.kt
@@ -4,9 +4,17 @@ package example.exampleJson12
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 97188ff5..e20afe28 100644
--- a/guide/example/example-json-13.kt
+++ b/guide/example/example-json-13.kt
@@ -4,15 +4,13 @@ package example.exampleJson13
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 0e5ba362..50de55fd 100644
--- a/guide/example/example-json-14.kt
+++ b/guide/example/example-json-14.kt
@@ -4,20 +4,12 @@ package example.exampleJson14
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 0aa317f4..384ae416 100644
--- a/guide/example/example-json-15.kt
+++ b/guide/example/example-json-15.kt
@@ -4,14 +4,9 @@ package example.exampleJson15
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 b66d3ac2..fff287ae 100644
--- a/guide/example/example-json-16.kt
+++ b/guide/example/example-json-16.kt
@@ -4,29 +4,15 @@ package example.exampleJson16
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 7b1b88f3..72a696a2 100644
--- a/guide/example/example-json-17.kt
+++ b/guide/example/example-json-17.kt
@@ -4,27 +4,20 @@ package example.exampleJson17
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 d3da62d3..1b655bfe 100644
--- a/guide/example/example-json-18.kt
+++ b/guide/example/example-json-18.kt
@@ -5,18 +5,13 @@ 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"
- })
-}
+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 4455d637..b001c55a 100644
--- a/guide/example/example-json-19.kt
+++ b/guide/example/example-json-19.kt
@@ -4,33 +4,20 @@ package example.exampleJson19
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()
+val format = Json { prettyPrint = true }
-object ProjectSerializer : JsonContentPolymorphicSerializer<Project>(Project::class) {
- override fun selectDeserializer(element: JsonElement) = when {
- "owner" in element.jsonObject -> OwnedProject.serializer()
- else -> BasicProject.serializer()
+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)
}
-}
-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))
+ println(format.encodeToString(piObject))
}
diff --git a/guide/example/example-json-20.kt b/guide/example/example-json-20.kt
index e613a08f..f522b3fa 100644
--- a/guide/example/example-json-20.kt
+++ b/guide/example/example-json-20.kt
@@ -4,56 +4,24 @@ package example.exampleJson20
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))
- }
+val format = Json { prettyPrint = true }
- 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)
+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)
}
-}
-@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))
+ println(format.encodeToString(piObject))
}
diff --git a/guide/example/example-json-21.kt b/guide/example/example-json-21.kt
index 92de429b..efd60710 100644
--- a/guide/example/example-json-21.kt
+++ b/guide/example/example-json-21.kt
@@ -4,34 +4,20 @@ package example.exampleJson21
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 00000000..e64ab06f
--- /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 00000000..ffa9f7d7
--- /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 00000000..010bd27d
--- /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 00000000..a7d19a7f
--- /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 00000000..b1b92999
--- /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 00000000..5905733a
--- /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 00000000..a3fab617
--- /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 157208fd..3db0b7ff 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 @@ object DateAsLongSerializer : KSerializer<Date> {
}
@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 e6c488e1..c5624ed3 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.json.*
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 91626316..9987e822 100644
--- a/guide/example/example-serializer-18.kt
+++ b/guide/example/example-serializer-18.kt
@@ -8,15 +8,29 @@ import kotlinx.serialization.descriptors.*
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 da51db3e..4622665a 100644
--- a/guide/example/example-serializer-19.kt
+++ b/guide/example/example-serializer-19.kt
@@ -6,30 +6,21 @@ import kotlinx.serialization.json.*
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
-class ProgrammingLanguage(
- val name: String,
- @Contextual
- val stableReleaseDate: Date
-)
+@Serializable(with = BoxSerializer::class)
+data class Box<T>(val contents: T)
-private val module = SerializersModule {
- contextual(DateAsLongSerializer)
+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))
}
-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 7b4e71c9..38b72e79 100644
--- a/guide/example/example-serializer-20.kt
+++ b/guide/example/example-serializer-20.kt
@@ -6,13 +6,17 @@ 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
+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 95879074..9a24b0aa 100644
--- a/guide/example/example-serializer-21.kt
+++ b/guide/example/example-serializer-21.kt
@@ -6,23 +6,30 @@ 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"
+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
+class ProgrammingLanguage(
+ val name: String,
+ @Contextual
+ val stableReleaseDate: Date
+)
- private var locked: Boolean = false // private, not accessible -- not serialized
-}
+private val module = SerializersModule {
+ contextual(DateAsLongSerializer)
+}
-@Serializer(forClass = Project::class)
-object ProjectSerializer
+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 00000000..4eba74b0
--- /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 00000000..4b7de25a
--- /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 74c4433d..11f9e9f2 100644
--- a/guide/test/BasicSerializationTest.kt
+++ b/guide/test/BasicSerializationTest.kt
@@ -9,7 +9,7 @@ class BasicSerializationTest {
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 @@ class BasicSerializationTest {
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 28273328..3cc96295 100644
--- a/guide/test/BuiltinClassesTest.kt
+++ b/guide/test/BuiltinClassesTest.kt
@@ -82,4 +82,18 @@ class BuiltinClassesTest {
"{}"
)
}
+
+ @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 c92f57bf..0c5ed85e 100644
--- a/guide/test/JsonTest.kt
+++ b/guide/test/JsonTest.kt
@@ -90,73 +90,129 @@ class JsonTest {
@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 e82dd689..344ed24d 100644
--- a/guide/test/PolymorphismTest.kt
+++ b/guide/test/PolymorphismTest.kt
@@ -16,15 +16,16 @@ class PolymorphismTest {
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 @@ class PolymorphismTest {
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 @@ class PolymorphismTest {
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 @@ class PolymorphismTest {
@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 0871b829..bda3f7f4 100644
--- a/guide/test/SerializersTest.kt
+++ b/guide/test/SerializersTest.kt
@@ -113,43 +113,57 @@ class SerializersTest {
@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 911cee18..6c4e700f 100644
--- a/integration-test/build.gradle
+++ b/integration-test/build.gradle
@@ -5,23 +5,31 @@ buildscript {
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 @@ kotlin {
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 @@ kotlin {
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 @@ dependencies {
}
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 398917a4..d29c5df2 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_version = 4.1.0
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 669386b8..31cca491 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 00000000..08c49839
--- /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 2360ae47..f8cb2d87 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 fc780b4c..edd96549 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 206348bb..b9686acf 100644
--- a/integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/BasicTypesSerializationTest.kt
@@ -124,7 +124,7 @@ class BasicTypesSerializationTest {
@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 @@ class BasicTypesSerializationTest {
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 BasicTypesSerializationTest {
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 @@ class BasicTypesSerializationTest {
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 @@ class BasicTypesSerializationTest {
}
fun expect(c: Char) {
- check(cur == c.toInt()) { "Expected '$c'" }
+ check(cur == c.code) { "Expected '$c'" }
next()
}
@@ -256,7 +256,7 @@ class BasicTypesSerializationTest {
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 6b704354..88a7a0d7 100644
--- a/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/JsonTest.kt
@@ -12,7 +12,7 @@ import kotlinx.serialization.modules.*
import kotlin.reflect.*
import kotlin.test.*
-public val jsonWithDefaults = Json { encodeDefaults = true }
+val jsonWithDefaults = Json { encodeDefaults = true }
class JsonTest {
@@ -129,10 +129,9 @@ class JsonTest {
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 01d8ca68..0cf9efa4 100644
--- a/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt
+++ b/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyModuleB.kt
@@ -5,59 +5,58 @@ import kotlinx.serialization.Serializable
@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 74750ca0..d3bddfa6 100644
--- a/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt
+++ b/integration-test/src/commonTest/kotlin/sample/MultiFileHierarchyTest.kt
@@ -28,49 +28,49 @@ class AbstractBaseTest {
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 c16985c6..5f7dc918 100644
--- a/integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt
+++ b/integration-test/src/jsTest/kotlin/sample/SampleTestsJS.kt
@@ -8,4 +8,4 @@ class SampleTestsJS {
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
index af949d1e..af949d1e 100644
--- a/integration-test/src/macosMain/kotlin/sample/SampleMacos.kt
+++ b/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
index 5ea67274..f86bf96e 100644
--- a/integration-test/src/macosTest/kotlin/sample/SampleTestsNative.kt
+++ b/integration-test/src/nativeTest/kotlin/sample/SampleTestsNative.kt
@@ -8,4 +8,4 @@ class SampleTestsNative {
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 00000000..4d01cf47
--- /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/wasmJsTest/kotlin/sample/SampleTestsWasm.kt b/integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
new file mode 100644
index 00000000..c5fd1a2f
--- /dev/null
+++ b/integration-test/src/wasmJsTest/kotlin/sample/SampleTestsWasm.kt
@@ -0,0 +1,11 @@
+package sample
+
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class SampleTestsWasm {
+ @Test
+ fun testHello() {
+ assertTrue("WasmJs" in hello())
+ }
+}
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 00000000..e461406b
--- /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/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt b/integration-test/src/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt
new file mode 100644
index 00000000..0ba180b2
--- /dev/null
+++ b/integration-test/src/wasmWasiTest/kotlin/sample/SampleTestsWasm.kt
@@ -0,0 +1,11 @@
+package sample
+
+import kotlin.test.Test
+import kotlin.test.assertTrue
+
+class SampleTestsWasm {
+ @Test
+ fun testHello() {
+ assertTrue("WasmWasi" in hello())
+ }
+}
diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock
index 0ccaae25..2ecec42c 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 @@ brace-expansion@^1.1.7:
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 @@ chalk@^4.1.0:
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 @@ concat-map@0.0.1:
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 @@ flat@^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:
+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 @@ glob-parent@~5.1.2:
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 @@ glob@7.1.7:
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 @@ is-unicode-supported@^0.1.0:
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 @@ log-symbols@4.1.0:
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 @@ ms@2.1.3:
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 @@ serialize-javascript@6.0.0:
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 @@ to-regex-range@^5.0.1:
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 00000000..f84d7b97
--- /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 00000000..ad5dd305
--- /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 10251efc..ed5256ee 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 @@ project(':kotlinx-serialization-bom').projectDir = file('./bom')
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 86598287..00000000
--- 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