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

Add support for encoding/deconding Properties as String #1158

Merged
merged 3 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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: 2 additions & 0 deletions formats/properties/api/kotlinx-serialization-properties.api
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ public abstract class kotlinx/serialization/properties/Properties : kotlinx/seri
public static final field Default Lkotlinx/serialization/properties/Properties$Default;
public synthetic fun <init> (Lkotlinx/serialization/modules/SerializersModule;Ljava/lang/Void;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun decodeFromMap (Lkotlinx/serialization/DeserializationStrategy;Ljava/util/Map;)Ljava/lang/Object;
public final fun decodeFromStringMap (Lkotlinx/serialization/DeserializationStrategy;Ljava/util/Map;)Ljava/lang/Object;
public final fun encodeToMap (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/util/Map;
public final fun encodeToStringMap (Lkotlinx/serialization/SerializationStrategy;Ljava/lang/Object;)Ljava/util/Map;
public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,50 +45,62 @@ public sealed class Properties(
ctorMarker: Nothing?
) : SerialFormat {

private inner class OutMapper : NamedValueEncoder() {
private abstract inner class OutMapper<Value : Any> : NamedValueEncoder() {
override val serializersModule: SerializersModule = this@Properties.serializersModule

internal val map: MutableMap<String, Any> = mutableMapOf()
val map: MutableMap<String, Value> = mutableMapOf()

protected abstract fun encode(value: Any): Value

override fun encodeTaggedValue(tag: String, value: Any) {
map[tag] = value
map[tag] = encode(value)
}

override fun encodeTaggedNull(tag: String) {
// ignore nulls in output
}

override fun encodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor, ordinal: Int) {
map[tag] = enumDescriptor.getElementName(ordinal)
map[tag] = encode(enumDescriptor.getElementName(ordinal))
}
}

private inner class InMapper(
private val map: Map<String, Any>, descriptor: SerialDescriptor
private inner class OutAnyMapper : OutMapper<Any>() {
override fun encode(value: Any): Any = value
}

private inner class OutStringMapper : OutMapper<String>() {
override fun encode(value: Any): String = value.toString()
}

private abstract inner class InMapper<Value : Any>(
protected val map: Map<String, Value>, descriptor: SerialDescriptor
) : NamedValueDecoder() {
override val serializersModule: SerializersModule = this@Properties.serializersModule

private var currentIndex = 0
private val isCollection = descriptor.kind == StructureKind.LIST || descriptor.kind == StructureKind.MAP
private val size = if (isCollection) Int.MAX_VALUE else descriptor.elementsCount

override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
return InMapper(map, descriptor).also { copyTagsTo(it) }
protected abstract fun structure(descriptor: SerialDescriptor): InMapper<Value>

final override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder {
return structure(descriptor).also { copyTagsTo(it) }
}

override fun decodeTaggedValue(tag: String): Any {
final override fun decodeTaggedValue(tag: String): Value {
return map.getValue(tag)
}

override fun decodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor): Int {
final override fun decodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor): Int {
return when (val taggedValue = map.getValue(tag)) {
is Int -> taggedValue
is String -> enumDescriptor.getElementIndex(taggedValue)
else -> throw SerializationException("Value of enum entry '$tag' is neither an Int nor a String")
}
}

override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
final override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
while (currentIndex < size) {
val name = descriptor.getTag(currentIndex++)
if (map.keys.any { it.startsWith(name) }) return currentIndex - 1
Expand All @@ -101,13 +113,48 @@ public sealed class Properties(
}
}

private inner class InAnyMapper(
map: Map<String, Any>, descriptor: SerialDescriptor
) : InMapper<Any>(map, descriptor) {
override fun structure(descriptor: SerialDescriptor): InAnyMapper =
InAnyMapper(map, descriptor)
}

private inner class InStringMapper(
map: Map<String, String>, descriptor: SerialDescriptor
) : InMapper<String>(map, descriptor) {
override fun structure(descriptor: SerialDescriptor): InStringMapper =
InStringMapper(map, descriptor)

override fun decodeTaggedBoolean(tag: String): Boolean = decodeTaggedValue(tag).toBoolean()
override fun decodeTaggedByte(tag: String): Byte = decodeTaggedValue(tag).toByte()
override fun decodeTaggedShort(tag: String): Short = decodeTaggedValue(tag).toShort()
override fun decodeTaggedInt(tag: String): Int = decodeTaggedValue(tag).toInt()
override fun decodeTaggedLong(tag: String): Long = decodeTaggedValue(tag).toLong()
override fun decodeTaggedFloat(tag: String): Float = decodeTaggedValue(tag).toFloat()
override fun decodeTaggedDouble(tag: String): Double = decodeTaggedValue(tag).toDouble()
override fun decodeTaggedChar(tag: String): Char = decodeTaggedValue(tag).single()
}

/**
* Encodes properties from the given [value] to a map using the given [serializer].
* `null` values are omitted from the output.
*/
@ExperimentalSerializationApi
public fun <T> encodeToMap(serializer: SerializationStrategy<T>, value: T): Map<String, Any> {
val m = OutMapper()
val m = OutAnyMapper()
m.encodeSerializableValue(serializer, value)
return m.map
}

/**
* Encodes properties from the given [value] to a map using the given [serializer].
* Converts all values to [String].
sandwwraith marked this conversation as resolved.
Show resolved Hide resolved
* `null` values are omitted from the output.
*/
@ExperimentalSerializationApi
public fun <T> encodeToStringMap(serializer: SerializationStrategy<T>, value: T): Map<String, String> {
val m = OutStringMapper()
m.encodeSerializableValue(serializer, value)
return m.map
}
Expand All @@ -118,7 +165,18 @@ public sealed class Properties(
*/
@ExperimentalSerializationApi
public fun <T> decodeFromMap(deserializer: DeserializationStrategy<T>, map: Map<String, Any>): T {
val m = InMapper(map, deserializer.descriptor)
val m = InAnyMapper(map, deserializer.descriptor)
return m.decodeSerializableValue(deserializer)
}

/**
* Decodes properties from the given [map] to a value of type [T] using the given [deserializer].
* All values are expected to be of type [String].
sandwwraith marked this conversation as resolved.
Show resolved Hide resolved
* [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
*/
@ExperimentalSerializationApi
public fun <T> decodeFromStringMap(deserializer: DeserializationStrategy<T>, map: Map<String, String>): T {
val m = InStringMapper(map, deserializer.descriptor)
return m.decodeSerializableValue(deserializer)
}

Expand Down Expand Up @@ -146,6 +204,14 @@ public fun Properties(module: SerializersModule): Properties = PropertiesImpl(mo
public inline fun <reified T> Properties.encodeToMap(value: T): Map<String, Any> =
encodeToMap(serializersModule.serializer(), value)

/**
* Encodes properties from given [value] to a map using serializer for reified type [T] and returns this map.
sandwwraith marked this conversation as resolved.
Show resolved Hide resolved
* `null` values are omitted from the output.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Properties.encodeToStringMap(value: T): Map<String, String> =
encodeToStringMap(serializersModule.serializer(), value)

/**
* Decodes properties from given [map], assigns them to an object using serializer for reified type [T] and returns this object.
* [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
Expand All @@ -154,6 +220,14 @@ public inline fun <reified T> Properties.encodeToMap(value: T): Map<String, Any>
public inline fun <reified T> Properties.decodeFromMap(map: Map<String, Any>): T =
decodeFromMap(serializersModule.serializer(), map)

/**
* Decodes properties from given [map], assigns them to an object using serializer for reified type [T] and returns this object.
sandwwraith marked this conversation as resolved.
Show resolved Hide resolved
* [T] may contain properties of nullable types; they will be filled by non-null values from the [map], if present.
*/
@ExperimentalSerializationApi
public inline fun <reified T> Properties.decodeFromStringMap(map: Map<String, String>): T =
decodeFromStringMap(serializersModule.serializer(), map)

// Migrations below

@PublishedApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,14 @@ class PropertiesTest {
) {
val map = Properties.encodeToMap(serializer, obj)
assertEquals(expectedMap, map)
val unmap = Properties.decodeFromMap<T>(serializer, map)
val unmap = Properties.decodeFromMap(serializer, map)
assertEquals(obj, unmap)

val stringMap = Properties.encodeToStringMap(serializer, obj)
val expectedStringMap = expectedMap.mapValues { it.value.toString() }
assertEquals(expectedStringMap, stringMap)
val stringUnmap = Properties.decodeFromStringMap(serializer, stringMap)
assertEquals(obj, stringUnmap)
}

private inline fun <reified T : Any> assertMappedNullableAndRestored(
Expand All @@ -69,8 +75,14 @@ class PropertiesTest {
) {
val map = Properties.encodeToMap(serializer, obj)
assertEquals(expectedMap, map)
val unmap = Properties.decodeFromMap<T>(serializer, map)
val unmap = Properties.decodeFromMap(serializer, map)
assertEquals(obj, unmap)

val stringMap = Properties.encodeToStringMap(serializer, obj)
val expectedStringMap = expectedMap.mapValues { it.value.toString() }
assertEquals(expectedStringMap, stringMap)
val stringUnmap = Properties.decodeFromStringMap(serializer, stringMap)
assertEquals(obj, stringUnmap)
}

@Test
Expand Down