Skip to content

Commit ccdd066

Browse files
committed
convertTo<> { parser {} } can now also defines Char parsing unless a char converter is passed explicitly
1 parent 3211749 commit ccdd066

File tree

3 files changed

+85
-1
lines changed

3 files changed

+85
-1
lines changed

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class ConverterScope(public val fromType: KType, public val toSchema: Col
5252
* df.convertTo<SomeSchema> {
5353
* // defines how to convert Int? -> String
5454
* convert<Int?>().with { it?.toString() ?: "No input given" }
55-
* // defines how to convert String -> SomeType
55+
* // defines how to convert String/Char -> SomeType
5656
* parser { SomeType(it) }
5757
* // fill missing column `sum` with expression `a+b`
5858
* fill { sum }.with { a + b }
@@ -102,6 +102,10 @@ public fun <T, C> ConvertToFill<T, C>.with(expr: RowExpression<T, C>) {
102102

103103
/**
104104
* Defines how to convert `String` values into given type [C].
105+
*
106+
* This method is a shortcut for `convert<String>().with { }`.
107+
*
108+
* If no converter is defined for `Char` values, this converter will be used for them as well.
105109
*/
106110
public inline fun <reified C> ConvertSchemaDsl<*>.parser(noinline parser: (String) -> C): Unit =
107111
convert<String>().with(parser)

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/convertTo.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ import org.jetbrains.kotlinx.dataframe.schema.ColumnSchema
4545
import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema
4646
import org.jetbrains.kotlinx.dataframe.size
4747
import kotlin.reflect.KType
48+
import kotlin.reflect.full.isSubtypeOf
4849
import kotlin.reflect.full.withNullability
4950
import kotlin.reflect.jvm.jvmErasure
51+
import kotlin.reflect.typeOf
5052

5153
private val logger = KotlinLogging.logger {}
5254

@@ -144,6 +146,25 @@ internal fun AnyFrame.convertToImpl(
144146
val from = originalColumn.type()
145147
val to = targetSchema.type
146148
val converter = dsl.getConverter(from, targetSchema)
149+
?: run {
150+
// Special case for Char columns:
151+
// If there is no explicit Char converter,
152+
// check if we have any converters for String -> target
153+
// if so, we can convert Char -> String -> target
154+
// this allows `parser {}` to work both for Strings and Chars :)
155+
156+
if (!from.isSubtypeOf(typeOf<Char?>())) return@run null
157+
158+
val stringConverter = dsl.getConverter(
159+
fromType = typeOf<String>().withNullability(from.isMarkedNullable),
160+
toSchema = targetSchema,
161+
) ?: return@run null
162+
163+
Converter(
164+
transform = { stringConverter.transform(this, (it as Char?)?.toString()) },
165+
skipNulls = stringConverter.skipNulls,
166+
)
167+
}
147168

148169
val convertedColumn = if (converter != null) {
149170
val nullsAllowed = to.isMarkedNullable

core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/convertTo.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,38 @@ class ConvertToTests {
5151
df.convertTo<Schema> { parser { A(it.toInt()) } }
5252
.single()
5353
.a.value shouldBe 1
54+
55+
// shortcut for:
56+
df.convertTo<Schema> { convert<String>().with { A(it.toInt()) } }
57+
.single()
58+
.a.value shouldBe 1
59+
}
60+
61+
@Test
62+
fun `convert from char with parser`() {
63+
val df = dataFrameOf("a")('1')
64+
65+
shouldThrow<TypeConverterNotFoundException> {
66+
df.convertTo<Schema>()
67+
}
68+
69+
// Char -> String -> Target
70+
df.convertTo<Schema> { parser { A(it.toInt()) } }
71+
.single()
72+
.a.value shouldBe 1
73+
74+
// shortcut for:
75+
df.convertTo<Schema> { convert<String>().with { A(it.toInt()) } }
76+
.single()
77+
.a.value shouldBe 1
78+
79+
// Char -> Target
80+
df.convertTo<Schema> {
81+
parser<A> { error("should not be triggered if convert<Char>() is present") }
82+
convert<String>().with<_, A> { error("should not be triggered if convert<Char>() is present") }
83+
84+
convert<Char>().with { A(it.digitToInt()) }
85+
}.single().a.value shouldBe 1
5486
}
5587

5688
@Test
@@ -335,4 +367,31 @@ class ConvertToTests {
335367
DataFrame.emptyOf<Entry>(),
336368
)
337369
}
370+
371+
enum class SimpleEnum { A, B }
372+
373+
@DataSchema
374+
interface SchemaWithNullableEnum {
375+
val a: SimpleEnum?
376+
}
377+
378+
@Test
379+
fun `convert Char to Enum`() {
380+
val df = dataFrameOf("a")('A', 'B', null)
381+
382+
val converted = df.convertTo<SchemaWithNullableEnum>()
383+
converted["a"].type() shouldBe typeOf<SimpleEnum?>()
384+
converted shouldBe dataFrameOf("a")(SimpleEnum.A, SimpleEnum.B, null)
385+
}
386+
387+
@Test
388+
fun `convert Char to Enum custom charParser`() {
389+
val df = dataFrameOf("a")('a', 'b', null)
390+
391+
val converted = df.convertTo<SchemaWithNullableEnum> {
392+
parser { SimpleEnum.valueOf(it.uppercase()) }
393+
}
394+
converted["a"].type() shouldBe typeOf<SimpleEnum?>()
395+
converted shouldBe dataFrameOf("a")(SimpleEnum.A, SimpleEnum.B, null)
396+
}
338397
}

0 commit comments

Comments
 (0)