CodableCSV allows you to read and write CSV files row-by-row or through Swift's Codable interface.
This framework provides:
- Active row-by-row (field-by-field) CSV reader & writer.
- Swift's Codable interface.
- Support for multiple inputs/outputs: in-memory, file system, binary socket, etc.
- CSV encoding & configuration inferral (e.g. what field/row delimiters are being used).
- Multiplatform support & no dependencies.
Swift's Codable is one of the easiest way to interface with encoded files (e.g. JSON, PLIST, and now CSV). The process is usually pretty similar.
import CodableCSV
let decoder = CSVDecoder()
decoder.delimiters = (.comma, .lineFeed)
let result = try decoder.decode(CustomType.self, from: data)
- Create and encoder or decoder for your targeted file type.
- Optionally pass any configuration you want to the decoder.
(.comma, .lineFeed)
are actually the defaults and do not need to be writen. - Decode the file (from an already preloaded datablob or a file in the file system) into a given type.
The type passed as argument must implement
Encodable
orDecodable
depending whether you are encoding or decoding. Most Swift Standard Library types already conform toCodable
. Thus, if you just want to retrieve the data raw from a CSV file, you could have done:let rows = try decoder.decode([[String]].self, from: data).
For custom types:
- Define the
Decodable
implementation. - Implement the
init(from:)
initializer if needed (many times you won't need to). - Remember that a CSV file is made of rows and each row contains several fields (how many will depend on the file). This means that you should only query for two levels of nested containers.
struct School: Decodable {
// The custom CSV file is a list of all the students.
let people: [Student]
}
struct Student: Decodable {
let name: String
let age: Int
let hasPet: Bool
}
The previous example will work if the CSV file has a header row and the header titles match exactly the property names (name
, age
, and hasPet
). A more efficient and detailed implementation:
struct Student: Decodable {
let name: String
let age: Int
let hasPet: Bool
init(from decoder: Decoder) {
var row = try decoder.unkeyedContainer
self.name = try row.decode(String.self)
self.age = try row.decode(Int.self)
self.hasPet = try row.decode(Boolean.self)
}
}
You can reap the benefits from the CSV parser just by calling the single static function parse
on a string or data blob (containing an encoded CSV file).
let (headers, rows) = try CSVReader.parse(data: input)
// `headers` is a [String]? and `rows` is a [[String]]
Optionally you can specify configuration variables specifying such things as the field and row delimiters or whether the file has a header row.
You could also initialize a CSVReader
instance and parse rows step by step.
let reader = try CSVReader(string: input)
while let row = try reader.parseRow() {
// Do something with each row
}
The CSV writer instance has a convenience static function that allows you to swiftly create a data blob from a sequence of rows.
let rows: [[String] = ...
let data = try CSVWriter.data(rows: rows)
Similarly to CSVReader
you can specify configuration variables such as file encoding or field and row delimiters.
If you want a more incremental way of writing data, you can instantiate CSVWriter
and call its public functions depending on your needs.
let writer = try CSVWriter(file: url)
try writer.beginFile()
let header = ["ID", "Name", "Age", "hasPet"]
try writer.write(row: header)
for student in school {
try writer.beginRow()
try writer.write(field: student.id)
try writer.write(field: student.name)
try writer.write(field: String(student.age))
try writer.write(field: String(student.hasPet))
}