SkillAgentSearch skills...

CodableCSV

Read and write CSV files row-by-row or through Swift's Codable interface.

Install / Use

/learn @dehesa/CodableCSV
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"> <img src="docs/assets/CodableCSV.svg" alt="Codable CSV"/> </p> <p align="center"> <a href="https://swift.org/about/#swiftorg-and-open-source"><img src="docs/assets/badges/Swift.svg" alt="Swift 5.x"></a> <a href="https://github.com/dehesa/CodableCSV/wiki/Implicit-dependencies"><img src="docs/assets/badges/Apple.svg" alt="macOS 10.10+ - iOS 8+ - tvOS 9+ - watchOS 2+"></a> <a href="https://ubuntu.com"><img src="docs/assets/badges/Ubuntu.svg" alt="Ubuntu 18.04"></a> <a href="http://doge.mit-license.org"><img src="docs/assets/badges/License.svg" alt="MIT License"></a> </p>

CodableCSV provides:

  • Imperative CSV reader/writer.
  • Declarative CSV encoder/decoder.
  • Support multiple inputs/outputs: Strings, Data blobs, URLs, and Streams (commonly used for stdin).
  • 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"])
        ]
    )
    
  • Cocoapods.

    pod 'CodableCSV', '~> 0.6.7'
    
</p></details> <details><summary>Import <code>CodableCSV</code> in the file that needs it.</summary><p>
import CodableCSV
</p></details> </ul>

There are two ways to use this library:

  1. imperatively, as a row-by-row and field-by-field reader/writer.
  2. declaratively, through Swift's Codable interface.

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 nil is 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 the headerStrategy is 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()
    
  • Sequence syntax parsing.

    let reader = try CSVReader(input: URL(...), configuration: ...)
    for row in reader {
        // Do something with the row: [String]
    }
    

    Please note the Sequence syntax (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, use readRow() instead.

Reader Configuration

CSVReader accepts the following configuration properties:

  • encoding (default nil) specify the CSV file encoding.

    This String.Encoding value specify how each underlying byte is represented (e.g. .utf8, .utf32littleEndian, etc.). If it is nil, 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, .utf8 is 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, String value, or nil for 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 (default false) 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 Data representation.

    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()
    

    CSVWriter has 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.

    CSVWriter enforces this by

Related Skills

View on GitHub
GitHub Stars482
CategoryDevelopment
Updated14d ago
Forks75

Languages

Swift

Security Score

100/100

Audited on Mar 25, 2026

No findings