You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A `CSVReadder`reads CSV data and lets you access each CSV row as an array of `String`s:
38
+
A `CSVReadder`parses CSV data from an input and returns you each CSV row as an array of strings.
35
39
36
-
-row-by-row.
40
+
-Row-by-row parsing.
37
41
38
42
```swift
39
-
let reader =tryCSVReader(fileURL: ...)
43
+
let reader =tryCSVReader(data: ...)
40
44
whilelet row =try reader.parseRow() {
41
45
// Do something with the row: [String]
42
46
}
43
47
```
44
48
45
-
-with `Sequence` syntax.
49
+
- `Sequence` syntax parsing.
46
50
47
51
```swift
48
-
let reader =tryCSVReader(data: ...)
52
+
let reader =tryCSVReader(fileURL: ...)
49
53
for row in reader {
50
54
// Do something with the row: [String]
51
55
}
52
56
```
53
57
54
58
Please note the `Sequence` syntax (i.e. `IteratorProtocol`) doesn't throw errors; therefore if the CSV data is invalid, the previous code will crash your program. If you don't control the origin of the CSV data, use the `parseRow()` function instead.
55
59
56
-
### Reader Inputs
57
-
58
-
A `CSVReader` are able to read the following input sources:
59
-
60
-
- `String`.
61
-
62
-
```swift
63
-
let reader =tryCSVReader(string: "A,B,C\n D,E,F\n G,H,I\n")
64
-
```
65
-
66
-
- `Data`.
67
-
68
-
```swift
69
-
let reader =tryCSVReader(data: Data(...))
70
-
```
71
-
72
-
- A file `URL`.
60
+
- Whole input parsing.
73
61
74
62
```swift
75
-
let reader =tryCSVReader(fileURL: URL(...))
63
+
let file =try CSVReader.parse(string: ..., configuration: ...)
64
+
// file is of type: (headers: [String], rows: [[String]])
76
65
```
77
66
78
-
During initialization, an optional `Configuration` structure may be provided. These configuration values lets you tweak the parsing process.
79
-
80
-
```swift
81
-
let reader =tryCSVReader(data: ..., configuration: ...)
82
-
```
83
-
84
67
### Reader Configuration
85
68
86
-
`CSVReader` accept the following configuration properties:
69
+
`CSVReader` accepts the following configuration properties:
87
70
88
71
-`encoding` (default: `nil`) specify the CSV file encoding.
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.
99
82
100
-
-`trimStrategy` (default: `.none`) trims the given characters at the beginning and end ofeach parsed row and field.
83
+
-`trimStrategy` (default: empty set) trims the given characters at the beginning and end ofeach parsed field.
84
+
85
+
The trim characters are applied for the escaped and unescaped fields.
101
86
102
87
-`presample` (default: `false`) indicates whether the CSV data should be completely loaded into memory before parsing begins.
103
88
104
89
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`.
105
90
106
-
There is a convenience initializer letting you specify configuration values within a closure during initialization:
91
+
The configuration values are only set during initialization and can be passed to the `CSVReader` instance through a structure or with a convenience closure syntax:
`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.
117
+
129
118
```swift
130
119
let decoder =CSVDecoder()
131
-
decoder.delimiters= (.comma, .lineFeed)
132
120
let result =try decoder.decode(CustomType.self, from: data)
133
121
```
134
122
135
-
</details>
123
+
### Decoder Configuration
124
+
125
+
The decoding process can be tweaked by specifying configuration values at initialization time. `CSVDecoder` accepts the [same configuration values as `CSVReader`](#Reader-Configuration) plus the following ones:
126
+
127
+
-`floatStrategy` (default: `.throw`) defines how to deal with non-conforming floating-point numbers (such as `NaN`, or `+Infinity`).
128
+
129
+
-`decimalStrategy` (default: `.locale(nil)`) indicates how decimal numbers are decoded (from `String` to `Decimal` value).
130
+
131
+
-`dataStrategy` (default: `.deferredToDate`) specify the strategy to use when decoding dates.
132
+
133
+
-`dataStrategy` (default: `.base64`) specify the strategy to use when decoding data blobs.
134
+
135
+
-`bufferingStrategy` (default: `.keepAll`) tells the decoder how to cache previously decoded CSV rows.
136
+
137
+
Caching rows allow random access through `KeyedDecodingContainer`s.
138
+
139
+
The configuration values can be set during `CSVDecoder` initialization or at any point before the `decode` function is called.
`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 very specific functionality. That is why I am leaving here some tips and advices concerning its usage:
166
+
167
+
<details><summary>Basic adoption.</summary><p>
168
+
169
+
`Codable` is just a type alias for `Decodable` and `Encodable`. When 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), but 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).
170
+
171
+
Lets see a regular CSV encoding/decoding usage through `Codable`'s interface. Let's suppose we have a list of students formatted in a CSV file:
172
+
173
+
```swift
174
+
let data ="""
175
+
name,age,hasPet
176
+
John,22,true
177
+
Marine,23,false
178
+
Alta,24,true
179
+
"""
180
+
```
181
+
182
+
In Swift, a _student_ has the following structure:
183
+
184
+
```swift
185
+
struct Student:Codable {
186
+
var name: String
187
+
var age: Int
188
+
var hasPet: Bool
189
+
}
190
+
```
191
+
192
+
To decode the CSV data, we just need to create a decoder and call `decode` on it passing the given data.
193
+
194
+
```swift
195
+
let decoder =CSVDecoder { $0.headerStrategy= .firstLine }
196
+
let students =try decoder.decode([Student], from: data)
197
+
```
198
+
199
+
The inverse process (from Swift to CSV) is very similar (and simple).
200
+
201
+
```swift
202
+
let encoder =CSVEncoder { $0.headerStraty= .firstLine }
203
+
let newData =try encoder.encode(students)
204
+
```
205
+
206
+
</p></details>
207
+
208
+
<details><summary>Specific behavior for CSV data.</summary><p>
209
+
210
+
When encoding/decoding CSV data, it is important to keep several points in mind:
211
+
212
+
</p>
213
+
<ul>
214
+
<details><summary>Default behavior requires a CSV with a headers row.</summary><p>
215
+
216
+
The defaultbehavior (i.e. not including `init(from:)` and `encode(to:)`) rely on the existance of the synthesized `CodingKey`s whose `stringValue`s are the property names. For these properties to match any CSV field, the CSV data must contain a _headers row_ at the very beginning. If your CSV doesn't contain a _headers row_, you can specify coding keys with integer values representing the field index.
217
+
218
+
```swift
219
+
struct Student: Codable {
220
+
var name: String
221
+
var age: Int
222
+
var hasPet: Bool
223
+
224
+
private CodingKeys:Int, CodingKey {
225
+
case name =0
226
+
case age =1
227
+
case hasPet =2
228
+
}
229
+
}
230
+
```
231
+
232
+
</p></details>
233
+
<details><summary>A CSV is a long list of records/rows.</summary><p>
234
+
235
+
CSV 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 defaultin CSV implementations (e.g. a list of users, whereeach user has a list of services she uses, and each service has a list of the user's configuration values).
236
+
237
+
You can definitely 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.
238
+
239
+
As an example, we can create a nested structure for a school with students who own pets.
240
+
241
+
```swift
242
+
struct School: Codable {
243
+
let students: [Student]
244
+
}
245
+
246
+
struct Student:Codable {
247
+
var name: String
248
+
var age: Int
249
+
var pet: Pet
250
+
}
251
+
252
+
struct Pet:Codable {
253
+
var nickname: String
254
+
var gender: Gender
255
+
256
+
enumGender: Codable {
257
+
casemale, female
258
+
}
259
+
}
260
+
```
261
+
262
+
By 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`).
/// - parameter unusedBytes: The input data bytes that have been read, but are not part from the BOM.
67
68
/// - parameter dataFetcher: Closure retrieving the input data up the the maximum supported by the given mutable buffer pointer. The closure returns the number of bytes actually read from the input data.
68
69
/// - parameter buffer: The buffer where the input data bytes will be placed.
0 commit comments