I'm using CSVMapper in my kotlin code to parse a CSV file.
Here's a sample code:
data class UserDto(name: String): Serializable {
}
class CSVMigrator {
private val csvFile: String
private val csvMapper = CsvMapper().registerModule(
KotlinModule.Builder()
.withReflectionCacheSize(512)
.configure(KotlinFeature.NullToEmptyCollection, false)
.configure(KotlinFeature.NullToEmptyMap, false)
.configure(KotlinFeature.NullIsSameAsDefault, false)
.configure(KotlinFeature.StrictNullChecks, false)
.build()
)
public constructor(filePath: String) {
this.csvFile = filePath
}
private inline fun <reified T> getIterator(reader: Reader): MappingIterator<T>? {
val schema = CsvSchema
.emptySchema()
.withLineSeparator("\n")
.withColumnSeparator(',')
.withQuoteChar('"')
.withHeader()
return csvMapper
.readerFor(T::class.java)
.without(StreamReadFeature.AUTO_CLOSE_SOURCE)
.with(schema)
.readValues<T>(reader)
}
fun readFromFile() {
FileReader(csvFile).use { reader ->
val iterator = getIterator<UserDto>(reader)
if (iterator != null) {
var data = mutableListOf<UserDto>()
while (iterator.hasNext()) {
try {
val lineElement = iterator.next()
print(lineElement)
} catch (e: RuntimeJsonMappingException) {
println("Iterator Exception: " + e.localizedMessage)
}
}
}
}
}
}
In the above code, I've hard-coded the UserDto class to parse the CSV to a model. But I want to make this class generic (as in below code) such that I can parse any CSV based on the data class.
class CSVMigrator<X: Serializable> {
}
And I want to change UserDto with Generic class "X" everywhere.
I'm getting the below error when I replace my data class with "X" in the line val iterator = getIterator<X>(reader)
The error is
Cannot use 'X' as reified type parameter. Use a class instead.
Class type parameters cannot be reified, so they cannot be passed to generic methods with reified type parameters.
You should store a Class<X>
in CSVMigrator
, or a TypeReference
if X
itself can be a generic type. Then write a non-reified version of getIterator
and pass those to a different overload of readerFor
that takes Class<T>
/TypeReference
.
class CSVMigrator<X: Serializable>(
val clazz: Class<X>,
// or:
// val typeReference: TypeReference<X>,
)
private fun <T> getIterator(
reader: Reader,
clazz: Class<T>,
// or:
// typeReference: TypeReference<T>,
): MappingIterator<T>? {
val schema = CsvSchema
.emptySchema()
.withLineSeparator("\n")
.withColumnSeparator(',')
.withQuoteChar('"')
.withHeader()
return csvMapper
.readerFor(clazz /* or typeReference */)
.without(StreamReadFeature.AUTO_CLOSE_SOURCE)
.with(schema)
.readValues<T>(reader)
}
Usage:
val iterator = getIterator(this.clazz /* or this.typeReference */)
When creating a CSVMigrator
, pass in the Class
or TypeReference
.
CSVMigrator(UserDto::class.java)
// or: (not sure if Jackson for Kotlin has an easier to create a TypeReference)
CSVMigrator(object: TypeReference<SomeGenericType<String>>() {})