Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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: 1 addition & 1 deletion core/api/core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1974,7 +1974,6 @@ public final class org/jetbrains/kotlinx/dataframe/api/DataColumnTypeKt {
public static final fun isFrameColumn (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
public static final fun isList (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
public static final fun isNumber (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
public static final fun isPrimitive (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
public static final fun isSubtypeOf (Lorg/jetbrains/kotlinx/dataframe/DataColumn;Lkotlin/reflect/KType;)Z
public static final fun isValueColumn (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
public static final fun valuesAreComparable (Lorg/jetbrains/kotlinx/dataframe/DataColumn;)Z
Expand Down Expand Up @@ -5404,6 +5403,7 @@ public final class org/jetbrains/kotlinx/dataframe/impl/api/SchemaKt {
public final class org/jetbrains/kotlinx/dataframe/impl/api/ToDataFrameKt {
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;
public static final fun createDataFrameImpl (Ljava/lang/Iterable;Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlinx/dataframe/DataFrame;
public static final fun getCanBeUnfolded (Lkotlin/reflect/KClass;)Z
public static final fun getHasProperties (Lkotlin/reflect/KClass;)Z
public static final fun isValueType (Lkotlin/reflect/KClass;)Z
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ import java.math.BigDecimal
import java.math.BigInteger
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.createType
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.withNullability
import kotlin.reflect.typeOf
Expand Down Expand Up @@ -81,13 +79,3 @@ public fun AnyCol.valuesAreComparable(): Boolean =
nullable = hasNulls(),
),
)

@PublishedApi
internal fun AnyCol.isPrimitive(): Boolean = typeClass.isPrimitive()

internal fun KClass<*>.isPrimitive(): Boolean =
isSubclassOf(Number::class) ||
this == String::class ||
this == Char::class ||
this == Array::class ||
isSubclassOf(Collection::class)
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import org.jetbrains.kotlinx.dataframe.annotations.Interpretable
import org.jetbrains.kotlinx.dataframe.annotations.Refine
import org.jetbrains.kotlinx.dataframe.columns.ColumnPath
import org.jetbrains.kotlinx.dataframe.impl.ColumnNameGenerator
import org.jetbrains.kotlinx.dataframe.impl.api.canBeUnfolded
import org.jetbrains.kotlinx.dataframe.impl.api.createDataFrameImpl
import org.jetbrains.kotlinx.dataframe.impl.api.hasProperties
import org.jetbrains.kotlinx.dataframe.impl.api.isValueType
import org.jetbrains.kotlinx.dataframe.impl.asList
import org.jetbrains.kotlinx.dataframe.impl.columnName
import org.jetbrains.kotlinx.dataframe.impl.columns.createColumnGuessingType
Expand All @@ -30,7 +29,7 @@ public inline fun <reified T> Iterable<T>.toDataFrame(): DataFrame<T> =
toDataFrame {
// check if type is value: primitives, primitive arrays, datetime types etc.,
// or has no properties
if (T::class.isValueType || !T::class.hasProperties) {
if (!T::class.canBeUnfolded) {
// create a single `value` column
ValueProperty<T>::value from { it }
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.jetbrains.kotlinx.dataframe.DataFrame
import org.jetbrains.kotlinx.dataframe.annotations.AccessApiOverload
import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
import org.jetbrains.kotlinx.dataframe.columns.toColumnSet
import org.jetbrains.kotlinx.dataframe.impl.api.canBeUnfolded
import org.jetbrains.kotlinx.dataframe.impl.api.createDataFrameImpl
import org.jetbrains.kotlinx.dataframe.typeClass
import kotlin.reflect.KProperty
Expand All @@ -17,7 +18,7 @@ public inline fun <reified T> DataColumn<T>.unfold(): AnyCol =
ColumnKind.Group, ColumnKind.Frame -> this

else -> when {
isPrimitive() -> this
!typeClass.canBeUnfolded -> this

else -> values()
.createDataFrameImpl(typeClass) { (this as CreateDataFrameDsl<T>).properties() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,24 @@ private val valueTypes = setOf(
kotlinx.datetime.TimeZone::class,
kotlinx.datetime.DateTimePeriod::class,
kotlinx.datetime.DateTimeUnit::class,
Map::class,
MutableMap::class,
)

/**
* Determines whether a class can be unfolded into its properties.
*
* A class is considered **unfoldable** if it has at least one public property
* or getter-like function. This excludes:
* - **Value types** such as primitives, enums, and date-time types.
* - **Classes without properties**, including empty or marker classes.
*
* @return `true` if the class has unfoldable properties, `false` otherwise.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very clean solution :)

@PublishedApi
internal val KClass<*>.canBeUnfolded: Boolean
get() = (!this.isValueType) && this.hasProperties

/**
* Checks if `KClass` is a value type (number, datetime, string, etc.)
* Should be aligned with `ConeKotlinType.isValueType()` in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,14 @@ class CreateDataFrameTests {
enums.toDataFrame() shouldBe dataFrameOf("value")(*enums.toTypedArray())
}

@Test
fun `should convert iterables of non-JSON Map to DataFrame with value column`() {
val maps: List<Map<*, *>?> = listOf(mapOf(1 to null, 2 to "val"), mapOf(3 to 1, 4 to true), null)
val df = maps.toDataFrame()
df.columnNames() shouldBe listOf("value")
df["value"].toList() shouldBe maps
}

class NoPublicPropsClass(private val a: Int, private val b: String)

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,14 @@ internal fun KotlinTypeFacade.toDataFrame(
traverseConfiguration: TraverseConfiguration,
): PluginDataFrameSchema {

val anyType = session.builtinTypes.nullableAnyType.type

fun ConeKotlinType.isValueType() =
this.isArrayTypeOrNullableArrayType ||
this.classId == StandardClassIds.Unit ||
this.classId == StandardClassIds.Any ||
this.classId == StandardClassIds.Map ||
this.classId == StandardClassIds.MutableMap ||
this.classId == StandardClassIds.String ||
this.classId in StandardClassIds.primitiveTypes ||
this.classId in StandardClassIds.unsignedTypes ||
Expand All @@ -226,6 +230,7 @@ internal fun KotlinTypeFacade.toDataFrame(
Names.TEMPORAL_AMOUNT_CLASS_ID.constructClassLikeType(emptyArray(), isNullable = true), session
)


fun FirNamedFunctionSymbol.isGetterLike(): Boolean {
val functionName = this.name.asString()
return (functionName.startsWith("get") || functionName.startsWith("is")) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,12 @@ fun box(): String {
val enumColNullable: DataColumn<EnumExample?> = enumDfNullable.value
enumColNullable.print()

// Non-JSON Map

val mapsDfNullable = listOf(mapOf(1 to null, 2 to "val"), mapOf(3 to 1, 4 to true), null).toDataFrame()
val mapsColNullable: DataColumn<Map<*, *>?> = mapsDfNullable.value
mapsColNullable.print()

return "OK"
}

Expand Down