Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serializers for common java types #350

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
bebf781
Prototype for cross-platform polymorphic (w/o respect for collections)
sandwwraith Nov 2, 2018
1868bac
Add standard types to polymorphic resolving (not tested!), convenient…
sandwwraith Nov 2, 2018
6155cfe
Dependency on snapshot plugin version from TeamCity so project can be
sandwwraith Nov 20, 2018
18ccd3f
Drop obsolete and poorly designed global class cache
sandwwraith Nov 22, 2018
f46ed1e
Unignore tests since project is built with snapshot plugin already
sandwwraith Nov 22, 2018
925bb2c
Move most of the tests for polymorphism to common code
sandwwraith Nov 23, 2018
13499c7
Update compiler version
sandwwraith Nov 23, 2018
7c29c6d
Add .bind and .rebind to extend base type of polymorphic modules.
sandwwraith Nov 23, 2018
10dae25
Add polymorphic test for Native BUT disable its module completely
sandwwraith Nov 23, 2018
a3adaaa
Update polymorphic tests with @Polymorphic
sandwwraith Nov 23, 2018
2d54ee7
Remove redundant @ImplicitReflectionSerializer on new polymorphic
sandwwraith Nov 26, 2018
f8b753a
Workaround missing kotlin-reflect on JVM side.
sandwwraith Nov 27, 2018
5b66a0b
Documentation for context, modules and new polymorphic
sandwwraith Nov 27, 2018
405d048
[Review] Renamed a lot of things, split modules and context to separa…
sandwwraith Nov 28, 2018
ed43395
[Review] Rename SerialModule -> SingletonModule, add tests for module…
sandwwraith Nov 28, 2018
cfce668
Update dev compiler
sandwwraith Dec 11, 2018
fb732fb
Merge branch 'dev' into new_polymorphic
sandwwraith Jan 24, 2019
3762d84
Cosmetic fixes of indentation and docs
sandwwraith Jan 24, 2019
fc8b72b
Introducing module with default Java types serializers
qwwdfsad Jan 28, 2019
15c6b59
j.u.Date serializer
qwwdfsad Jan 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ buildscript {
ext.serializationPluginVersion = property('plugin.version')
ext.konanVersion = property('konan.version')
}
ext.eapChannel = "https://teamcity.jetbrains.com/guestAuth/app/rest/builds/id:1799882/artifacts/content/maven"
ext.serializationCoordinates = "org.jetbrains.kotlin:kotlin-serialization"
ext.eapChannel = 'https://teamcity.jetbrains.com/guestAuth/app/rest/builds/id:1907319/artifacts/content/maven'
ext.serializationRepo = 'https://kotlin.bintray.com/kotlinx'
ext.experimentalsEnabled = ["-progressive", "-Xuse-experimental=kotlin.Experimental",
"-Xuse-experimental=kotlin.ExperimentalMultiplatform",
Expand Down
8 changes: 4 additions & 4 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
# limitations under the License.
#

library.version=0.10.0
kotlin.version=1.3.20
library.version=0.11.0
kotlin.version=1.3.20-dev-2207
kotlin.version.snapshot=1.3-SNAPSHOT
plugin.version=1.3.20
plugin.version=1.3.20-dev-2207
plugin.version.snapshot=1.3-SNAPSHOT
konan.version=1.3.20-eap-116
konan.version=1.3.10
konan.version.snapshot=1.3-SNAPSHOT-native-1.1.0-dev
# Also set KONAN_LOCAL_DIST enviroment variable to auto-assign konan.home

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ annotation class Transient
annotation class SerialInfo

/**
* Instructs to use [ContextSerializer] on an annotated property or type.
* Instructs to use [ContextSerializer] on an annotated property or type usage.
* If used on a file, instructs to use [ContextSerializer] for all listed KClasses.
*
* @param [forClasses] Classes to use ContextSerializer for in current file.
Expand All @@ -89,7 +89,7 @@ annotation class ContextualSerialization(vararg val forClasses: KClass<*>)
annotation class UseSerializers(vararg val serializerClasses: KClass<*>)

/**
* Instructs to use [PolymorphicSerializer] on an annotated property or type.
* Instructs to use [PolymorphicSerializer] on an annotated property or type usage.
*/
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.TYPE)
annotation class Polymorphic
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package kotlinx.serialization

import kotlinx.serialization.internal.SerialClassDescImpl
import kotlinx.serialization.context.MutableSerialContext
import kotlin.reflect.KClass

/**
* A [SerialDescriptor] for polymorphic serialization with special kind.
*/
object PolymorphicClassDescriptor : SerialClassDescImpl("kotlin.Any") {
override val kind: SerialKind = UnionKind.POLYMORPHIC

init {
// todo: serial ids for this?
addElement("class")
addElement("value")
}
}

/**
* Thrown when a subclass was not registered for polymorphic serialization in a scope of given `basePolyType`.
*
* @see MutableSerialContext.registerPolymorphicSerializer
*/
class SubtypeNotRegisteredException(subClassName: String, basePolyType: KClass<*>):
SerializationException("$subClassName is not registered for polymorphic serialization in the scope of $basePolyType") {

constructor(subClass: KClass<*>, basePolyType: KClass<*>): this(subClass.toString(), basePolyType)
}


/**
* This class provides support for multiplatform polymorphic serialization.
* Due to security and reflection usage concerns, all serializable implementations of some abstract class must be registered in advance.
* However, it allows registering subclasses in runtime, not compile-time. For example, it allows adding additional subclasses to the registry
* that were defined in a separate module, dependent on the base module with the base class.
*
* Polymorphic serialization is never enabled automatically. To enable this feature, use @SerializableWith(PolymorphicSerializer::class) or just @Polymorphic on the property.
*
* Another security requirement is that we only allow registering subclasses in the scope of a base class called [basePolyType]
* The motivation for this is easily understandable from the example:

```
abstract class BaseRequest()
@Serializable data class RequestA(val id: Int): BaseRequest()
@Serializable data class RequestB(val s: String): BaseRequest()

abstract class BaseResponse()
@Serializable data class ResponseC(val payload: Long): BaseResponse()
@Serializable data class ResponseD(val payload: ByteArray): BaseResponse()

@Serializable data class Message(
@Polymorphic val request: BaseRequest,
@Polymorphic val response: BaseResponse
)
```
* In this example, both request and response in Message are serializable with [PolymorphicSerializer] because of the annotation on them;
* BaseRequest and BaseResponse became [basePolyType]s as they're captured during compile time by the plugin. They are not required to be serializable by themselves.
* Yet PolymorphicSerializer for request should only allow RequestA and RequestB serializers, and none of the response's serializers.
* This is achieved via [MutableSerialContext.registerPolymorphicSerializer] function, which accepts two KClass references.
*
* By default (without special support from [Encoder]), polymorphic values are serialized as list with
* two elements: classname (String) and the object itself.
*
* @see MutableSerialContext.registerPolymorphicSerializer
*/
@Suppress("UNCHECKED_CAST")
class PolymorphicSerializer<T : Any>(private val basePolyType: KClass<T>) : KSerializer<Any> {
override fun serialize(encoder: Encoder, obj: Any) {
val actualSerializer = encoder.context.resolveFromBase(basePolyType, obj as T)
?: throw SubtypeNotRegisteredException(obj::class, basePolyType)

val out = encoder.beginStructure(descriptor)
out.encodeStringElement(descriptor, 0, actualSerializer.descriptor.name)
out.encodeSerializableElement(descriptor, 1, actualSerializer as KSerializer<Any>, obj)
out.endStructure(descriptor)
}

override fun deserialize(decoder: Decoder): Any {
@Suppress("NAME_SHADOWING")
val input = decoder.beginStructure(descriptor)
var klassName: String? = null
var value: Any? = null
mainLoop@ while (true) {
when (val index = input.decodeElementIndex(descriptor)) {
CompositeDecoder.READ_ALL -> {
klassName = input.decodeStringElement(descriptor, 0)
val loader = input.context.resolveFromBase(basePolyType, klassName)
?: throw SubtypeNotRegisteredException(klassName, basePolyType)
value = input.decodeSerializableElement(descriptor, 1, loader)
break@mainLoop
}
CompositeDecoder.READ_DONE -> {
break@mainLoop
}
0 -> {
klassName = input.decodeStringElement(descriptor, index)
}
1 -> {
klassName = requireNotNull(klassName) { "Cannot read polymorphic value before its type token" }
val loader = input.context.resolveFromBase(basePolyType, klassName)
?: throw SubtypeNotRegisteredException(klassName, basePolyType)
value = input.decodeSerializableElement(descriptor, index, loader)
}
else -> throw SerializationException("Invalid index in polymorphic deserialization of " +
"${klassName ?: "unknown class"} with base $basePolyType" +
"\n Expected 0, 1, READ_ALL(-2) or READ_DONE(-1), but found $index")
}
}

input.endStructure(descriptor)
return requireNotNull(value) { "Polymorphic value have not been read" }
}

override val descriptor: SerialDescriptor = PolymorphicClassDescriptor
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ interface SerialDescriptor {
val isNullable: Boolean
get() = false

fun isElementOptional(index: Int): Boolean
fun isElementOptional(index: Int): Boolean = false
}

interface SerializationStrategy<in T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,17 @@ expect fun <E: Enum<E>> KClass<E>.enumClassName(): String
expect fun <E: Enum<E>> KClass<E>.enumMembers(): Array<E>

expect fun <T: Any, E: T?> ArrayList<E>.toNativeArray(eClass: KClass<T>): Array<E>

/**
* Checks if an [obj] 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 expect fun isInstance(kclass: KClass<*>, obj: Any): Boolean
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2018 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.context

/**
* A [SerialModule] for composing other modules
*
* Has convenient operator [plusAssign].
*
* @see SerialModule.plus
*/
class CompositeModule(modules: List<SerialModule> = listOf()): SerialModule {
constructor(vararg modules: SerialModule) : this(modules.toList())

private val modules: MutableList<SerialModule> = modules.toMutableList()

override fun registerIn(context: MutableSerialContext) {
modules.forEach { it.registerIn(context) }
}

public operator fun plusAssign(module: SerialModule): Unit { modules += module }
public fun addModule(module: SerialModule) = plusAssign(module)
}

/**
* Composes [this] module with [other].
*/
operator fun SerialModule.plus(other: SerialModule): CompositeModule {
if (this is CompositeModule) {
this.addModule(other)
return this
}
return CompositeModule(this, other)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,50 @@ package kotlinx.serialization.context
import kotlinx.serialization.*
import kotlin.reflect.KClass

/**
* Serial context is a runtime mechanism used by [ContextSerializer] and [PolymorphicSerializer]
* to obtain serializers which were not found at compile-time by the serialization plugin.
*
* It can be regarded as a map where serializers are found using statically known KClasses.
*/
interface SerialContext {

/**
* Returns a dependent serializer associated with given [kclass].
*
* This method is used in context-sensitive operations
* on a property marked with [ContextualSerialization], by a [ContextSerializer]
*/
operator fun <T: Any> get(kclass: KClass<T>): KSerializer<T>?

fun <T: Any> getByValue(value: T): KSerializer<T>?
}
/**
* Returns serializer registered for polymorphic serialization of an [obj]'s class in a scope of [basePolyType].
*
* This method is used inside a [PolymorphicSerializer] when statically known class of a property marked with [Polymorphic]
* is [basePolyType], and the actual object in this property is [obj].
*/
fun <T : Any> resolveFromBase(basePolyType: KClass<T>, obj: T): KSerializer<out T>?

inline fun <reified T: Any> SerialContext.get(): KSerializer<T>? = get(T::class)

interface MutableSerialContext: SerialContext {
fun <T: Any> registerSerializer(forClass: KClass<T>, serializer: KSerializer<T>)
/**
* Returns serializer registered for polymorphic serialization of a class with [serializedClassName] in a scope of [basePolyType].
*
* This method is used inside a [PolymorphicSerializer] when statically known class of a property marked with [Polymorphic]
* is [basePolyType], and the class name received from [Decoder] is a [serializedClassName].
*/
fun <T : Any> resolveFromBase(basePolyType: KClass<T>, serializedClassName: String): KSerializer<out T>?
}

class MutableSerialContextImpl(private val parentContext: SerialContext? = null): MutableSerialContext {

private val classMap: MutableMap<KClass<*>, KSerializer<*>> = hashMapOf()

override fun <T: Any> registerSerializer(forClass: KClass<T>, serializer: KSerializer<T>) {
classMap[forClass] = serializer
}

override fun <T : Any> getByValue(value: T): KSerializer<T>? {
// if (value == null) throw SerializationException("Cannot determine class for value $value")
val t: T = value
val klass = t::class
return get(klass) as? KSerializer<T>
}
inline fun <reified T: Any> SerialContext.get(): KSerializer<T>? = get(T::class)

override fun <T: Any> get(kclass: KClass<T>): KSerializer<T>? = classMap[kclass] as? KSerializer<T> ?: parentContext?.get(kclass)
/**
* Returns a serializer associated with KClass which given [value] has.
*
* This method is used in context-sensitive operations
* on a property marked with [ContextualSerialization], by a [ContextSerializer]
*/
fun <T: Any> SerialContext.getByValue(value: T): KSerializer<T>? {
val klass = value::class
return get(klass) as? KSerializer<T>
}

@ImplicitReflectionSerializer
Expand All @@ -41,7 +57,45 @@ fun <T: Any> SerialContext?.getOrDefault(klass: KClass<T>) = this?.let { get(kla
@ImplicitReflectionSerializer
fun <T: Any> SerialContext?.getByValueOrDefault(value: T): KSerializer<T> = this?.let { getByValue(value) } ?: value::class.serializer() as KSerializer<T>

/**
* A [SerialContext] which always returns `null`.
*/
object EmptyContext: SerialContext {
override fun <T : Any> get(kclass: KClass<T>): KSerializer<T>? = null
override fun <T : Any> getByValue(value: T): KSerializer<T>? = null
override fun <T : Any> resolveFromBase(basePolyType: KClass<T>, obj: T): KSerializer<out T>? =
null

override fun <T : Any> resolveFromBase(basePolyType: KClass<T>, serializedClassName: String): KSerializer<out T>? =
null
}

/**
* A [SerialContext] which has ability to register a [KSerializer] in it.
*
* @see SerialModule
*/
interface MutableSerialContext: SerialContext {
/**
* Registers a [serializer] for a [forClass].
*
* Given [serializer] will be used in [get] and [getByValue].
*/
fun <T: Any> registerSerializer(forClass: KClass<T>, serializer: KSerializer<T>)

/**
* Registers a [concreteSerializer] for a [concreteClass] in a scope of [basePolyType].
*
* Given [concreteSerializer] will be used in polymorphic serialization
* if statically known type of property is [basePolyType] and runtime type is a [concreteClass].
*
* For serialization process, classname is determined using name of a serializer descriptor.
*
* All standard types and collections are registered in advance as a subtypes in a scope of [kotlin.Any].
*/
fun <Base : Any, Sub : Base> registerPolymorphicSerializer(
basePolyType: KClass<Base>,
concreteClass: KClass<Sub>,
concreteSerializer: KSerializer<Sub>
)
}

Loading