CodableCSV
Read and write CSV files row-by-row or through Swift's Codable interface.
Install / Use
/learn @dehesa/CodableCSVREADME
CodableCSV provides:
- Imperative CSV reader/writer.
- Declarative CSV encoder/decoder.
- Support multiple inputs/outputs:
Strings,Datablobs,URLs, andStreams (commonly used forstdin). - Support numerous string encodings and Byte Order Markers (BOM).
- Extensive configuration: delimiters, escaping scalar, trim strategy, codable strategies, presampling, etc.
- RFC4180 compliant with default configuration and CRLF (
\r\n) row delimiter. - Multiplatform support with no dependencies (the Swift Standard Library and Foundation are implicit dependencies).
Usage
To use this library, you need to:
<ul> <details><summary>Add <code>CodableCSV</code> to your project.</summary><p>You can choose to add the library through SPM or Cocoapods:
-
SPM (Swift Package Manager).
// swift-tools-version:5.1 import PackageDescription let package = Package( /* Your package name, supported platforms, and generated products go here */ dependencies: [ .package(url: "https://github.com/dehesa/CodableCSV.git", from: "0.6.7") ], targets: [ .target(name: /* Your target name here */, dependencies: ["CodableCSV"]) ] ) -
pod 'CodableCSV', '~> 0.6.7'
import CodableCSV
</p></details>
</ul>
There are two ways to use this library:
- imperatively, as a row-by-row and field-by-field reader/writer.
- declaratively, through Swift's
Codableinterface.
Imperative Reader/Writer
The following types provide imperative control on how to read/write CSV data.
<ul> <details><summary><code>CSVReader</code></summary><p>A CSVReader parses CSV data from a given input (String, Data, URL, or InputStream) and returns CSV rows as a Strings array. CSVReader can be used at a high-level, in which case it parses an input completely; or at a low-level, in which each row is decoded when requested.
-
Complete input parsing.
let data: Data = ... let result = try CSVReader.decode(input: data)Once the input is completely parsed, you can choose how to access the decoded data:
let headers: [String] = result.headers // Access the CSV rows (i.e. raw [String] values) let rows = result.rows let row = result[0] // Access the CSV record (i.e. convenience structure over a single row) let records = result.records let record = result[record: 0] // Access the CSV columns through indices or header values. let columns = result.columns let column = result[column: 0] let column = result[column: "Name"] // Access fields through indices or header values. let fieldB: String = result[row: 3, column: 2] let fieldA: String? = result[row: 2, column: "Age"] -
Row-by-row parsing.
let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine } let rowA = try reader.readRow()Parse a row at a time, till
nilis returned; or exit the scope and the reader will clean up all used memory.// Let's assume the input is: let string = "numA,numB,numC\n1,2,3\n4,5,6\n7,8,9" // The headers property can be accessed at any point after initialization. let headers: [String] = reader.headers // ["numA", "numB", "numC"] // Keep querying rows till `nil` is received. guard let rowB = try reader.readRow(), // ["4", "5", "6"] let rowC = try reader.readRow() /* ["7", "8", "9"] */ else { ... }Alternatively you can use the
readRecord()function which also returns the next CSV row, but it wraps the result in a convenience structure. This structure lets you access each field with the header name (as long as theheaderStrategyis marked with.firstLine).let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine } let headers = reader.headers // ["numA", "numB", "numC"] let recordA = try reader.readRecord() let rowA = recordA.row // ["1", "2", "3"] let fieldA = recordA[0] // "1" let fieldB = recordA["numB"] // "2" let recordB = try reader.readRecord() -
Sequencesyntax parsing.let reader = try CSVReader(input: URL(...), configuration: ...) for row in reader { // Do something with the row: [String] }Please note the
Sequencesyntax (i.e.IteratorProtocol) doesn't throw errors; therefore if the CSV data is invalid, the previous code will crash. If you don't control the CSV data origin, usereadRow()instead.
Reader Configuration
CSVReader accepts the following configuration properties:
-
encoding(defaultnil) specify the CSV file encoding.This
String.Encodingvalue specify how each underlying byte is represented (e.g..utf8,.utf32littleEndian, etc.). If it isnil, the library will try to figure out the file encoding through the file's Byte Order Marker. If the file doesn't contain a BOM,.utf8is presumed. -
delimiters(default(field: ",", row: "\n")) specify the field and row delimiters.CSV fields are separated within a row with field delimiters (commonly a "comma"). CSV rows are separated through row delimiters (commonly a "line feed"). You can specify any unicode scalar,
Stringvalue, ornilfor unknown delimiters. -
escapingStrategy(default") specify the Unicode scalar used to escape fields.CSV fields can be escaped in case they contain privilege characters, such as field/row delimiters. Commonly the escaping character is a double quote (i.e.
"), by setting this configuration value you can change it (e.g. a single quote), or disable the escaping functionality. -
headerStrategy(default.none) indicates whether the CSV data has a header row or not.CSV files may contain an optional header row at the very beginning. This configuration value lets you specify whether the file has a header row or not, or whether you want the library to figure it out.
-
trimStrategy(default empty set) trims the given characters at the beginning and end of each parsed field.The trim characters are applied for the escaped and unescaped fields. The set cannot include any of the delimiter characters or the escaping scalar. If so, an error will be thrown during initialization.
-
presample(defaultfalse) indicates whether the CSV data should be completely loaded into memory before parsing begins.Loading all data into memory may provide faster iteration for small to medium size files, since you get rid of the overhead of managing an
InputStream.
The configuration values are set during initialization and can be passed to the CSVReader instance through a structure or with a convenience closure syntax:
let reader = CSVReader(input: ...) {
$0.encoding = .utf8
$0.delimiters.row = "\r\n"
$0.headerStrategy = .firstLine
$0.trimStrategy = .whitespaces
}
</p></details>
<details><summary><code>CSVWriter</code></summary><p>
A CSVWriter encodes CSV information into a specified target (i.e. a String, or Data, or a file). It can be used at a high-level, by encoding completely a prepared set of information; or at a low-level, in which case rows or fields can be written individually.
-
Complete CSV rows encoding.
let input = [ ["numA", "numB", "name" ], ["1" , "2" , "Marcos" ], ["4" , "5" , "Marine-Anaïs"] ] let data = try CSVWriter.encode(rows: input) let string = try CSVWriter.encode(rows: input, into: String.self) try CSVWriter.encode(rows: input, into: URL("~/Desktop/Test.csv")!, append: false) -
Row-by-row encoding.
let writer = try CSVWriter(fileURL: URL("~/Desktop/Test.csv")!, append: false) for row in input { try writer.write(row: row) } try writer.endEncoding()Alternatively, you may write directly to a buffer in memory and access its
Datarepresentation.let writer = try CSVWriter { $0.headers = input[0] } for row in input.dropFirst() { try writer.write(row: row) } try writer.endEncoding() let result = try writer.data() -
Field-by-field encoding.
let writer = try CSVWriter(fileURL: URL("~/Desktop/Test.csv")!, append: false) try writer.write(row: input[0]) input[1].forEach { try writer.write(field: field) } try writer.endRow() try writer.write(fields: input[2]) try writer.endRow() try writer.endEncoding()CSVWriterhas a wealth of low-level imperative APIs, that let you write one field, several fields at a time, end a row, write an empty row, etc.Please notice that a CSV requires all rows to have the same amount of fields.
CSVWriterenforces this by
Related Skills
node-connect
352.2kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
352.2kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
352.2kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
