Skip to content

Commit 5091e23

Browse files
Merge pull request #1097 from Kotlin/map_unfold
Map unfold
2 parents 23f4fb6 + 901a806 commit 5091e23

File tree

8 files changed

+40
-17
lines changed

8 files changed

+40
-17
lines changed

core/api/core.api

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1783,7 +1783,6 @@ public final class org/jetbrains/kotlinx/dataframe/api/DataColumnTypeKt {
17831783
public static final fun isFrameColumn (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17841784
public static final fun isList (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17851785
public static final fun isNumber (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
1786-
public static final fun isPrimitive (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17871786
public static final fun isSubtypeOf (Lorg/jetbrains/kotlinx/dataframe/DataColumn;Lkotlin/reflect/KType;)Z
17881787
public static final fun isValueColumn (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
17891788
public static final fun valuesAreComparable (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
@@ -5254,6 +5253,7 @@ public final class org/jetbrains/kotlinx/dataframe/impl/api/SchemaKt {
52545253
public final class org/jetbrains/kotlinx/dataframe/impl/api/ToDataFrameKt {
52555254
public static final fun convertToDataFrame (Ljava/lang/Iterable;Lkotlin/reflect/KClass;Ljava/util/List;Ljava/util/Set;Ljava/util/Set;Ljava/util/Set;I)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
52565255
public static final fun createDataFrameImpl (Ljava/lang/Iterable;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
5256+
public static final fun getCanBeUnfolded (Lkotlin/reflect/KClass;)Z
52575257
public static final fun getHasProperties (Lkotlin/reflect/KClass;)Z
52585258
public static final fun isValueType (Lkotlin/reflect/KClass;)Z
52595259
}

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ import java.math.BigDecimal
1616
import java.math.BigInteger
1717
import kotlin.contracts.ExperimentalContracts
1818
import kotlin.contracts.contract
19-
import kotlin.reflect.KClass
2019
import kotlin.reflect.KType
2120
import kotlin.reflect.KTypeProjection
2221
import kotlin.reflect.KVariance
2322
import kotlin.reflect.full.createType
24-
import kotlin.reflect.full.isSubclassOf
2523
import kotlin.reflect.full.isSubtypeOf
2624
import kotlin.reflect.full.withNullability
2725
import kotlin.reflect.typeOf
@@ -81,13 +79,3 @@ public fun AnyCol.valuesAreComparable(): Boolean =
8179
nullable = hasNulls(),
8280
),
8381
)
84-
85-
@PublishedApi
86-
internal fun AnyCol.isPrimitive(): Boolean = typeClass.isPrimitive()
87-
88-
internal fun KClass<*>.isPrimitive(): Boolean =
89-
isSubclassOf(Number::class) ||
90-
this == String::class ||
91-
this == Char::class ||
92-
this == Array::class ||
93-
isSubclassOf(Collection::class)

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import org.jetbrains.kotlinx.dataframe.annotations.Interpretable
1111
import org.jetbrains.kotlinx.dataframe.annotations.Refine
1212
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
1313
import org.jetbrains.kotlinx.dataframe.impl.ColumnNameGenerator
14+
import org.jetbrains.kotlinx.dataframe.impl.api.canBeUnfolded
1415
import org.jetbrains.kotlinx.dataframe.impl.api.createDataFrameImpl
15-
import org.jetbrains.kotlinx.dataframe.impl.api.hasProperties
16-
import org.jetbrains.kotlinx.dataframe.impl.api.isValueType
1716
import org.jetbrains.kotlinx.dataframe.impl.asList
1817
import org.jetbrains.kotlinx.dataframe.impl.columnName
1918
import org.jetbrains.kotlinx.dataframe.impl.columns.createColumnGuessingType
@@ -30,7 +29,7 @@ public inline fun <reified T> Iterable<T>.toDataFrame(): DataFrame<T> =
3029
toDataFrame {
3130
// check if type is value: primitives, primitive arrays, datetime types etc.,
3231
// or has no properties
33-
if (T::class.isValueType || !T::class.hasProperties) {
32+
if (!T::class.canBeUnfolded) {
3433
// create a single `value` column
3534
ValueProperty<T>::value from { it }
3635
} else {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame
88
import org.jetbrains.kotlinx.dataframe.annotations.AccessApiOverload
99
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
1010
import org.jetbrains.kotlinx.dataframe.columns.toColumnSet
11+
import org.jetbrains.kotlinx.dataframe.impl.api.canBeUnfolded
1112
import org.jetbrains.kotlinx.dataframe.impl.api.createDataFrameImpl
1213
import org.jetbrains.kotlinx.dataframe.typeClass
1314
import kotlin.reflect.KProperty
@@ -17,7 +18,7 @@ public inline fun <reified T> DataColumn<T>.unfold(): AnyCol =
1718
ColumnKind.Group, ColumnKind.Frame -> this
1819

1920
else -> when {
20-
isPrimitive() -> this
21+
!typeClass.canBeUnfolded -> this
2122

2223
else -> values()
2324
.createDataFrameImpl(typeClass) { (this as CreateDataFrameDsl<T>).properties() }

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,24 @@ private val valueTypes = setOf(
5656
kotlinx.datetime.TimeZone::class,
5757
kotlinx.datetime.DateTimePeriod::class,
5858
kotlinx.datetime.DateTimeUnit::class,
59+
Map::class,
60+
MutableMap::class,
5961
)
6062

63+
/**
64+
* Determines whether a class can be unfolded into its properties.
65+
*
66+
* A class is considered **unfoldable** if it has at least one public property
67+
* or getter-like function. This excludes:
68+
* - **Value types** such as primitives, enums, and date-time types.
69+
* - **Classes without properties**, including empty or marker classes.
70+
*
71+
* @return `true` if the class has unfoldable properties, `false` otherwise.
72+
*/
73+
@PublishedApi
74+
internal val KClass<*>.canBeUnfolded: Boolean
75+
get() = (!this.isValueType) && this.hasProperties
76+
6177
/**
6278
* Checks if `KClass` is a value type (number, datetime, string, etc.)
6379
* Should be aligned with `ConeKotlinType.isValueType()` in

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,14 @@ class CreateDataFrameTests {
398398
enums.toDataFrame() shouldBe dataFrameOf("value")(*enums.toTypedArray())
399399
}
400400

401+
@Test
402+
fun `should convert iterables of non-JSON Map to DataFrame with value column`() {
403+
val maps: List<Map<*, *>?> = listOf(mapOf(1 to null, 2 to "val"), mapOf(3 to 1, 4 to true), null)
404+
val df = maps.toDataFrame()
405+
df.columnNames() shouldBe listOf("value")
406+
df["value"].toList() shouldBe maps
407+
}
408+
401409
class NoPublicPropsClass(private val a: Int, private val b: String)
402410

403411
@Test

plugins/kotlin-dataframe/src/org/jetbrains/kotlinx/dataframe/plugin/impl/api/toDataFrame.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,14 @@ internal fun KotlinTypeFacade.toDataFrame(
198198
traverseConfiguration: TraverseConfiguration,
199199
): PluginDataFrameSchema {
200200

201+
val anyType = session.builtinTypes.nullableAnyType.type
202+
201203
fun ConeKotlinType.isValueType() =
202204
this.isArrayTypeOrNullableArrayType ||
203205
this.classId == StandardClassIds.Unit ||
204206
this.classId == StandardClassIds.Any ||
207+
this.classId == StandardClassIds.Map ||
208+
this.classId == StandardClassIds.MutableMap ||
205209
this.classId == StandardClassIds.String ||
206210
this.classId in StandardClassIds.primitiveTypes ||
207211
this.classId in StandardClassIds.unsignedTypes ||
@@ -226,6 +230,7 @@ internal fun KotlinTypeFacade.toDataFrame(
226230
Names.TEMPORAL_AMOUNT_CLASS_ID.constructClassLikeType(emptyArray(), isNullable = true), session
227231
)
228232

233+
229234
fun FirNamedFunctionSymbol.isGetterLike(): Boolean {
230235
val functionName = this.name.asString()
231236
return (functionName.startsWith("get") || functionName.startsWith("is")) &&

plugins/kotlin-dataframe/testData/box/toDataFrameValueTypes.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,12 @@ fun box(): String {
197197
val enumColNullable: DataColumn<EnumExample?> = enumDfNullable.value
198198
enumColNullable.print()
199199

200+
// Non-JSON Map
201+
202+
val mapsDfNullable = listOf(mapOf(1 to null, 2 to "val"), mapOf(3 to 1, 4 to true), null).toDataFrame()
203+
val mapsColNullable: DataColumn<Map<*, *>?> = mapsDfNullable.value
204+
mapsColNullable.print()
205+
200206
return "OK"
201207
}
202208

0 commit comments

Comments
 (0)