@@ -6,6 +6,7 @@ import io.kotest.matchers.collections.shouldBeIn
66import io.kotest.matchers.shouldBe
77import io.kotest.matchers.string.shouldContain
88import io.kotest.matchers.string.shouldNotContain
9+ import io.kotest.matchers.string.shouldStartWith
910import io.kotest.matchers.types.instanceOf
1011import io.kotest.matchers.types.shouldBeInstanceOf
1112import kotlinx.serialization.json.Json
@@ -20,35 +21,43 @@ import org.intellij.lang.annotations.Language
2021import org.jetbrains.kotlinx.dataframe.AnyFrame
2122import org.jetbrains.kotlinx.dataframe.DataFrame
2223import org.jetbrains.kotlinx.dataframe.DataRow
24+ import org.jetbrains.kotlinx.dataframe.api.FormattedFrame
2325import org.jetbrains.kotlinx.dataframe.api.JsonPath
2426import org.jetbrains.kotlinx.dataframe.api.allNulls
2527import org.jetbrains.kotlinx.dataframe.api.colsOf
2628import org.jetbrains.kotlinx.dataframe.api.columnsCount
2729import org.jetbrains.kotlinx.dataframe.api.convert
2830import org.jetbrains.kotlinx.dataframe.api.dataFrameOf
2931import org.jetbrains.kotlinx.dataframe.api.forEach
32+ import org.jetbrains.kotlinx.dataframe.api.format
3033import org.jetbrains.kotlinx.dataframe.api.getColumnGroup
34+ import org.jetbrains.kotlinx.dataframe.api.getColumns
3135import org.jetbrains.kotlinx.dataframe.api.getFrameColumn
3236import org.jetbrains.kotlinx.dataframe.api.print
3337import org.jetbrains.kotlinx.dataframe.api.schema
3438import org.jetbrains.kotlinx.dataframe.api.toFloat
3539import org.jetbrains.kotlinx.dataframe.api.toMap
40+ import org.jetbrains.kotlinx.dataframe.api.with
3641import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
3742import org.jetbrains.kotlinx.dataframe.columns.ColumnKind
3843import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
3944import org.jetbrains.kotlinx.dataframe.columns.ValueColumn
4045import org.jetbrains.kotlinx.dataframe.impl.io.SERIALIZATION_VERSION
4146import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.COLUMNS
4247import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.DATA
48+ import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.IS_FORMATTED
4349import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.KIND
4450import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.KOTLIN_DATAFRAME
4551import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.METADATA
4652import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.NCOL
4753import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.NROW
54+ import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.TYPE
55+ import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.TYPES
4856import org.jetbrains.kotlinx.dataframe.impl.io.SerializationKeys.VERSION
4957import org.jetbrains.kotlinx.dataframe.impl.io.readJsonImpl
5058import org.jetbrains.kotlinx.dataframe.io.JSON.TypeClashTactic.ANY_COLUMNS
5159import org.jetbrains.kotlinx.dataframe.io.JSON.TypeClashTactic.ARRAY_AND_VALUE_COLUMNS
60+ import org.jetbrains.kotlinx.dataframe.jupyter.KotlinNotebookPluginUtils.convertToDataFrame
5261import java.net.URL
5362import kotlin.reflect.KType
5463import kotlin.reflect.typeOf
@@ -1040,6 +1049,35 @@ class JsonTests {
10401049 val metadata = json[METADATA ]!! .jsonObject
10411050 metadata[NROW ]!! .jsonPrimitive.int shouldBe 1
10421051 metadata[NCOL ]!! .jsonPrimitive.int shouldBe 4
1052+ metadata[IS_FORMATTED ]!! .jsonPrimitive.boolean shouldBe false
1053+ val columns = metadata[COLUMNS ]!! .jsonArray.map { it.jsonPrimitive.content }
1054+ columns shouldBe listOf (" id" , " node_id" , " name" , " full_name" )
1055+
1056+ val decodedData = json[KOTLIN_DATAFRAME ]!! .jsonArray
1057+ val decodedDf = DataFrame .readJsonStr(decodedData.toString())
1058+ decodedDf shouldBe df
1059+ }
1060+
1061+ @Suppress(" USELESS_IS_CHECK" )
1062+ @Test
1063+ fun `json with metadata flat formatted table` () {
1064+ @Language(" json" )
1065+ val data =
1066+ """
1067+ [{"id":3602279,"node_id":"MDEwOlJlcG9zaXRvcnkzNjAyMjc5","name":"kotlin-web-demo","full_name":"JetBrains/kotlin-web-demo"}]
1068+ """ .trimIndent()
1069+ // simulating the functions used to define whether the dataframe is formatted
1070+ val formattedDf = DataFrame .readJsonStr(data).format().with { background(blue) }
1071+ val df = convertToDataFrame(formattedDf)
1072+ val jsonStr = df.toJsonWithMetadata(df.rowsCount(), isFormatted = formattedDf is FormattedFrame <* >).trimIndent()
1073+ val json = parseJsonStr(jsonStr)
1074+
1075+ json[VERSION ]!! .jsonPrimitive.content shouldBe SERIALIZATION_VERSION
1076+
1077+ val metadata = json[METADATA ]!! .jsonObject
1078+ metadata[NROW ]!! .jsonPrimitive.int shouldBe 1
1079+ metadata[NCOL ]!! .jsonPrimitive.int shouldBe 4
1080+ metadata[IS_FORMATTED ]!! .jsonPrimitive.boolean shouldBe true
10431081 val columns = metadata[COLUMNS ]!! .jsonArray.map { it.jsonPrimitive.content }
10441082 columns shouldBe listOf (" id" , " node_id" , " name" , " full_name" )
10451083
@@ -1079,6 +1117,8 @@ class JsonTests {
10791117 val df = DataFrame .readJson(testJson(" repositories" ))
10801118 val jsonStr = df.toJsonWithMetadata(df.rowsCount()).trimIndent()
10811119 val json = parseJsonStr(jsonStr)
1120+ json[METADATA ]!! .jsonObject[IS_FORMATTED ]!! .jsonPrimitive.boolean shouldBe false
1121+
10821122 val row = json[KOTLIN_DATAFRAME ]!! .jsonArray[0 ].jsonObject
10831123
10841124 val contributors = row[" contributors" ]!! .jsonObject
@@ -1101,6 +1141,8 @@ class JsonTests {
11011141 val nestedFrameRowLimit = 20
11021142 val jsonStr = df.toJsonWithMetadata(df.rowsCount(), nestedFrameRowLimit).trimIndent()
11031143 val json = parseJsonStr(jsonStr)
1144+ json[METADATA ]!! .jsonObject[IS_FORMATTED ]!! .jsonPrimitive.boolean shouldBe false
1145+
11041146 val row = json[KOTLIN_DATAFRAME ]!! .jsonArray[0 ].jsonObject
11051147
11061148 val contributors = row[" contributors" ]!! .jsonObject
@@ -1114,6 +1156,33 @@ class JsonTests {
11141156 decodedData.size shouldBe nestedFrameRowLimit
11151157 }
11161158
1159+ @Test
1160+ fun `json with metadata containing formatted nested frames` () {
1161+ val df = DataFrame .readJson(testJson(" repositories" ))
1162+ .convert { frameCols() }.with { it.format().with { background(blue) } }
1163+
1164+ // simulating the functions used to define whether the dataframe is formatted
1165+ val hasFormattedColumns = df.getColumns { colsAtAnyDepth().colsOf<FormattedFrame <* >? > () }.isNotEmpty()
1166+ val jsonStr = df.toJsonWithMetadata(df.rowsCount(), isFormatted = hasFormattedColumns).trimIndent()
1167+
1168+ val json = parseJsonStr(jsonStr)
1169+ val metadata = json[METADATA ]!! .jsonObject
1170+ metadata[IS_FORMATTED ]!! .jsonPrimitive.boolean shouldBe true
1171+ metadata[NCOL ]!! .jsonPrimitive.int shouldBe 1
1172+ metadata[NROW ]!! .jsonPrimitive.int shouldBe 1
1173+ metadata[COLUMNS ]!! .jsonArray.single().jsonPrimitive.content shouldBe " contributors"
1174+ metadata[TYPES ]!! .jsonArray.single().jsonObject.let {
1175+ it[KIND ]!! .jsonPrimitive.content shouldBe " ValueColumn"
1176+ it[TYPE ]!! .jsonPrimitive.content shouldStartWith FormattedFrame ::class .qualifiedName!!
1177+ }
1178+
1179+ val row = json[KOTLIN_DATAFRAME ]!! .jsonArray[0 ].jsonObject
1180+
1181+ // is read as value column
1182+ val contributors = row[" contributors" ]!! .jsonPrimitive.content
1183+ contributors shouldStartWith FormattedFrame ::class .qualifiedName!!
1184+ }
1185+
11171186 @Test
11181187 fun `serialize column with list of objects` () {
11191188 val df = dataFrameOf(" col" )(Regex (" .+" ).findAll(" abc" ).toList())
0 commit comments