Skip to content

Commit f9355b2

Browse files
committed
SingleValueEncodingContainer & field decoding optimization added
1 parent ef686db commit f9355b2

File tree

8 files changed

+184
-45
lines changed

8 files changed

+184
-45
lines changed

sources/Codable/Decodable/Containers/DecodingValue.swift

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,11 @@ extension ShadowDecoder.SingleValueContainer {
156156
}
157157
}
158158

159-
extension ShadowDecoder.SingleValueContainer {
159+
internal extension ShadowDecoder.SingleValueContainer {
160160
/// Decodes a single value of the given type.
161161
/// - parameter type: The type to decode as.
162162
/// - returns: A value of the requested type.
163-
internal func decode(_ type: Date.Type) throws -> Date {
163+
func decode(_ type: Date.Type) throws -> Date {
164164
switch self.decoder.source.configuration.dateStrategy {
165165
case .deferredToDate:
166166
return try Date(from: self.decoder)
@@ -169,7 +169,7 @@ extension ShadowDecoder.SingleValueContainer {
169169
return Foundation.Date(timeIntervalSince1970: number)
170170
case .millisecondsSince1970:
171171
let number = try self.decode(Double.self)
172-
return Foundation.Date(timeIntervalSince1970: number / 1000.0)
172+
return Foundation.Date(timeIntervalSince1970: number / 1_000)
173173
case .iso8601:
174174
let string = try self.decode(String.self)
175175
return try DateFormatter.iso8601.date(from: string) ?! DecodingError.invalidDateISO(string: string, codingPath: self.codingPath)
@@ -184,7 +184,7 @@ extension ShadowDecoder.SingleValueContainer {
184184
/// Decodes a single value of the given type.
185185
/// - parameter type: The type to decode as.
186186
/// - returns: A value of the requested type.
187-
internal func decode(_ type: Data.Type) throws -> Data {
187+
func decode(_ type: Data.Type) throws -> Data {
188188
switch self.decoder.source.configuration.dataStrategy {
189189
case .deferredToData:
190190
return try Data(from: self.decoder)
@@ -199,7 +199,7 @@ extension ShadowDecoder.SingleValueContainer {
199199
/// Decodes a single value of the given type.
200200
/// - parameter type: The type to decode as.
201201
/// - returns: A value of the requested type.
202-
internal func decode(_ type: Decimal.Type) throws -> Decimal {
202+
func decode(_ type: Decimal.Type) throws -> Decimal {
203203
switch self.decoder.source.configuration.decimalStrategy {
204204
case .locale(let locale):
205205
let string = try self.decode(String.self)
@@ -212,16 +212,16 @@ extension ShadowDecoder.SingleValueContainer {
212212
/// Decodes a single value of the given type.
213213
/// - parameter type: The type to decode as.
214214
/// - returns: A value of the requested type.
215-
internal func decode(_ type: URL.Type) throws -> URL {
215+
func decode(_ type: URL.Type) throws -> URL {
216216
try self.lowlevelDecode { URL(string: $0) }
217217
}
218218
}
219219

220220
// MARK: -
221221

222-
extension ShadowDecoder.SingleValueContainer {
222+
private extension ShadowDecoder.SingleValueContainer {
223223
/// CSV keyed container focus (i.e. where the container is able to operate on).
224-
private enum Focus {
224+
enum Focus {
225225
/// The container represents the whole CSV file and each decoding operation outputs a row/record.
226226
case file
227227
/// The container represents a CSV row and each decoding operation outputs a field.
@@ -232,18 +232,19 @@ extension ShadowDecoder.SingleValueContainer {
232232

233233
/// Decodes the `String` value under the receiving single value container's `focus` and then tries to transform it in the requested type.
234234
/// - parameter transform: Closure transforming the decoded `String` value into the required type. If it fails, the closure returns `nil`.
235-
private func lowlevelDecode<T>(transform: (String) -> T?) throws -> T {
235+
func lowlevelDecode<T>(transform: (String) -> T?) throws -> T {
236+
let source = self.decoder.source
237+
236238
switch self.focus {
237239
case .field(let rowIndex, let fieldIndex):
238-
let string = try self.decoder.source.field(at: rowIndex, fieldIndex)
240+
let string = try source.field(at: rowIndex, fieldIndex)
239241
return try transform(string) ?! DecodingError.invalid(type: T.self, string: string, codingPath: self.codingPath)
240242
case .row(let rowIndex):
241-
// Values are only allowed to be decoded directly from a single value container in "row level" if the CSV rows have a single column.
242-
guard self.decoder.source.numFields == 1 else { throw DecodingError.invalidNestedRequired(codingPath: self.codingPath) }
243-
let string = try self.decoder.source.field(at: rowIndex, 0)
243+
// Values are only allowed to be decoded directly from a single value container in "row level" if the CSV has single column rows.
244+
guard source.numFields == 1 else { throw DecodingError.invalidNestedRequired(codingPath: self.codingPath) }
245+
let string = try source.field(at: rowIndex, 0)
244246
return try transform(string) ?! DecodingError.invalid(type: T.self, string: string, codingPath: self.codingPath + [DecodingKey(0)])
245247
case .file:
246-
let source = self.decoder.source
247248
// Values are only allowed to be decoded directly from a single value container in "file level" if the CSV file has a single row with a single column.
248249
if source.isRowAtEnd(index: 1), source.numFields == 1 {
249250
let string = try self.decoder.source.field(at: 0, 0)

sources/Codable/Decodable/Shadow/DecodingSource.swift

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -110,33 +110,26 @@ extension ShadowDecoder.Source {
110110
/// Returns the field value in the given `rowIndex` row at the given `fieldIndex` position.
111111
func field(at rowIndex: Int, _ fieldIndex: Int) throws -> String {
112112
var nextIndex = self.reader.rowIndex
113-
/// If the row has been parsed previously, retrieve it from the buffer.
113+
// 1. If the row has been parsed previously, retrieve it from the buffer.
114114
guard rowIndex >= nextIndex else {
115115
guard let row = self.buffer.retrieve(at: rowIndex) else {
116116
throw DecodingError.expiredCache(rowIndex: rowIndex, fieldIndex: fieldIndex)
117117
}
118118
return row[fieldIndex]
119119
}
120-
121-
var result: [String]? = nil
122-
var counter = rowIndex - (nextIndex - 1)
123-
124-
while counter > 0 {
125-
guard let row = try self.reader.parseRow() else {
126-
throw DecodingError.rowOutOfBounds(rowIndex: rowIndex, rowCount: nextIndex)
127-
}
128-
self.buffer.store(row, at: nextIndex)
120+
// 2. If the row hasn't been parsed yet, parse rows and store them in the row buffer, till the targeted row is reached.
121+
var result: [String]
122+
repeat {
123+
result = try self.reader.parseRow() ?! DecodingError.rowOutOfBounds(rowIndex: rowIndex, rowCount: nextIndex)
124+
self.buffer.store(result, at: nextIndex)
129125
nextIndex += 1
130-
counter -= 1
131-
result = row
132-
}
133-
134-
guard let row = result else { fatalError() }
135-
let numFields = row.count
126+
} while rowIndex > nextIndex
127+
// 3. Check that the requested fields is not out of bounds.
128+
let numFields = result.count
136129
guard numFields > fieldIndex else {
137130
throw DecodingError.fieldOutOfBounds(rowIndex: rowIndex, fieldIndex: fieldIndex, fieldCount: numFields)
138131
}
139-
return row[fieldIndex]
132+
return result[fieldIndex]
140133
}
141134
}
142135

sources/Codable/Encodable/Containers/EncodingKeyed.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@ extension ShadowEncoder {
55
struct KeyedContainer<Key>: KeyedEncodingContainerProtocol where Key:CodingKey {
66
/// The representation of the encoding process point-in-time.
77
private let encoder: ShadowEncoder
8+
/// The focus for this container.
9+
private let focus: Focus
810

9-
///
11+
/// Fast initializer that doesn't perform any checks on the coding path (assuming it is valid).
12+
/// - parameter encoder: The `Encoder` instance in charge of encoding CSV data.
13+
/// - parameter rowIndex: The CSV row targeted for encoding.
14+
init(unsafeEncoder encoder: ShadowEncoder, rowIndex: Int) {
15+
self.encoder = encoder
16+
self.focus = .row(rowIndex)
17+
}
18+
19+
/// Creates a unkeyed container only if the passed encoder's coding path is valid.
20+
/// - parameter encoder: The `Encoder` instance in charge of encoding CSV data.
1021
init(encoder: ShadowEncoder) {
1122
fatalError()
1223
}
@@ -166,3 +177,15 @@ extension ShadowEncoder.KeyedContainer {
166177
fatalError()
167178
}
168179
}
180+
181+
// MARK: -
182+
183+
extension ShadowEncoder.KeyedContainer {
184+
/// CSV unkeyed container focus (i.e. where the container is able to operate on).
185+
private enum Focus {
186+
/// The container represents the whole CSV file and each encoding operation writes a row/record.
187+
case file
188+
/// The container represents a CSV row and each encoding operation outputs a field.
189+
case row(Int)
190+
}
191+
}

sources/Codable/Encodable/Containers/EncodingUnkeyed.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
extension ShadowEncoder {
2+
/// Unkeyed container for the CSV shadow encoder.
23
///
4+
/// This contaienr lets you sequentially write CSV rows or specific fields within a single rows.
35
struct UnkeyedContainer: UnkeyedEncodingContainer {
46
/// The representation of the encoding process point-in-time.
57
private let encoder: ShadowEncoder
8+
/// The focus for this container.
9+
private let focus: Focus
610

11+
/// Fast initializer that doesn't perform any checks on the coding path (assuming it is valid).
12+
/// - parameter encoder: The `Encoder` instance in charge of encoding CSV data.
13+
/// - parameter rowIndex: The CSV row targeted for encoding.
14+
init(unsafeEncoder encoder: ShadowEncoder, rowIndex: Int) {
15+
self.encoder = encoder
16+
self.focus = .row(rowIndex)
17+
}
18+
19+
/// Creates a unkeyed container only if the passed encoder's coding path is valid.
20+
/// - parameter encoder: The `Encoder` instance in charge of encoding CSV data.
721
init(encoder: ShadowEncoder) {
822
fatalError()
923
}
@@ -163,3 +177,15 @@ extension ShadowEncoder.UnkeyedContainer {
163177
fatalError()
164178
}
165179
}
180+
181+
// MARK: -
182+
183+
extension ShadowEncoder.UnkeyedContainer {
184+
/// CSV unkeyed container focus (i.e. where the container is able to operate on).
185+
private enum Focus {
186+
/// The container represents the whole CSV file and each encoding operation writes a row/record.
187+
case file
188+
/// The container represents a CSV row and each encoding operation outputs a field.
189+
case row(Int)
190+
}
191+
}

sources/Codable/Encodable/Containers/EncodingValue.swift

Lines changed: 87 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import Foundation
2+
13
extension ShadowEncoder {
2-
///
4+
/// Single value container for the CSV shadow encoder.
35
struct SingleValueContainer {
46
/// The representation of the encoding process point-in-time.
57
private let encoder: ShadowEncoder
@@ -18,6 +20,7 @@ extension ShadowEncoder {
1820
/// Creates a single value container only if the passed encoder's coding path is valid.
1921
/// - parameter encoder: The `Encoder` instance in charge of encoding CSV data.
2022
init(encoder: ShadowEncoder) {
23+
#warning("TODO")
2124
fatalError()
2225
}
2326

@@ -29,7 +32,7 @@ extension ShadowEncoder {
2932

3033
extension ShadowEncoder.SingleValueContainer: SingleValueEncodingContainer {
3134
mutating func encode(_ value: String) throws {
32-
try self.lowlevelEncoding { $0 }
35+
try self.lowlevelEncoding { value }
3336
}
3437

3538
mutating func encodeNil() throws {
@@ -84,8 +87,7 @@ extension ShadowEncoder.SingleValueContainer: SingleValueEncodingContainer {
8487
let strategy = self.encoder.sink.configuration.floatStrategy
8588
try self.lowlevelEncoding {
8689
switch strategy {
87-
case .throw:
88-
throw DecodingError.invalidFloatingPoint(value, codingPath: self.codingPath)
90+
case .throw: throw DecodingError.invalidFloatingPoint(value, codingPath: self.codingPath)
8991
case .convert(let positiveInfinity, let negativeInfinity, let nan):
9092
if value.isNaN {
9193
return nan
@@ -100,8 +102,7 @@ extension ShadowEncoder.SingleValueContainer: SingleValueEncodingContainer {
100102
let strategy = self.encoder.sink.configuration.floatStrategy
101103
try self.lowlevelEncoding {
102104
switch strategy {
103-
case .throw:
104-
throw DecodingError.invalidFloatingPoint(value, codingPath: self.codingPath)
105+
case .throw: throw DecodingError.invalidFloatingPoint(value, codingPath: self.codingPath)
105106
case .convert(let positiveInfinity, let negativeInfinity, let nan):
106107
if value.isNaN {
107108
return nan
@@ -112,11 +113,74 @@ extension ShadowEncoder.SingleValueContainer: SingleValueEncodingContainer {
112113
}
113114
}
114115

115-
mutating func encode<T>(_ value: T) throws where T : Encodable {
116-
fatalError()
116+
mutating func encode<T>(_ value: T) throws where T:Encodable {
117+
switch value {
118+
case let date as Date: try self.encode(date)
119+
case let data as Data: try self.encode(data)
120+
case let num as Decimal: try self.encode(num)
121+
case let url as URL: try self.encode(url)
122+
default: try value.encode(to: self.encoder)
123+
}
117124
}
118125
}
119126

127+
internal extension ShadowEncoder.SingleValueContainer {
128+
/// Encodes a single value of the given type.
129+
/// - parameter value: The value to encode.
130+
mutating func encode(_ value: Date) throws {
131+
switch self.encoder.sink.configuration.dateStrategy {
132+
case .deferredToDate:
133+
try value.encode(to: self.encoder)
134+
case .secondsSince1970:
135+
try self.encode(value.timeIntervalSince1970)
136+
case .millisecondsSince1970:
137+
try self.encode(value.timeIntervalSince1970 * 1_000)
138+
case .iso8601:
139+
let string = DateFormatter.iso8601.string(from: value)
140+
try self.encode(string)
141+
case .formatted(let formatter):
142+
let string = formatter.string(from: value)
143+
try self.encode(string)
144+
case .custom(let closure):
145+
try closure(value, self.encoder)
146+
}
147+
}
148+
149+
/// Encodes a single value of the given type.
150+
/// - parameter value: The value to encode.
151+
mutating func encode(_ value: Data) throws {
152+
switch self.encoder.sink.configuration.dataStrategy {
153+
case .deferredToData:
154+
try value.encode(to: self.encoder)
155+
case .base64:
156+
try self.encode(value.base64EncodedString())
157+
case .custom(let closure):
158+
try closure(value, self.encoder)
159+
}
160+
}
161+
162+
/// Encodes a single value of the given type.
163+
/// - parameter value: The value to encode.
164+
mutating func encode(_ value: Decimal) throws {
165+
switch self.encoder.sink.configuration.decimalStrategy {
166+
case .locale(let locale):
167+
var number = value
168+
let string = NSDecimalString(&number, locale)
169+
try self.encode(string)
170+
case .custom(let closure):
171+
try closure(value, self.encoder)
172+
}
173+
}
174+
175+
/// Encodes a single value of the given type.
176+
/// - parameter value: The value to encode.
177+
func encode(_ value: URL) throws {
178+
try self.lowlevelEncoding { value.path }
179+
}
180+
}
181+
182+
// MARK: -
183+
120184
extension ShadowEncoder.SingleValueContainer {
121185
/// CSV keyed container focus (i.e. where the container is able to operate on).
122186
private enum Focus {
@@ -129,14 +193,26 @@ extension ShadowEncoder.SingleValueContainer {
129193
}
130194

131195
/// Encodes a value by transforming it into a `String` through the closure and then passing it to the sink.
132-
private func lowlevelEncoding<T>(transform: (T) throws -> String) throws {
133-
fatalError()
196+
private func lowlevelEncoding(transform: () throws -> String) throws {
197+
let sink = self.encoder.sink
198+
199+
switch self.focus {
200+
case .field(let rowIndex, let fieldIndex):
201+
let string = try transform()
202+
try sink.field(value: string, at: rowIndex, fieldIndex)
203+
case .row(let rowIndex):
204+
// Values are only allowed to be encoded directly from a single value container in "row level" if the CSV has single column rows.
205+
fatalError()
206+
case .file:
207+
// Values are only allowed to be decoded directly from a single value container in "file level" if the CSV file has a single row with a single column.
208+
fatalError()
209+
}
134210
}
135211
}
136212

137213
fileprivate extension DecodingError {
138214
/// Error raised when a non-conformant floating-point is being encoded and there is no support.
139-
static func invalidFloatingPoint<T>(_ value: T, codingPath: [CodingKey]) -> DecodingError where T:BinaryFloatingPoint {
215+
static func invalidFloatingPoint<T:BinaryFloatingPoint>(_ value: T, codingPath: [CodingKey]) -> DecodingError {
140216
DecodingError.dataCorrupted(
141217
Context(codingPath: codingPath,
142218
debugDescription: "The value '\(value)' is a non-conformant floating-point.")

sources/Codable/Encodable/EncoderConfiguration.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ extension CSVEncoder {
55
@usableFromInline private(set) internal var writerConfiguration: CSVWriter.Configuration
66
/// The strategy to use when dealing with non-conforming numbers.
77
public var floatStrategy: Strategy.NonConformingFloat
8+
/// The strategy to use when encoding decimal values.
9+
public var decimalStrategy: Strategy.DecimalEncoding
810
/// The strategy to use when encoding dates.
911
public var dateStrategy: Strategy.DateEncoding
1012
/// The strategy to use when encoding binary data.
@@ -14,6 +16,7 @@ extension CSVEncoder {
1416
public init() {
1517
self.writerConfiguration = .init()
1618
self.floatStrategy = .throw
19+
self.decimalStrategy = .locale(nil)
1720
self.dateStrategy = .deferredToDate
1821
self.dataStrategy = .base64
1922
}

0 commit comments

Comments
 (0)