Skip to content

Commit

Permalink
implicit conversion from Char to String in DataFrame.convertTo<X> { p…
Browse files Browse the repository at this point in the history
…arser { ... } }
  • Loading branch information
Jolanrensen committed Dec 11, 2024
1 parent f86a6cc commit 5c54f58
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class ConverterScope(public val fromType: KType, public val toSchema: Col
* df.convertTo<SomeSchema> {
* // defines how to convert Int? -> String
* convert<Int?>().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 }
Expand Down Expand Up @@ -102,6 +102,10 @@ public fun <T, C> ConvertToFill<T, C>.with(expr: RowExpression<T, C>) {

/**
* Defines how to convert `String` values into given type [C].
*
* This method is a shortcut for `convert<String>().with { }`.
*
* If no converter is defined for `Char` values, this converter will be used for them as well.
*/
public inline fun <reified C> ConvertSchemaDsl<*>.parser(noinline parser: (String) -> C): Unit =
convert<String>().with(parser)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down Expand Up @@ -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<Char?>())) {
val stringConverter = dsl.getConverter(
fromType = typeOf<String>().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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,38 @@ class ConvertToTests {
df.convertTo<Schema> { parser { A(it.toInt()) } }
.single()
.a.value shouldBe 1

// shortcut for:
df.convertTo<Schema> { convert<String>().with { A(it.toInt()) } }
.single()
.a.value shouldBe 1
}

@Test
fun `convert from char with parser`() {
val df = dataFrameOf("a")('1')

shouldThrow<TypeConverterNotFoundException> {
df.convertTo<Schema>()
}

// Char -> String -> Target
df.convertTo<Schema> { parser { A(it.toInt()) } }
.single()
.a.value shouldBe 1

// shortcut for:
df.convertTo<Schema> { convert<String>().with { A(it.toInt()) } }
.single()
.a.value shouldBe 1

// Char -> Target
df.convertTo<Schema> {
parser<A> { error("should not be triggered if convert<Char>() is present") }
convert<String>().with<_, A> { error("should not be triggered if convert<Char>() is present") }

convert<Char>().with { A(it.digitToInt()) }
}.single().a.value shouldBe 1
}

@Test
Expand Down

0 comments on commit 5c54f58

Please sign in to comment.