{"id":1705,"url":"https://github.com/dehesa/CodableCSV","last_synced_at":"2025-08-06T13:32:00.399Z","repository":{"id":35362059,"uuid":"144853304","full_name":"dehesa/CodableCSV","owner":"dehesa","description":"Read and write CSV files row-by-row or through Swift's Codable interface.","archived":false,"fork":false,"pushed_at":"2023-11-17T04:16:07.000Z","size":941,"stargazers_count":462,"open_issues_count":16,"forks_count":74,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-23T22:59:46.715Z","etag":null,"topics":["codable","csv","csv-parser","csv-reader","csv-writer","decoder","encoder","swift","swift-codable"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dehesa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"docs/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"docs/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":"docs/SUPPORT.md","governance":null,"roadmap":null,"authors":null}},"created_at":"2018-08-15T12:58:38.000Z","updated_at":"2024-11-20T19:27:58.000Z","dependencies_parsed_at":"2024-01-02T21:05:37.861Z","dependency_job_id":"96e294ca-670a-4592-a91c-325c1e016bdd","html_url":"https://github.com/dehesa/CodableCSV","commit_stats":{"total_commits":174,"total_committers":11,"mean_commits":"15.818181818181818","dds":"0.13218390804597702","last_synced_commit":"99ace2cfdfbc19108b529c021005fb57a460e715"},"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2FCodableCSV","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2FCodableCSV/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2FCodableCSV/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dehesa%2FCodableCSV/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dehesa","download_url":"https://codeload.github.com/dehesa/CodableCSV/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228905444,"owners_count":17989766,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codable","csv","csv-parser","csv-reader","csv-writer","decoder","encoder","swift","swift-codable"],"created_at":"2024-01-05T20:15:53.832Z","updated_at":"2024-12-09T14:30:43.860Z","avatar_url":"https://github.com/dehesa.png","language":"Swift","readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"docs/assets/CodableCSV.svg\" alt=\"Codable CSV\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://swift.org/about/#swiftorg-and-open-source\"\u003e\u003cimg src=\"docs/assets/badges/Swift.svg\" alt=\"Swift 5.x\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/dehesa/CodableCSV/wiki/Implicit-dependencies\"\u003e\u003cimg src=\"docs/assets/badges/Apple.svg\" alt=\"macOS 10.10+ - iOS 8+ - tvOS 9+ - watchOS 2+\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://ubuntu.com\"\u003e\u003cimg src=\"docs/assets/badges/Ubuntu.svg\" alt=\"Ubuntu 18.04\"\u003e\u003c/a\u003e\n    \u003ca href=\"http://doge.mit-license.org\"\u003e\u003cimg src=\"docs/assets/badges/License.svg\" alt=\"MIT License\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n[CodableCSV](https://github.com/dehesa/CodableCSV) provides:\n\n-   Imperative CSV reader/writer.\n-   Declarative CSV encoder/decoder.\n-   Support multiple inputs/outputs: `String`s, `Data` blobs, `URL`s, and `Stream`s (commonly used for `stdin`).\n-   Support numerous string encodings and [Byte Order Markers](https://en.wikipedia.org/wiki/Byte_order_mark) (BOM).\n-   Extensive configuration: delimiters, escaping scalar, trim strategy, codable strategies, presampling, etc.\n-   [RFC4180](https://tools.ietf.org/html/rfc4180) compliant with default configuration and CRLF (`\\r\\n`) row delimiter.\n-   Multiplatform support with no dependencies (the Swift Standard Library and Foundation are implicit dependencies).\n\n# Usage\n\nTo use this library, you need to:\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003eAdd \u003ccode\u003eCodableCSV\u003c/code\u003e to your project.\u003c/summary\u003e\u003cp\u003e\n\nYou can choose to add the library through SPM or Cocoapods:\n\n-   [SPM](https://github.com/apple/swift-package-manager/tree/master/Documentation) (Swift Package Manager).\n\n    ```swift\n    // swift-tools-version:5.1\n    import PackageDescription\n\n    let package = Package(\n        /* Your package name, supported platforms, and generated products go here */\n        dependencies: [\n            .package(url: \"https://github.com/dehesa/CodableCSV.git\", from: \"0.6.7\")\n        ],\n        targets: [\n            .target(name: /* Your target name here */, dependencies: [\"CodableCSV\"])\n        ]\n    )\n    ```\n\n-   [Cocoapods](https://cocoapods.org).\n\n    ```\n    pod 'CodableCSV', '~\u003e 0.6.7'\n    ```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eImport \u003ccode\u003eCodableCSV\u003c/code\u003e in the file that needs it.\u003c/summary\u003e\u003cp\u003e\n\n```swift\nimport CodableCSV\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\nThere are two ways to use this library:\n\n1. imperatively, as a row-by-row and field-by-field reader/writer.\n2. declaratively, through Swift's `Codable` interface.\n\n## Imperative Reader/Writer\n\nThe following types provide imperative control on how to read/write CSV data.\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVReader\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nA `CSVReader` parses CSV data from a given input (`String`, `Data`, `URL`, or `InputStream`) and returns CSV rows as a `String`s 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.\n\n-   Complete input parsing.\n\n    ```swift\n    let data: Data = ...\n    let result = try CSVReader.decode(input: data)\n    ```\n\n    Once the input is completely parsed, you can choose how to access the decoded data:\n\n    ```swift\n    let headers: [String] = result.headers\n    // Access the CSV rows (i.e. raw [String] values)\n    let rows = result.rows\n    let row = result[0]\n    // Access the CSV record (i.e. convenience structure over a single row)\n    let records = result.records\n    let record = result[record: 0]\n    // Access the CSV columns through indices or header values.\n    let columns = result.columns\n    let column = result[column: 0]\n    let column = result[column: \"Name\"]\n    // Access fields through indices or header values.\n    let fieldB: String = result[row: 3, column: 2]\n    let fieldA: String? = result[row: 2, column: \"Age\"]\n    ```\n\n-   Row-by-row parsing.\n\n    ```swift\n    let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine }\n    let rowA = try reader.readRow()\n    ```\n\n    Parse a row at a time, till `nil` is returned; or exit the scope and the reader will clean up all used memory.\n\n    ```swift\n    // Let's assume the input is:\n    let string = \"numA,numB,numC\\n1,2,3\\n4,5,6\\n7,8,9\"\n    // The headers property can be accessed at any point after initialization.\n    let headers: [String] = reader.headers  // [\"numA\", \"numB\", \"numC\"]\n    // Keep querying rows till `nil` is received.\n    guard let rowB = try reader.readRow(),  // [\"4\", \"5\", \"6\"]\n          let rowC = try reader.readRow()   /* [\"7\", \"8\", \"9\"] */ else { ... }\n    ```\n\n    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`).\n\n    ```swift\n    let reader = try CSVReader(input: string) { $0.headerStrategy = .firstLine }\n    let headers = reader.headers      // [\"numA\", \"numB\", \"numC\"]\n\n    let recordA = try reader.readRecord()\n    let rowA = recordA.row         // [\"1\", \"2\", \"3\"]\n    let fieldA = recordA[0]        // \"1\"\n    let fieldB = recordA[\"numB\"]   // \"2\"\n\n    let recordB = try reader.readRecord()\n    ```\n\n-   `Sequence` syntax parsing.\n\n    ```swift\n    let reader = try CSVReader(input: URL(...), configuration: ...)\n    for row in reader {\n        // Do something with the row: [String]\n    }\n    ```\n\n    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.\n\n### Reader Configuration\n\n`CSVReader` accepts the following configuration properties:\n\n-   `encoding` (default `nil`) specify the CSV file encoding.\n\n    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](https://en.wikipedia.org/wiki/Byte_order_mark). If the file doesn't contain a BOM, `.utf8` is presumed.\n\n-   `delimiters` (default `(field: \",\", row: \"\\n\")`) specify the field and row delimiters.\n\n    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.\n\n-   `escapingStrategy` (default `\"`) specify the Unicode scalar used to escape fields.\n\n    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.\n\n-   `headerStrategy` (default `.none`) indicates whether the CSV data has a header row or not.\n\n    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.\n\n-   `trimStrategy` (default empty set) trims the given characters at the beginning and end of each parsed field.\n\n    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.\n\n-   `presample` (default `false`) indicates whether the CSV data should be completely loaded into memory before parsing begins.\n\n    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`.\n\nThe configuration values are set during initialization and can be passed to the `CSVReader` instance through a structure or with a convenience closure syntax:\n\n```swift\nlet reader = CSVReader(input: ...) {\n    $0.encoding = .utf8\n    $0.delimiters.row = \"\\r\\n\"\n    $0.headerStrategy = .firstLine\n    $0.trimStrategy = .whitespaces\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVWriter\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nA `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.\n\n-   Complete CSV rows encoding.\n\n    ```swift\n    let input = [\n        [\"numA\", \"numB\", \"name\"        ],\n        [\"1\"   , \"2\"   , \"Marcos\"      ],\n        [\"4\"   , \"5\"   , \"Marine-Anaïs\"]\n    ]\n    let data   = try CSVWriter.encode(rows: input)\n    let string = try CSVWriter.encode(rows: input, into: String.self)\n    try CSVWriter.encode(rows: input, into: URL(\"~/Desktop/Test.csv\")!, append: false)\n    ```\n\n-   Row-by-row encoding.\n\n    ```swift\n    let writer = try CSVWriter(fileURL: URL(\"~/Desktop/Test.csv\")!, append: false)\n    for row in input {\n        try writer.write(row: row)\n    }\n    try writer.endEncoding()\n    ```\n\n    Alternatively, you may write directly to a buffer in memory and access its `Data` representation.\n\n    ```swift\n    let writer = try CSVWriter { $0.headers = input[0] }\n    for row in input.dropFirst() {\n        try writer.write(row: row)\n    }\n    try writer.endEncoding()\n    let result = try writer.data()\n    ```\n\n-   Field-by-field encoding.\n\n    ```swift\n    let writer = try CSVWriter(fileURL: URL(\"~/Desktop/Test.csv\")!, append: false)\n    try writer.write(row: input[0])\n\n    input[1].forEach {\n        try writer.write(field: field)\n    }\n    try writer.endRow()\n\n    try writer.write(fields: input[2])\n    try writer.endRow()\n\n    try writer.endEncoding()\n    ```\n\n    `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.\n\n    \u003e Please notice that a CSV requires all rows to have the same amount of fields.\n\n    `CSVWriter` enforces this by throwing an error when you try to write more the expected amount of fields, or filling a row with empty fields when you call `endRow()` but not all fields have been written.\n\n### Writer Configuration\n\n`CSVWriter` accepts the following configuration properties:\n\n-   `delimiters` (default `(field: \",\", row: \"\\n\")`) specify the field and row delimiters.\n\n    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.\n\n-   `escapingStrategy` (default `.doubleQuote`) specify the Unicode scalar used to escape fields.\n\n    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.\n\n-   `headers` (default `[]`) indicates whether the CSV data has a header row or not.\n\n    CSV files may contain an optional header row at the very beginning. If this configuration value is empty, no header row is written.\n\n-   `encoding` (default `nil`) specify the CSV file encoding.\n\n    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](https://en.wikipedia.org/wiki/Byte_order_mark). If the file doesn't contain a BOM, `.utf8` is presumed.\n\n-   `bomStrategy` (default `.convention`) indicates whether a Byte Order Marker will be included at the beginning of the CSV representation.\n\n    The OS convention is that BOMs are never written, except when `.utf16`, `.utf32`, or `.unicode` string encodings are specified. You could however indicate that you always want the BOM written (`.always`) or that is never written (`.never`).\n\nThe configuration values are set during initialization and can be passed to the `CSVWriter` instance through a structure or with a convenience closure syntax:\n\n```swift\nlet writer = CSVWriter(fileURL: ...) {\n    $0.delimiters.row = \"\\r\\n\"\n    $0.headers = [\"Name\", \"Age\", \"Pet\"]\n    $0.encoding = .utf8\n    $0.bomStrategy = .never\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVError\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nMany of `CodableCSV`'s imperative functions may throw errors due to invalid configuration values, invalid CSV input, file stream failures, etc. All these throwing operations exclusively throw `CSVError`s that can be easily caught with `do`-`catch` clause.\n\n```swift\ndo {\n    let writer = try CSVWriter()\n    for row in customData {\n        try writer.write(row: row)\n    }\n} catch let error {\n    print(error)\n}\n```\n\n`CSVError` adopts Swift Evolution's [SE-112 protocols](https://github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md) and `CustomDebugStringConvertible`. The error's properties provide rich commentary explaining what went wrong and indicate how to fix the problem.\n\n-   `type`: The error group category.\n-   `failureReason`: Explanation of what went wrong.\n-   `helpAnchor`: Advice on how to solve the problem.\n-   `errorUserInfo`: Arguments associated with the operation that threw the error.\n-   `underlyingError`: Optional underlying error, which provoked the operation to fail (most of the time is `nil`).\n-   `localizedDescription`: Returns a human readable string with all the information contained in the error.\n\n\u003cbr\u003eYou can get all the information by simply printing the error or calling the `localizedDescription` property on a properly casted `CSVError\u003cCSVReader\u003e` or `CSVError\u003cCSVWriter\u003e`.\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n## Declarative Decoder/Encoder\n\nThe encoders/decoders provided by this library let you use Swift's `Codable` declarative approach to encode/decode CSV data.\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVDecoder\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\n`CSVDecoder` transforms CSV data into a Swift type conforming to `Decodable`. The decoding process is very simple and it only requires creating a decoding instance and call its `decode` function passing the `Decodable` type and the input data.\n\n```swift\nlet decoder = CSVDecoder()\nlet result = try decoder.decode(CustomType.self, from: data)\n```\n\n`CSVDecoder` can decode CSVs represented as a `Data` blob, a `String`, an actual file in the file system, or an `InputStream` (e.g. `stdin`).\n\n```swift\nlet decoder = CSVDecoder { $0.bufferingStrategy = .sequential }\nlet content = try decoder.decode([Student].self, from: URL(\"~/Desktop/Student.csv\"))\n```\n\nIf you are dealing with a big CSV file, it is preferred to used direct file decoding, a `.sequential` or `.unrequested` buffering strategy, and set _presampling_ to false; since then memory usage is drastically reduced.\n\n### Decoder Configuration\n\nThe decoding process can be tweaked by specifying configuration values at initialization time. `CSVDecoder` accepts the same configuration values as `CSVReader` plus the following ones:\n\n-   `nilStrategy` (default: `.empty`) indicates how the `nil` _concept_ (absence of value) is represented on the CSV.\n\n-   `boolStrategy` (default: `.insensitive`) defines how strings are decoded to `Bool` values.\n\n-   `nonConformingFloatStrategy` (default `.throw`) specifies how to handle non-numbers (e.g. `NaN` and infinity).\n\n-   `decimalStrategy` (default `.locale`) indicates how strings are decoded to `Decimal` values.\n\n-   `dateStrategy` (default `.deferredToDate`) specify how strings are decoded to `Date` values.\n\n-   `dataStrategy` (default `.base64`) indicates how strings are decoded to `Data` values.\n\n-   `bufferingStrategy` (default `.keepAll`) controls the behavior of `KeyedDecodingContainer`s.\n\n    Selecting a buffering strategy affects the decoding performance and the amount of memory used during the decoding process. For more information check the README's [Tips using `Codable`](#Tips-using-codable) section and the [`Strategy.DecodingBuffer` definition](sources/declarative/decodable/DecoderConfiguration.swift).\n\nThe configuration values can be set during `CSVDecoder` initialization or at any point before the `decode` function is called.\n\n```swift\nlet decoder = CSVDecoder {\n    $0.encoding = .utf8\n    $0.delimiters.field = \"\\t\"\n    $0.headerStrategy = .firstLine\n    $0.bufferingStrategy = .keepAll\n    $0.decimalStrategy = .custom({ (decoder) in\n        let value = try Float(from: decoder)\n        return Decimal(value)\n    })\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVDecoder.Lazy\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nA CSV input can be decoded _on demand_ (i.e. row-by-row) with the decoder's `lazy(from:)` function.\n\n```swift\nlet decoder = CSVDecoder(configuration: config).lazy(from: fileURL)\nlet student1 = try decoder.decodeRow(Student.self)\nlet student2 = try decoder.decodeRow(Student.self)\n```\n\n`CSVDecoder.Lazy` conforms to Swift's [`Sequence` protocol](https://developer.apple.com/documentation/swift/sequence), letting you use functionality such as `map()`, `allSatisfy()`, etc. Please note, `CSVDecoder.Lazy` cannot be used for repeated access; It _consumes_ the input CSV.\n\n```swift\nlet decoder = CSVDecoder().lazy(from: fileData)\nlet students = try decoder.map { try $0.decode(Student.self) }\n```\n\nA nice benefit of using the _lazy_ operation, is that it lets you switch how a row is decoded at any point. For example:\n```swift\nlet decoder = CSVDecoder().lazy(from: fileString)\n// The first 100 rows are students.\nlet students = (  0..\u003c100).map { _ in try decoder.decode(Student.self) }\n// The second 100 rows are teachers.\nlet teachers = (100..\u003c110).map { _ in try decoder.decode(Teacher.self) }\n```\n\nSince `CSVDecoder.Lazy` exclusively provides sequential access; setting the buffering strategy to `.sequential` will reduce the decoder's memory usage.\n\n```swift\nlet decoder = CSVDecoder {\n    $0.headerStrategy = .firstLine\n    $0.bufferingStrategy = .sequential\n}.lazy(from: fileURL)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVEncoder\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\n`CSVEncoder` transforms Swift types conforming to `Encodable` into CSV data. The encoding process is very simple and it only requires creating an encoding instance and call its `encode` function passing the `Encodable` value.\n\n```swift\nlet encoder = CSVEncoder()\nlet data = try encoder.encode(value, into: Data.self)\n```\n\nThe `Encoder`'s `encode()` function creates a CSV file as a `Data` blob, a `String`, or an actual file in the file system.\n\n```swift\nlet encoder = CSVEncoder { $0.headers = [\"name\", \"age\", \"hasPet\"] }\ntry encoder.encode(value, into: URL(\"~/Desktop/Students.csv\"))\n```\n\nIf you are dealing with a big CSV content, it is preferred to use direct file encoding and a `.sequential` or `.assembled` buffering strategy, since then memory usage is drastically reduced.\n\n### Encoder Configuration\n\nThe encoding process can be tweaked by specifying configuration values. `CSVEncoder` accepts the same configuration values as `CSVWriter` plus the following ones:\n\n-   `nilStrategy` (default: `.empty`) indicates how the `nil` _concept_ (absence of value) is represented on the CSV.\n\n-   `boolStrategy` (default: `.deferredToString`) defines how Boolean values are encoded to `String` values.\n\n-   `nonConformingFloatStrategy` (default `.throw`) specifies how to handle non-numbers (i.e. `NaN` and infinity).\n\n-   `decimalStrategy` (default `.locale`) indicates how decimal numbers are encoded to `String` values.\n\n-   `dateStrategy` (default `.deferredToDate`) specify how dates are encoded to `String` values.\n\n-   `dataStrategy` (default `.base64`) indicates how data blobs are encoded to `String` values.\n\n-   `bufferingStrategy` (default `.keepAll`) controls the behavior of `KeyedEncodingContainer`s.\n\n    Selecting a buffering strategy directly affect the encoding performance and the amount of memory used during the process. For more information check this README's [Tips using `Codable`](#Tips-using-codable) section and the [`Strategy.EncodingBuffer` definition](sources/declarative/encodable/EncoderConfiguration.swift).\n\nThe configuration values can be set during `CSVEncoder` initialization or at any point before the `encode` function is called.\n\n```swift\nlet encoder = CSVEncoder {\n    $0.headers = [\"name\", \"age\", \"hasPet\"]\n    $0.delimiters = (field: \";\", row: \"\\r\\n\")\n    $0.dateStrategy = .iso8601\n    $0.bufferingStrategy = .sequential\n    $0.floatStrategy = .convert(positiveInfinity: \"∞\", negativeInfinity: \"-∞\", nan: \"≁\")\n    $0.dataStrategy = .custom({ (data, encoder) in\n        let string = customTransformation(data)\n        var container = try encoder.singleValueContainer()\n        try container.encode(string)\n    })\n}\n```\n\n\u003e The `.headers` configuration is required if you are using keyed encoding container.\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCSVEncoder.Lazy\u003c/code\u003e\u003c/summary\u003e\u003cp\u003e\n\nA series of codable types (representing CSV rows) can be encoded _on demand_ with the encoder's `lazy(into:)` function.\n\n```swift\nlet encoder = CSVEncoder().lazy(into: Data.self)\nfor student in students {\n    try encoder.encodeRow(student)\n}\nlet data = try encoder.endEncoding()\n```\n\nCall `endEncoding()` once there is no more values to be encoded. The function will return the encoded CSV.\n```swift\nlet encoder = CSVEncoder().lazy(into: String.self)\nstudents.forEach {\n    try encoder.encode($0)\n}\nlet string = try encoder.endEncoding()\n```\n\nA nice benefit of using the _lazy_ operation, is that it lets you switch how a row is encoded at any point. For example:\n```swift\nlet encoder = CSVEncoder(configuration: config).lazy(into: fileURL)\nstudents.forEach { try encoder.encode($0) }\nteachers.forEach { try encoder.encode($0) }\ntry encoder.endEncoding()\n```\n\nSince `CSVEncoder.Lazy` exclusively provides sequential encoding; setting the buffering strategy to `.sequential` will reduce the encoder's memory usage.\n\n```swift\nlet encoder = CSVEncoder {\n    $0.bufferingStrategy = .sequential\n}.lazy(into: String.self)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n### Tips using `Codable`\n\n`Codable` is fairly easy to use and most Swift standard library types already conform to it. However, sometimes it is tricky to get custom types to comply to `Codable` for specific functionality.\n\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003eBasic adoption.\u003c/summary\u003e\u003cp\u003e\n\nWhen a custom type conforms to `Codable`, the type is stating that it has the ability to decode itself from and encode itself to a external representation. Which representation depends on the decoder or encoder chosen. Foundation provides support for [JSON and Property Lists](https://developer.apple.com/documentation/foundation/archives_and_serialization) and the community provide many other formats, such as: [YAML](https://github.com/jpsim/Yams), [XML](https://github.com/MaxDesiatov/XMLCoder), [BSON](https://github.com/OpenKitten/BSON), and CSV (through this library).\n\nUsually a CSV represents a long list of _entities_. The following is a simple example representing a list of students.\n\n```swift\nlet string = \"\"\"\n    name,age,hasPet\n    John,22,true\n    Marine,23,false\n    Alta,24,true\n    \"\"\"\n```\n\nA _student_ can be represented as a structure:\n\n```swift\nstruct Student: Codable {\n    var name: String\n    var age: Int\n    var hasPet: Bool\n}\n```\n\nTo decode the list of students, create a decoder and call `decode` on it passing the CSV sample.\n\n```swift\nlet decoder = CSVDecoder { $0.headerStrategy = .firstLine }\nlet students = try decoder.decode([Student].self, from: string)\n```\n\nThe inverse process (from Swift to CSV) is very similar (and simple).\n\n```swift\nlet encoder = CSVEncoder { $0.headers = [\"name\", \"age\", \"hasPet\"] }\nlet newData = try encoder.encode(students)\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eSpecific behavior for CSV data.\u003c/summary\u003e\u003cp\u003e\n\nWhen encoding/decoding CSV data, it is important to keep several points in mind:\n\n\u003c/p\u003e\n\u003cul\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003ccode\u003eCodable\u003c/code\u003e's automatic synthesis requires CSV files with a headers row.\u003c/summary\u003e\u003cp\u003e\n\n`Codable` is able to synthesize `init(from:)` and `encode(to:)` for your custom types when all its members/properties conform to `Codable`. This automatic synthesis create a hidden `CodingKeys` enumeration containing all your property names.\n\nDuring decoding, `CSVDecoder` tries to match the enumeration string values with a field position within a row. For this to work the CSV data must contain a _headers row_ with the property names. If your CSV doesn't contain a _headers row_, you can specify coding keys with integer values representing the field index.\n\n```swift\nstruct Student: Codable {\n    var name: String\n    var age: Int\n    var hasPet: Bool\n\n    private enum CodingKeys: Int, CodingKey {\n        case name = 0\n        case age = 1\n        case hasPet = 2\n    }\n}\n```\n\n\u003e Using integer coding keys has the added benefit of better encoder/decoder performance. By explicitly indicating the field index, you let the decoder skip the functionality of matching coding keys string values to headers.\n\n\u003c/p\u003e\u003c/details\u003e\n\u003cdetails\u003e\u003csummary\u003eA CSV is a long list of rows/records.\u003c/summary\u003e\u003cp\u003e\n\nCSV formatted data is commonly used with flat hierarchies (e.g. a list of students, a list of car models, etc.). Nested structures, such as the ones found in JSON files, are not supported by default in CSV implementations (e.g. a list of users, where each user has a list of services she uses, and each service has a list of the user's configuration values).\n\nYou can support complex structures in CSV, but you would have to flatten the hierarchy in a single model or build a custom encoding/decoding process. This process would make sure there is always a maximum of two keyed/unkeyed containers.\n\nAs an example, we can create a nested structure for a school with students who own pets.\n\n```swift\nstruct School: Codable {\n    let students: [Student]\n}\n\nstruct Student: Codable {\n    var name: String\n    var age: Int\n    var pet: Pet\n}\n\nstruct Pet: Codable {\n    var nickname: String\n    var gender: Gender\n\n    enum Gender: Codable {\n        case male, female\n    }\n}\n```\n\nBy default the previous example wouldn't work. If you want to keep the nested structure, you need to overwrite the custom `init(from:)` implementation (to support `Decodable`).\n\n```swift\nextension School {\n    init(from decoder: Decoder) throws {\n        var container = try decoder.unkeyedContainer()\n        while !container.isAtEnd {\n            self.student.append(try container.decode(Student.self))\n        }\n    }\n}\n\nextension Student {\n    init(from decoder: Decoder) throws {\n        var container = try decoder.container(keyedBy: CustomKeys.self)\n        self.name = try container.decode(String.self, forKey: .name)\n        self.age = try container.decode(Int.self, forKey: .age)\n        self.pet = try decoder.singleValueContainer.decode(Pet.self)\n    }\n}\n\nextension Pet {\n    init(from decoder: Decoder) throws {\n        var container = try decoder.container(keyedBy: CustomKeys.self)\n        self.nickname = try container.decode(String.self, forKey: .nickname)\n        self.gender = try container.decode(Gender.self, forKey: .gender)\n    }\n}\n\nextension Pet.Gender {\n    init(from decoder: Decoder) throws {\n        var container = try decoder.singleValueContainer()\n        self = try container.decode(Int.self) == 1 ? .male : .female\n    }\n}\n\nprivate CustomKeys: Int, CodingKey {\n    case name = 0\n    case age = 1\n    case nickname = 2\n    case gender = 3\n}\n```\n\nYou could have avoided building the initializers overhead by defining a flat structure such as:\n\n```swift\nstruct Student: Codable {\n    var name: String\n    var age: Int\n    var nickname: String\n    var gender: Gender\n\n    enum Gender: Int, Codable {\n        case male = 1\n        case female = 2\n    }\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eEncoding/decoding strategies.\u003c/summary\u003e\u003cp\u003e\n\n[SE167](https://github.com/apple/swift-evolution/blob/master/proposals/0167-swift-encoders.md) proposal introduced to Foundation JSON and PLIST encoders/decoders. This proposal also featured encoding/decoding strategies as a new way to configure the encoding/decoding process. `CodableCSV` continues this _tradition_ and mirrors such strategies including some new ones specific to the CSV file format.\n\nTo configure the encoding/decoding process, you need to set the configuration values of the `CSVEncoder`/`CSVDecoder` before calling the `encode()`/`decode()` functions. There are two ways to set configuration values:\n\n-   At initialization time, passing the `Configuration` structure to the initializer.\n\n    ```swift\n    var config = CSVDecoder.Configuration()\n    config.nilStrategy = .empty\n    config.decimalStrategy = .locale(.current)\n    config.dataStrategy = .base64\n    config.bufferingStrategy = .sequential\n    config.trimStrategy = .whitespaces\n    config.encoding = .utf16\n    config.delimiters.row = \"\\r\\n\"\n\n    let decoder = CSVDecoder(configuration: config)\n    ```\n\n    Alternatively, there are convenience initializers accepting a closure with a `inout Configuration` value.\n\n    ```swift\n    let decoder = CSVDecoder {\n        $0.nilStrategy = .empty\n        $0.decimalStrategy = .locale(.current)\n        // and so on and so forth\n    }\n    ```\n\n-   `CSVEncoder` and `CSVDecoder` implement `@dynamicMemberLookup` exclusively for their configuration values. Therefore you can set configuration values after initialization or after a encoding/decoding process has been performed.\n\n    ```swift\n    let decoder = CSVDecoder()\n    decoder.bufferingStrategy = .sequential\n    decoder.decode([Student].self, from: url1)\n\n    decoder.bufferingStrategy = .keepAll\n    decoder.decode([Pets].self, from: url2)\n    ```\n\nThe strategies labeled with `.custom` let you insert behavior into the encoding/decoding process without forcing you to manually conform to `init(from:)` and `encode(to:)`. When set, they will reference the targeted type for the whole process. For example, if you want to encode a CSV file where empty fields are marked with the word `null` (for some reason). You could do the following:\n\n```swift\nlet decoder = CSVDecoder()\ndecoder.nilStrategy = .custom({ (encoder) in\n    var container = encoder.singleValueContainer()\n    try container.encode(\"null\")\n})\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003eType-safe headers row.\u003c/summary\u003e\u003cp\u003e\n\nYou can generate type-safe name headers using Swift introspection tools (i.e. `Mirror`) or explicitly defining the `CodingKey` enum with `String` raw value conforming to `CaseIterable`.\n\n```swift\nstruct Student {\n    var name: String\n    var age: Int\n    var hasPet: Bool\n\n    enum CodingKeys: String, CodingKey, CaseIterable {\n        case name, age, hasPet\n    }\n}\n```\n\nThen configure your encoder with explicit headers.\n\n```swift\nlet encoder = CSVEncoder {\n    $0.headers = Student.CodingKeys.allCases.map { $0.rawValue }\n}\n```\n\n\u003c/p\u003e\u003c/details\u003e\n\n\u003cdetails\u003e\u003csummary\u003ePerformance advices.\u003c/summary\u003e\u003cp\u003e\n\n#warning(\"TODO:\")\n\n\u003c/p\u003e\u003c/details\u003e\n\u003c/ul\u003e\n\n# Roadmap\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"docs/assets/Roadmap.svg\" alt=\"Roadmap\"/\u003e\n\u003c/p\u003e\n\nThe library has been heavily documented and any contribution is welcome. Check the small [How to contribute](docs/CONTRIBUTING.md) document or take a look at the [Github projects](https://github.com/dehesa/CodableCSV/projects) for a more in-depth roadmap.\n\n### Community\n\nIf `CodableCSV` is not of your liking, the Swift community offers other CSV solutions:\n\n-   [CSV.swift](https://github.com/yaslab/CSV.swift) contains an imperative CSV reader/writer and a _lazy_ row decoder and adheres to the [RFC4180](https://tools.ietf.org/html/rfc4180) standard.\n-   [SwiftCSV](https://github.com/swiftcsv/SwiftCSV) is a well-tested parse-only library which loads the whole CSV in memory (not intended for large files).\n-   [CSwiftV](https://github.com/Daniel1of1/CSwiftV) is a parse-only library which loads the CSV in memory and parses it in a single go (no imperative reading).\n-   [CSVImporter](https://github.com/Flinesoft/CSVImporter) is an asynchronous parse-only library with support for big CSV files (incremental loading).\n-   [SwiftCSVExport](https://github.com/vigneshuvi/SwiftCSVExport) reads/writes CSV imperatively with Objective-C support.\n-   [swift-csv](https://github.com/brutella/swift-csv) offers an imperative CSV reader/writer based on Foundation's streams.\n\nThere are many good tools outside the Swift community. Since writing them all would be a hard task, I will just point you to the great [AwesomeCSV](https://github.com/secretGeek/awesomeCSV) github repo. There are a lot of treasures to be found there.\n","funding_links":[],"categories":["Parsing","Libs","Swift","Data Management [🔝](#readme)"],"sub_categories":["CSV","Data Management"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehesa%2FCodableCSV","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdehesa%2FCodableCSV","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdehesa%2FCodableCSV/lists"}