
Utility enhances data class readability in logs by formatting them like their primary constructor. Offers `deepPrint` and `deepPrintReflection` methods using KSP or reflection for flexible usage.
Don't print with the default toString() like this in your logs:
ThreeClassesDeep3(age=55, person=SamplePersonClass(name=Dave, sampleClass=SampleClass(x=0.5, y=2.6, name=A point)), sampleClass=SampleClass(x=0.5, y=2.6, name=A point))
Use deepPrint() or deepPrintReflection() to print this instead:
ThreeClassesDeep3(
age = 55,
person =
SamplePersonClass(
name = "Dave",
sampleClass =
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
),
),
sampleClass =
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
),
)DeepPrint offers 2 implementations: 1 using KSP and the other using reflection. They have similar functionality, but don't have exact parity. This is partly due to the limitations of reflection, and partly because some features have not been added yet.
Kotlin Symbol Processing is a configurable code generation Kotlin compiler plugin from Google. Its benefits are:
To see it in action, check out KSP Simple Example and KSP Deeper Example. To try it out, please refer to KSP Quick Start.
The reflection implementation is only for Kotlin on the JVM, but its benefits are:
data classesIf you're using Kotlin on the JVM or Android, just add the dependency:
implementation("com.bradyaiello.deepprint:deep-print-reflection:<latest-version>")Now, calling deepPrintReflection() on a data class will return a readable String
that is a valid Kotlin constructor call:
MapContainer(
name = "my map",
mapToHold = mutableMapOf(
"Monday" to
Dish(
name = "Pizza",
ingredients = mutableListOf(
"dough",
"tomato sauce",
"cheese",
),
),
"Tuesday" to
Dish(
name = "Mac n Cheese",
ingredients = mutableListOf(
"mac",
"cheese",
),
),
),
id = 12345,
)There are also versions of deepPrintReflection() just for collection types.
listOf("Hi", "Hey", "How's it going?", "What's up?", "Hello")
.deepPrintListReflection()The above prints:
listOf(
"Hi",
"Hey",
"How's it going?",
"What's up?",
"Hello",
)Why the different function names for collections?
You may notice in the MapContainer that it prints mutableMapOf()
and not mapOf().
At runtime, we can't know if we're dealing with a Map or a MutableMap.
MutableMap fits the bill for both, so in a data class, mutableMapOf(),
mutableListOf(), etc. are used.
However, If you're only printing the collection as a standalone object, then
you know the type, and may want to reflect that.
For this reason, there are functions for both mutable and immutable variants,
eg. deepPrintListReflection() and deepPrintMutableListReflection().
For a simple example, we'll use a small class SampleClass:
data class SampleClass(val x: Float, val y: Float, val name: String)Calling sampleClass.toString() results in:
SampleClass(x=0.5, y=2.6, name=A point)
If we call sampleClass.deepPrint() we get readable String that is also a valid Kotlin constructor call:
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
)This can save a lot of time turning real data into test data on deeper objects.
Given the classes:
data class SampleClass(val x: Float, val y: Float, val name: String)
data class SamplePersonClass(val name: String, val sampleClass: SampleClass)
data class ThreeClassesDeep(val person: SamplePersonClass, val age: Int)If we call threeClassesDeep.toString() we get this output all on a single line, which is not valid code:
ThreeClassesDeep(person=SamplePersonClass(name=Brady, sampleClass=SampleClass(x=0.5, y=2.6, name=A point)), age=37)
But, if we call
threeClassesDeep.deepPrint()Our text output is valid Kotlin:
ThreeClassesDeep(
person =
SamplePersonClass(
name = "Brady",
sampleClass =
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
),
),
age = 37,
)We can just copy this from a log and use it in a test without modification. You can see more examples in test-project and test-project-multiplatform
Given the previous sample classes, we just add the @DeepPrint annotation,
and DeepPrint generates the deepPrint() extension functions.
Like @Parcelable, all data class properties of a data class must also
be annotated.
@DeepPrint
data class SampleClass(val x: Float, val y: Float, val name: String)
@DeepPrint
data class SamplePersonClass(val name: String, val sampleClass: SampleClass)
@DeepPrint
data class ThreeClassesDeep(val person: SamplePersonClass, val age: Int)You can reference the KSP quickstart docs for this, or check out the sample projects: test-project is for Kotlin for the JVM and test-project-multiplatform tests all targets DeepPrint supports.
settings.gradle.kts:pluginManagement {
repositories {
gradlePluginPortal()
}
}build.gradle.kts:plugins {
id("com.google.devtools.ksp") version "1.8.0-1.0.8"
}build.gradle.kts
plugins {
kotlin("jvm") // or another platform
id("com.google.devtools.ksp")
}dependencies {
// @DeepPrint annotation and a few helper functions
implementation(project("com.bradyaiello.deepprint:deep-print-annotations:0.1.0-alpha"))
// Where all the DeepPrint code generation logic resides
implementation("com.bradyaiello.deepprint:deep-print-processor:0.1.0-alpha")
// Apply the KSP plugin to the processor
ksp(implementation("com.bradyaiello.deepprint:deep-print-processor:0.1.0-alpha"))
}kotlin.sourceSets {
main {
kotlin.srcDirs(
file("$buildDir/generated/ksp/main/kotlin"),
)
}
test {
kotlin.srcDirs(
file("$buildDir/generated/ksp/test/kotlin"),
)
}
}ksp {
arg("indent", "2")
}build.gradle.kts
plugins {
kotlin("multiplatform")
id("com.google.devtools.ksp")
}kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("com.bradyaiello.deepprint:deep-print-annotations:0.1.0-alpha")
}
kotlin.srcDir("$buildDir/generated/ksp/metadata/commonMain/kotlin")
}
}
}commonMain source set before any other compile tasks.// https://github.com/evant/kotlin-inject/issues/193#issuecomment-1112930931
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().all {
if (name != "kspCommonMainKotlinMetadata") {
dependsOn("kspCommonMainKotlinMetadata")
}
kotlinOptions.freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn")
}commonMain source set.dependencies {
add("kspCommonMainMetadata", project(":deep-print-processor"))
}ksp {
arg("indent", "2")
}This project supports JVM, iOS, macos, linux, windows, NodeJS and JS for the browser.
Check out test-project-multiplatform and the docs above for setup.
The classes for the KMP example are defined in the commonMain source set because KSP does not yet support the commonTest source set.
That is not true for single source projects, like test-project.
data classes.toString().commonTest source set. Hence, test classes for the KMP test project are in commonMain.Thank you Pavlo Stavytskyi for the sample KSP project and its accompanying article. https://github.com/Morfly/ksp-sample
Don't print with the default toString() like this in your logs:
ThreeClassesDeep3(age=55, person=SamplePersonClass(name=Dave, sampleClass=SampleClass(x=0.5, y=2.6, name=A point)), sampleClass=SampleClass(x=0.5, y=2.6, name=A point))
Use deepPrint() or deepPrintReflection() to print this instead:
ThreeClassesDeep3(
age = 55,
person =
SamplePersonClass(
name = "Dave",
sampleClass =
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
),
),
sampleClass =
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
),
)DeepPrint offers 2 implementations: 1 using KSP and the other using reflection. They have similar functionality, but don't have exact parity. This is partly due to the limitations of reflection, and partly because some features have not been added yet.
Kotlin Symbol Processing is a configurable code generation Kotlin compiler plugin from Google. Its benefits are:
To see it in action, check out KSP Simple Example and KSP Deeper Example. To try it out, please refer to KSP Quick Start.
The reflection implementation is only for Kotlin on the JVM, but its benefits are:
data classesIf you're using Kotlin on the JVM or Android, just add the dependency:
implementation("com.bradyaiello.deepprint:deep-print-reflection:<latest-version>")Now, calling deepPrintReflection() on a data class will return a readable String
that is a valid Kotlin constructor call:
MapContainer(
name = "my map",
mapToHold = mutableMapOf(
"Monday" to
Dish(
name = "Pizza",
ingredients = mutableListOf(
"dough",
"tomato sauce",
"cheese",
),
),
"Tuesday" to
Dish(
name = "Mac n Cheese",
ingredients = mutableListOf(
"mac",
"cheese",
),
),
),
id = 12345,
)There are also versions of deepPrintReflection() just for collection types.
listOf("Hi", "Hey", "How's it going?", "What's up?", "Hello")
.deepPrintListReflection()The above prints:
listOf(
"Hi",
"Hey",
"How's it going?",
"What's up?",
"Hello",
)Why the different function names for collections?
You may notice in the MapContainer that it prints mutableMapOf()
and not mapOf().
At runtime, we can't know if we're dealing with a Map or a MutableMap.
MutableMap fits the bill for both, so in a data class, mutableMapOf(),
mutableListOf(), etc. are used.
However, If you're only printing the collection as a standalone object, then
you know the type, and may want to reflect that.
For this reason, there are functions for both mutable and immutable variants,
eg. deepPrintListReflection() and deepPrintMutableListReflection().
For a simple example, we'll use a small class SampleClass:
data class SampleClass(val x: Float, val y: Float, val name: String)Calling sampleClass.toString() results in:
SampleClass(x=0.5, y=2.6, name=A point)
If we call sampleClass.deepPrint() we get readable String that is also a valid Kotlin constructor call:
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
)This can save a lot of time turning real data into test data on deeper objects.
Given the classes:
data class SampleClass(val x: Float, val y: Float, val name: String)
data class SamplePersonClass(val name: String, val sampleClass: SampleClass)
data class ThreeClassesDeep(val person: SamplePersonClass, val age: Int)If we call threeClassesDeep.toString() we get this output all on a single line, which is not valid code:
ThreeClassesDeep(person=SamplePersonClass(name=Brady, sampleClass=SampleClass(x=0.5, y=2.6, name=A point)), age=37)
But, if we call
threeClassesDeep.deepPrint()Our text output is valid Kotlin:
ThreeClassesDeep(
person =
SamplePersonClass(
name = "Brady",
sampleClass =
SampleClass(
x = 0.5f,
y = 2.6f,
name = "A point",
),
),
age = 37,
)We can just copy this from a log and use it in a test without modification. You can see more examples in test-project and test-project-multiplatform
Given the previous sample classes, we just add the @DeepPrint annotation,
and DeepPrint generates the deepPrint() extension functions.
Like @Parcelable, all data class properties of a data class must also
be annotated.
@DeepPrint
data class SampleClass(val x: Float, val y: Float, val name: String)
@DeepPrint
data class SamplePersonClass(val name: String, val sampleClass: SampleClass)
@DeepPrint
data class ThreeClassesDeep(val person: SamplePersonClass, val age: Int)You can reference the KSP quickstart docs for this, or check out the sample projects: test-project is for Kotlin for the JVM and test-project-multiplatform tests all targets DeepPrint supports.
settings.gradle.kts:pluginManagement {
repositories {
gradlePluginPortal()
}
}build.gradle.kts:plugins {
id("com.google.devtools.ksp") version "1.8.0-1.0.8"
}build.gradle.kts
plugins {
kotlin("jvm") // or another platform
id("com.google.devtools.ksp")
}dependencies {
// @DeepPrint annotation and a few helper functions
implementation(project("com.bradyaiello.deepprint:deep-print-annotations:0.1.0-alpha"))
// Where all the DeepPrint code generation logic resides
implementation("com.bradyaiello.deepprint:deep-print-processor:0.1.0-alpha")
// Apply the KSP plugin to the processor
ksp(implementation("com.bradyaiello.deepprint:deep-print-processor:0.1.0-alpha"))
}kotlin.sourceSets {
main {
kotlin.srcDirs(
file("$buildDir/generated/ksp/main/kotlin"),
)
}
test {
kotlin.srcDirs(
file("$buildDir/generated/ksp/test/kotlin"),
)
}
}ksp {
arg("indent", "2")
}build.gradle.kts
plugins {
kotlin("multiplatform")
id("com.google.devtools.ksp")
}kotlin {
sourceSets {
val commonMain by getting {
dependencies {
implementation("com.bradyaiello.deepprint:deep-print-annotations:0.1.0-alpha")
}
kotlin.srcDir("$buildDir/generated/ksp/metadata/commonMain/kotlin")
}
}
}commonMain source set before any other compile tasks.// https://github.com/evant/kotlin-inject/issues/193#issuecomment-1112930931
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().all {
if (name != "kspCommonMainKotlinMetadata") {
dependsOn("kspCommonMainKotlinMetadata")
}
kotlinOptions.freeCompilerArgs = listOf("-opt-in=kotlin.RequiresOptIn")
}commonMain source set.dependencies {
add("kspCommonMainMetadata", project(":deep-print-processor"))
}ksp {
arg("indent", "2")
}This project supports JVM, iOS, macos, linux, windows, NodeJS and JS for the browser.
Check out test-project-multiplatform and the docs above for setup.
The classes for the KMP example are defined in the commonMain source set because KSP does not yet support the commonTest source set.
That is not true for single source projects, like test-project.
data classes.toString().commonTest source set. Hence, test classes for the KMP test project are in commonMain.Thank you Pavlo Stavytskyi for the sample KSP project and its accompanying article. https://github.com/Morfly/ksp-sample