From 5c54f58224497ecb6200f3796152b21419a766e0 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Wed, 11 Dec 2024 13:59:46 +0100 Subject: [PATCH] implicit conversion from Char to String in DataFrame.convertTo { parser { ... } } --- .../kotlinx/dataframe/api/convertTo.kt | 6 +++- .../kotlinx/dataframe/impl/api/convertTo.kt | 19 ++++++++++- .../kotlinx/dataframe/api/convertTo.kt | 32 +++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt index 70f2954940..4e7fd9aa08 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt @@ -52,7 +52,7 @@ public class ConverterScope(public val fromType: KType, public val toSchema: Col * df.convertTo { * // defines how to convert Int? -> String * convert().with { it?.toString() ?: "No input given" } - * // defines how to convert String -> SomeType + * // defines how to convert String -> SomeType (and Char.toString() -> SomeType) * parser { SomeType(it) } * // fill missing column `sum` with expression `a+b` * fill { sum }.with { a + b } @@ -102,6 +102,10 @@ public fun ConvertToFill.with(expr: RowExpression) { /** * Defines how to convert `String` values into given type [C]. + * + * This method is a shortcut for `convert().with { }`. + * + * If no converter is defined for `Char` values, this converter will be used for them as well. */ public inline fun ConvertSchemaDsl<*>.parser(noinline parser: (String) -> C): Unit = convert().with(parser) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convertTo.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convertTo.kt index 48c2864df8..452abe96e1 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convertTo.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convertTo.kt @@ -45,8 +45,10 @@ import org.jetbrains.kotlinx.dataframe.schema.ColumnSchema import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema import org.jetbrains.kotlinx.dataframe.size import kotlin.reflect.KType +import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.full.withNullability import kotlin.reflect.jvm.jvmErasure +import kotlin.reflect.typeOf private val logger = KotlinLogging.logger {} @@ -143,7 +145,22 @@ internal fun AnyFrame.convertToImpl( // try to perform any user-specified conversions first val from = originalColumn.type() val to = targetSchema.type - val converter = dsl.getConverter(from, targetSchema) + var converter = dsl.getConverter(from, targetSchema) + + // special case for Char columns; check if we have any converters for String -> target + // if so, we can convert Char -> String -> target + if (converter == null && from.isSubtypeOf(typeOf())) { + val stringConverter = dsl.getConverter( + fromType = typeOf().withNullability(from.isMarkedNullable), + toSchema = targetSchema, + ) + if (stringConverter != null) { + converter = Converter( + transform = { stringConverter.transform(this, (it as Char?)?.toString()) }, + skipNulls = stringConverter.skipNulls, + ) + } + } val convertedColumn = if (converter != null) { val nullsAllowed = to.isMarkedNullable diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt index 176ab06975..56e5ff36a2 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt @@ -51,6 +51,38 @@ class ConvertToTests { df.convertTo { parser { A(it.toInt()) } } .single() .a.value shouldBe 1 + + // shortcut for: + df.convertTo { convert().with { A(it.toInt()) } } + .single() + .a.value shouldBe 1 + } + + @Test + fun `convert from char with parser`() { + val df = dataFrameOf("a")('1') + + shouldThrow { + df.convertTo() + } + + // Char -> String -> Target + df.convertTo { parser { A(it.toInt()) } } + .single() + .a.value shouldBe 1 + + // shortcut for: + df.convertTo { convert().with { A(it.toInt()) } } + .single() + .a.value shouldBe 1 + + // Char -> Target + df.convertTo { + parser { error("should not be triggered if convert() is present") } + convert().with<_, A> { error("should not be triggered if convert() is present") } + + convert().with { A(it.digitToInt()) } + }.single().a.value shouldBe 1 } @Test