Current Encoder/Decoder has a problem that most types (except for Date, Data and Float) have no way to modify encoding/decoding strategies.
I want to give them the way.
Proposed solution
for example of Decoder,
struct NonConformingTypeDecodingStrategies {
subscript<T>(type: T.Type) -> NonConformingTypeDecodingStrategy<T>?
}
public enum NonConformingTypeDecodingStrategy {
/// Throw upon encountering non-conforming values. This is the default strategy.
case `throw`
/// Assume the values as the given value.
case `default`(Any)
/// Decode as a custom value by the given closure.
case custom((_ decoder: Decoder) throws -> Any)
}
open class JSONDecoder {
...
open var nonConformingTypeDecodingStrategies: NonConformingTypeDecodingStrategies
}
Usage
var decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
decoder.dataDecodingStrategy = .custom(myBase85Decoder)
decoder.nonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN")
decoder.nonConformingTypeDecodingStrategies[Int.self] = .default(0)
decoder.nonConformingTypeDecodingStrategies[CGPoint.self] = .custom {
// Default CGPoint is decoded from `[1.0, 1.0]` but I prefer `{x: 1.0, y: 1.0}` instead.
enum CodingKeys: String, CodingKey {
case x
case y
}
let container = try decoder.nestedContainer()
let x = try container.decode(Double.self, forKey: .x)
let y = try container.decode(Double.self, forKey: .y)
return CGPoint(x: x, y: y)
}
Detailed design
private enum AnyNonConformingTypeDecodingStrategy {
/// Throw upon encountering non-conforming values. This is the default strategy.
case `throw`
/// Assume the values as the given value.
case `default`(Any)
/// Decode as a custom value by the given closure.
case custom((_ decoder: Decoder) throws -> Any)
init<T>(original: NonConformingTypeDecodingStrategy<T>) {
switch original {
case .throw: self = .throw
case .default(let t): self = .default(t as Any)
case .custom(let closure): self = .custom({ return try closure($0) as Any })
}
}
}
private extension NonConformingTypeDecodingStrategy {
static func from<T>(erased: AnyNonConformingTypeDecodingStrategy) -> NonConformingTypeDecodingStrategy<T> {
switch erased {
case .throw: return .throw
case .default(let any): return .default(any as! T)
case .custom(let closure): return .custom({ return try closure($0) as! T })
}
}
}
struct NonConformingTypeDecodingStrategies {
private var store: [String: AnyNonConformingTypeDecodingStrategy] = [:]
subscript<T>(type: T.Type) -> NonConformingTypeDecodingStrategy<T>? {
get {
return store[String(describing: type)].flatMap(NonConformingTypeDecodingStrategy<T>.from(erased:))
}
set {
store[String(describing: type)] = newValue.flatMap(AnyNonConformingTypeDecodingStrategy.init(original:))
}
}
}