Check this post - https://www.baeldung.com/kotlin/csv-files - it contains a few nice references to some libraries or pure Kotlin approaches if you prefer to use those.
Applying it to your use case, maybe something like:
fun OutputStream.writeCsv(context: Context, listOfData: List<YourData>) {
//get data create directory
......
val writer = bufferedWriter()
writer.write(""""Name", "Address", "Code"""")
writer.newLine()
listOfData.forEach {
writer.write("${it.name}, ${it.address}, \"${it.code}\"")
writer.newLine()
}
writer.flush()
writer.close()
}
And then call this where you need it,
FileOutputStream("out777.cs").apply { writeCsv(listOfData) }
One thing to note here is using bufferedWriter:
FileWriter writes directly into Files and should be used only when the number of writes is less. BufferedWriter: BufferedWriter is almost similar to FileWriter but it uses internal buffer to write data into File. So if the number of write operations is more, the actual IO operations are less and performance is better.
Not tested but this might point you in a more cleaner and efficient solution. There are also some libraries specifically for this. Take a look
Answer from moondev on Stack OverflowThis can be achieved using basic Kotlin instead of an external library.
data class
data class Category(
val id: String,
val name: String,
)
utility function
fun <T> csvOf(
headers: List<String>,
data: List<T>,
itemBuilder: (T) -> List<String>
) = buildString {
append(headers.joinToString(",") { "\"$it\"" })
append("\n")
data.forEach { item ->
append(itemBuilder(item).joinToString(",") { "\"$it\"" })
append("\n")
}
}
usage
fun main() {
val firstCategory = Category(0, "Phone")
val secondCategory = Category(1, "Laptop")
val categories = listOf(firstCategory, secondCategory)
val csv = csvOf(
listOf("id", "name"),
categories
) {
listOf(it.id.toString(), it.name)
}
println(csv)
}
You can use the solution offered by this medium article:
To sum up
You can create a simple generic function
import java.io.FileWriter
inline fun <reified T> writeCsvFile(data: Collection<T>, fileName: String) {
FileWriter(fileName).use { writer ->
csvMapper.writer(csvMapper.schemaFor(T::class.java).withHeader())
.writeValues(writer)
.writeAll(data)
.close()
}
}
I tested it with the following dependencies:
com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.11.1
com.fasterxml.jackson.module:jackson-module-kotlin:2.12.5
The given regex just works fine. Currently you try to split the string s at the raw regex as delimeter, which does not exists in s. Simply add .toRegex() to the regex.
val s = "John Doe, 13, \"Subject 1, Subject 2, Subject 3\""
var list: List<String> = s.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)".toRegex())
Log.d("size:", list.size.toString() + " - subjects:" + list[2])
I'd recommend not doing the parsing yourself, but using an existing library. (For example, I found Apache Commons CSV easy to use from Kotlin.)
Although writing parsing code can be fun, and CSV seems simple enough, it has enough complications and variations that unless you created it yourself, you're likely to miss some cases. (Not just escaped quotes, but other escaped characters, nested quotes, fields which include newlines, comment lines… And my favourite gotcha: MS Excel uses the machine's list separator, which can be semicolon or another character instead of comma to separate fields!)
Trust me, I've been there…
The simplest way to solve this is not to use reflection at all. You can pass the property values directly to the class constructor:
data class Member(val firstName: String, val lastName: String)
fun readCsvFileKotlin() {
// ...
for (line in reader) {
val mbrProperties = line.split(",")
memberList.add(Member(mbrProperties[0], mbrProperties[1]))
}
}
If you want to set properties through reflection, you need to declare them as var rather than val, so that they can be changed after the object is created. Then you can use the set method on the property object to change the property value:
data class Member(var firstName: String? = null, var lastName: String? = null)
fun readCsvFileKotlin() {
// ...
val prop = Member::firstName
for (line in reader) {
val mbrProperties = line.split(",")
val member = Member()
prop.set(member, mbrProperties[0])
memberList.add(member)
}
}
If you still look to do that, I created my own CSV library that uses reflection to instantiate POJOs directly from the CSV input and manages errors in the CSV much better than regular parsers I tested, called Kotlin CSV Stream
I actually created it after giving up on traditional parsers because they all did not fill my needs.
[Edit October 2019] A couple of months after I wrote this answer, Koyama Kenta wrote a Kotlin targeted library which can be found at https://github.com/doyaaaaaken/kotlin-csv and which looks much better to me than opencsv.
Example usage: (for more info see the github page mentioned)
import com.github.doyaaaaaken.kotlincsv.dsl.csvReader
fun main() {
csvReader().open("src/main/resources/test.csv") {
readAllAsSequence().forEach { row ->
//Do something
println(row) //[a, b, c]
}
}
}
For a complete minimal project with this example, see https://github.com/PHPirates/kotlin-csv-reader-example
Old answer using opencsv:
As suggested, it is convenient to use opencsv. Here is a somewhat minimal example:
// You can of course remove the .withCSVParser part if you use the default separator instead of ;
val csvReader = CSVReaderBuilder(FileReader("filename.csv"))
.withCSVParser(CSVParserBuilder().withSeparator(';').build())
.build()
// Maybe do something with the header if there is one
val header = csvReader.readNext()
// Read the rest
var line: Array<String>? = csvReader.readNext()
while (line != null) {
// Do something with the data
println(line[0])
line = csvReader.readNext()
}
As seen in the docs when you do not need to process every line separately you can get the result in the form of a Map:
import com.opencsv.CSVReaderHeaderAware
import java.io.FileReader
fun main() {
val reader = CSVReaderHeaderAware(FileReader("test.csv"))
val resultList = mutableListOf<Map<String, String>>()
var line = reader.readMap()
while (line != null) {
resultList.add(line)
line = reader.readMap()
}
println(resultList)
// Line 2, by column name
println(resultList[1]["my column name"])
}
Dependency for Gradle: compile 'com.opencsv:opencsv:4.6' or for Gradle Kotlin DSL: compile("com.opencsv:opencsv:4.6") (as always, check for latest version in docs).
In terms of easiness, kotlin written csv library is better.
For example, you can write code in DSL like way with below library that I created:
https://github.com/doyaaaaaken/kotlin-csv
csvReader().open("test.csv") {
readAllAsSequence().forEach { row ->
//Do something with the data
println(row)
}
}