Simple CSV parsing for macOS, iOS, tvOS, and watchOS.
CSV content can be loaded using the CSV
class:
import SwiftCSV
do {
// As a string
let csv: CSV = try CSV(string: "id,name,age\n1,Alice,18")
// With a custom delimiter character
let tsv: CSV = try CSV(string: "id\tname\tage\n1\tAlice\t18", delimiter: "\t")
// From a file (with errors)
let csvFile: CSV = try CSV(url: URL(fileURLWithPath: "path/to/users.csv"))
// From a file inside the app bundle, with a custom delimiter, errors, and custom encoding
let resource: CSV? = try CSV(
name: "users",
extension: "tsv",
bundle: .main,
delimiter: "\t",
encoding: .utf8)
} catch parseError as CSVParseError {
// Catch errors from parsing invalid formed CSV
} catch {
// Catch errors from trying to load files
}
If you don't care about accessing named columns, you can set the loadColumns
argument to false
and the columns Dictionary will not be populated. This can increase performance in critical cases for lots of data.
class CSV {
/// Load CSV data from a string.
///
/// - parameter string: CSV contents to parse.
/// - parameter delimiter: Character used to separate row and header fields (default is ',')
/// - parameter loadColumns: Whether to populate the `columns` dictionary (default is `true`)
/// - throws: `CSVParseError` when parsing `string` fails.
public init(string: String,
delimiter: Character = comma,
loadColumns: Bool = true) throws
/// Load a CSV file as a named resource from `bundle`.
///
/// - parameter name: Name of the file resource inside `bundle`.
/// - parameter ext: File extension of the resource; use `nil` to load the first file matching the name (default is `nil`)
/// - parameter bundle: `Bundle` to use for resource lookup (default is `.main`)
/// - parameter delimiter: Character used to separate row and header fields (default is ',')
/// - parameter encoding: encoding used to read file (default is `.utf8`)
/// - parameter loadColumns: Whether to populate the columns dictionary (default is `true`)
/// - throws: `CSVParseError` when parsing the contents of the resource fails, or file loading errors.
/// - returns: `nil` if the resource could not be found
public convenience init?(
name: String,
extension ext: String? = nil,
bundle: Bundle = .main,
delimiter: Character = comma,
encoding: String.Encoding = .utf8,
loadColumns: Bool = true) throws
/// Load a CSV file from `url`.
///
/// - parameter url: URL of the file (will be passed to `String(contentsOfURL:encoding:)` to load)
/// - parameter delimiter: Character used to separate row and header fields (default is ',')
/// - parameter encoding: Character encoding to read file (default is `.utf8`)
/// - parameter loadColumns: Whether to populate the columns dictionary (default is `true`)
/// - throws: `CSVParseError` when parsing the contents of `url` fails, or file loading errors.
public convenience init(
url: URL,
delimiter: Character = comma,
encoding: String.Encoding = .utf8,
loadColumns: Bool = true)
}
public enum CSVParseError: Error {
case generic(message: String)
case quotation(message: String)
}
let csv = CSV(string: "id,name,age\n1,Alice,18\n2,Bob,19")
csv.header //=> ["id", "name", "age"]
csv.namedRows //=> [["id": "1", "name": "Alice", "age": "18"], ["id": "2", "name": "Bob", "age": "19"]]
csv.namedColumns //=> ["id": ["1", "2"], "name": ["Alice", "Bob"], "age": ["18", "19"]]
The rows can also parsed and passed to a block on the fly, reducing the memory needed to store the whole lot in an array:
// Access each row as an array (array not guaranteed to be equal length to the header)
csv.enumerateAsArray { array in
print(array.first)
}
// Access them as a dictionary
csv.enumerateAsDict { dict in
print(dict["name"])
}
pod "SwiftCSV"
github "swiftcsv/SwiftCSV"