From 86f1edd32e2a5cfa4f78758ef122efd775c1e96d Mon Sep 17 00:00:00 2001 From: Whathecode Date: Sat, 13 Apr 2024 15:24:53 +0200 Subject: [PATCH] Fix: serializing unknown types using JS release Also had to update kotlinx serialization typescript wrapper due to new method call which was added in `UnknownPolymorphicSerializer` and caused the exported names to change. Closes #473 --- .../UnknownPolymorphicSerializer.kt | 30 +++++++++++++------ .../carp-kotlinx-serialization/index.ts | 2 +- .../tests/carp-protocols-test.ts | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/carp.common/src/commonMain/kotlin/dk/cachet/carp/common/infrastructure/serialization/UnknownPolymorphicSerializer.kt b/carp.common/src/commonMain/kotlin/dk/cachet/carp/common/infrastructure/serialization/UnknownPolymorphicSerializer.kt index 4bb19b955..8cba7858e 100644 --- a/carp.common/src/commonMain/kotlin/dk/cachet/carp/common/infrastructure/serialization/UnknownPolymorphicSerializer.kt +++ b/carp.common/src/commonMain/kotlin/dk/cachet/carp/common/infrastructure/serialization/UnknownPolymorphicSerializer.kt @@ -1,6 +1,5 @@ package dk.cachet.carp.common.infrastructure.serialization -import dk.cachet.carp.common.infrastructure.reflect.AccessInternals import dk.cachet.carp.common.infrastructure.reflect.reflectIfAvailable import kotlinx.serialization.* import kotlinx.serialization.descriptors.* @@ -58,18 +57,30 @@ abstract class UnknownPolymorphicSerializer

( { throw unsupportedException } - getClassDiscriminator( encoder.json ) // Throws error in case array polymorphism is used. + val classDiscriminator = getClassDiscriminator( encoder.json ) // Throws error in case of array polymorphism. // Get the unknown JSON object. check( value is UnknownPolymorphicWrapper ) val unknown = Json.parseToJsonElement( value.jsonSource ) as JsonObject + val unknownTypeFields = unknown.filter { it.key != classDiscriminator } + + // Create a serial descriptor which contains all elements of the unknown JSON object, except type discriminator. + // If the encoder is in polymorphic writing mode, a class discriminator field will automatically be added using + // the serial name of the descriptor. + val unknownType = checkNotNull( unknown[ classDiscriminator ]?.jsonPrimitive?.content ) + val jsonSerializer = JsonElement.serializer() + val overrideDescriptor = buildClassSerialDescriptor( unknownType ) + { + unknownTypeFields.keys.forEach { element( it, jsonSerializer.descriptor ) } + } - // HACK: Modify kotlinx.serialization internals to ensure the encoder is not in polymorphic mode. - // Otherwise, `encoder.encodeJsonElement` encodes type information, but this is already represented in the wrapped unknown object. - AccessInternals.setField( encoder, "polymorphicDiscriminator", null ) - - // Output the originally wrapped JSON. - encoder.encodeJsonElement( unknown ) + // Write the JSON object. + encoder.encodeStructure( overrideDescriptor ) + { + var id = 0 + for ( field in unknownTypeFields.values ) + encodeSerializableElement( overrideDescriptor, id++, JsonElement.serializer(), field ) + } } override fun deserialize( decoder: Decoder ): P @@ -85,7 +96,8 @@ abstract class UnknownPolymorphicSerializer

( // Get raw JSON for the unknown type. val jsonElement = decoder.decodeJsonElement() val jsonSource = jsonElement.toString() - val className = jsonElement.jsonObject[ classDiscriminator ]!!.jsonPrimitive.content + val className = requireNotNull( jsonElement.jsonObject[ classDiscriminator ]?.jsonPrimitive?.content ) + { "Can't deserialize type which was serialized non-polymorphically." } return createWrapper( className, jsonSource, decoder.json ) } diff --git a/typescript-declarations/carp-kotlinx-serialization/index.ts b/typescript-declarations/carp-kotlinx-serialization/index.ts index 8139722bf..71661a929 100644 --- a/typescript-declarations/carp-kotlinx-serialization/index.ts +++ b/typescript-declarations/carp-kotlinx-serialization/index.ts @@ -7,7 +7,7 @@ import extendJson from "@cachet/kotlinx-serialization-kotlinx-serialization-json // Facade with better method names and type conversions for internal types. export namespace kotlinx.serialization { - export function getSerializer( type: any ) { return type.Companion.t16() } + export function getSerializer( type: any ) { return type.Companion.m16() } } export namespace kotlinx.serialization.json { diff --git a/typescript-declarations/tests/carp-protocols-test.ts b/typescript-declarations/tests/carp-protocols-test.ts index 54b5ce387..fb3faf955 100644 --- a/typescript-declarations/tests/carp-protocols-test.ts +++ b/typescript-declarations/tests/carp-protocols-test.ts @@ -27,7 +27,7 @@ describe( "carp-protocols-core", () => { expect( parsed ).is.instanceOf( StudyProtocolSnapshot ) } ) - it.skip( "can deserialize and serialize unknown types", () => { + it( "can deserialize and serialize unknown types", () => { const snapshotWithUnknownTypes = serializedSnapshot.replace( "dk.cachet.carp.common.infrastructure.test.StubTaskConfiguration", "com.unknown.CustomTaskConfiguration"