Provides implementations of Encoder and Decoder, which I created for personal use.
Be aware, that this is a very early release, which "works for me" for the moment.
Provides CSVEncoder and CSVDecoder following the example set by JSONEncoder and JSONDecoder. They allow encoding and decoding of CSV-files.
- Instantiate a
CSVEncoderorCSVDecoder. - Optionally configure options via methods on .configuration (currently only supported by
CSVDecoder.) - Use methods of
Encoder/Decoder
The CSV-file:
"name";"age";"doesLikeBurgers"
"John Doe";"23";"0"
"Jane Doe";"42";"1"
The code:
struct Person: Decodable {
let name: String
let age: Int
let doesLikeBurgers: Bool
}
let decoder = CSVDecoder()
decoder.configuration
.decodeBoolean(trueValue: "1", falseValue: "0")
do {
let listOfPeople = try decoder.decode([Person].self, fromFile: URL(fileURLWithPath: "listofpeople.csv"))
print(listOfPeople)
} catch let error {
print(error)
}Output:
[Person(name: "John Doe", age: 23, doesLikeBurgers: false), Person(name: "Jane Doe", age: 42, doesLikeBurgers: true)]
Configuration not yet supported.
The code:
struct Person: Encodable {
let name: String
let age: Int
let doesLikeBurgers: Bool
}
let listOfPeople = [
Person(name: "John Doe", age: 23, doesLikeBurgers: false),
Person(name: "Jane Doe", age: 42, doesLikeBurgers: true)
]
do {
let csvData = try CSVEncoder().encode(listOfPeople)
print(String(data: csvData, encoding: .utf8)!)
} catch let error {
print(error)
}Output:
"name","age","doesLikeBurgers"
"John Doe","23","false"
"Jane Doe","42","true"
As CSV-files are tables, some limitations with regards to data-structures exist. To have simple support for Codable, the optimal model is a flat struct of simple types, for example:
struct Demo: Codable {
let aBool: Bool
let aString: String
let aDouble: Double
let anInt: Int
}Nested structures, like properties of non-primitive types or arrays of anything are not supported, for example:
struct Demo: Codable {
let anArray: [String]
let nestedType: Nested
struct Nested: Codable {
let aString: String
}
}In decoding, though, custom decoders can be configured, for example:
struct Demo: Decodable {
let custom: Custom
struct Custom: Decodable {
let first: Int
let second: Int
}
}
decoder.configuration
.addCustomDecoder {
let values = $0.split(separator: "|")
Custom(first: String(values[0]), second: String(values[1]))
}With a CSV-file like:
"custom"
"1|2"
"3|4"
"5|6"
I created LogEncoder and LogDecoder to help me with understanding, how Encoder and Decoder works. I didn't find a lot of useful information. Most of it was "do this, do that", but no explanation of what is happening when and why.
LogEncoder will "encode" by writing output using print(), describing the current coding-path and function. LogDecoder decodes random-values and also prints the steps taken. Both helped me a lot to understand what is actually happening, especially weird cases.
So, if you have a use-case, a certain model that needs encoding into a format, you want to write an encoder for, you could do this:
struct Demo: Codable {
let aBool: Bool
let anOptional: Int?
let aNested: Nested
let anArray: [String]
struct Nested: Codable {
let anotherBool: Bool
let anotherString: String
}
}
let demo = Demo(
aBool: true,
anOptional: nil,
aNested: Nested(
anotherbool: false,
anotherString: "Wahtuuup?"
),
anArray: ["Hello", "World", "!"]
)
do {
try LogEncoder().encode(demo)
} catch let error {
print(error)
}Output:
/ _LogEncoder.container(keyedBy:)
/ _LogKeyedEncodingContainer<CodingKeys>.encode(_:forKey:) value(Bool)=true key=CodingKeys(stringValue: "aBool", intValue: nil)
/ _LogKeyedEncodingContainer<CodingKeys>.encode(_:forKey:) value<T>(Nested)=Nested(anotherBool: false, anotherString: "Wahtuuup?") key=CodingKeys(stringValue: "aNested", intValue: nil)
/ _LogEncoder.codingPath = /aNested
/aNested _LogEncoder.container(keyedBy:)
/aNested _LogKeyedEncodingContainer<CodingKeys>.encode(_:forKey:) value(Bool)=false key=CodingKeys(stringValue: "anotherBool", intValue: nil)
/aNested _LogKeyedEncodingContainer<CodingKeys>.encode(_:forKey:) value(String)="Wahtuuup?" key=CodingKeys(stringValue: "anotherString", intValue: nil)
/aNested _LogEncoder.codingPath = /
/ _LogKeyedEncodingContainer<CodingKeys>.encode(_:forKey:) value<T>(Array<String>)=["Hello", "World", "!"] key=CodingKeys(stringValue: "anArray", intValue: nil)
/ _LogEncoder.codingPath = /anArray
/anArray _LogEncoder.unkeyedContainer()
/anArray _LogUnkeyedEncodingContainer.encode(_:) count=0 value<T>(String)=Hello
/anArray _LogEncoder.codingPath = /anArray/0
/anArray/0 _LogEncoder.singleValueContainer()
/anArray/0 _LogSingleValueEncodingContainer.encode(_:) value(String)="Hello"
/anArray/0 _LogUnkeyedEncodingContainer.count = 1
/anArray/0 _LogEncoder.codingPath = /anArray
/anArray _LogUnkeyedEncodingContainer.encode(_:) count=1 value<T>(String)=World
/anArray _LogEncoder.codingPath = /anArray/1
/anArray/1 _LogEncoder.singleValueContainer()
/anArray/1 _LogSingleValueEncodingContainer.encode(_:) value(String)="World"
/anArray/1 _LogUnkeyedEncodingContainer.count = 2
/anArray/1 _LogEncoder.codingPath = /anArray
/anArray _LogUnkeyedEncodingContainer.encode(_:) count=2 value<T>(String)=!
/anArray _LogEncoder.codingPath = /anArray/2
/anArray/2 _LogEncoder.singleValueContainer()
/anArray/2 _LogSingleValueEncodingContainer.encode(_:) value(String)="!"
/anArray/2 _LogUnkeyedEncodingContainer.count = 3
/anArray/2 _LogEncoder.codingPath = /anArray
/anArray _LogEncoder.codingPath = /