Created
November 13, 2017 16:44
-
-
Save mishagray/9a595806a15dca07ad6a9fd5867bf0ff to your computer and use it in GitHub Desktop.
defines DefaultingJSONDecoder and CodingKeyWithDefaults
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DefaultingDecoder.swift | |
// | |
// Created by Michael Gray on 10/12/17. | |
// | |
import Foundation | |
// swiftlint:disable force_cast file_length | |
public protocol CodingKeyWithDefaults: CodingKey { | |
func defaultValue<T>(_ type: T.Type) throws -> T? | |
} | |
public protocol DecodableWithDefault: Decodable { | |
init() | |
} | |
public protocol KeyedDecodedProcessing { | |
func postDecode<K>(container:K, forKey: K.Key) throws where K: KeyedDecodingContainerProtocol | |
} | |
extension CodingKeyWithDefaults { | |
// swiftlint:disable:next cyclomatic_complexity | |
public func defaultZeroValuesForNumberTypes<T>(_ type: T.Type) -> T? { | |
switch T.self { | |
case is Bool.Type: | |
return (Bool(false) as! T) | |
case is Int.Type: | |
return (Int(0) as! T) // swiftlint:disable:this force_cast | |
case is Int8.Type: | |
return (Int8(0) as! T) // swiftlint:disable:this force_cast | |
case is Int16.Type: | |
return (Int16(0) as! T) // swiftlint:disable:this force_cast | |
case is Int32.Type: | |
return (Int32(0) as! T) // swiftlint:disable:this force_cast | |
case is Int64.Type: | |
return (Int64(0) as! T) // swiftlint:disable:this force_cast | |
case is UInt.Type: | |
return (UInt(0) as! T) // swiftlint:disable:this force_cast | |
case is UInt8.Type: | |
return (UInt8(0) as! T) // swiftlint:disable:this force_cast | |
case is UInt16.Type: | |
return (UInt16(0) as! T) // swiftlint:disable:this force_cast | |
case is UInt32.Type: | |
return (UInt32(0) as! T) // swiftlint:disable:this force_cast | |
case is UInt64.Type: | |
return (UInt64(0) as! T) // swiftlint:disable:this force_cast | |
case is Float.Type: | |
return (Float(0) as! T) // swiftlint:disable:this force_cast | |
case is Double.Type: | |
return (Double(0) as! T) // swiftlint:disable:this force_cast | |
default: | |
return nil | |
} | |
} | |
public func defaultValue<T>(_ type: T.Type) throws -> T? { | |
if let v = self.defaultZeroValuesForNumberTypes(type) { | |
return v | |
} | |
return nil | |
} | |
public func defaultValue<T>(container: KeyedDecodingContainer<Self>, _ type: T.Type) throws -> T { | |
if let t = try self.defaultValue(type) { | |
return t | |
} | |
let debugDescription = "no default value found for \(self) in \(Self.self)" | |
let context = DecodingError.Context(codingPath: container.codingPath, | |
debugDescription: debugDescription) | |
throw DecodingError.typeMismatch(Self.self, context) | |
} | |
} | |
protocol DefaultDecoderTarget { | |
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable | |
} | |
extension JSONDecoder: DefaultDecoderTarget {} | |
extension DefaultDecoderTarget { | |
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: DecodableWithDefault { | |
let decoderGrabber = try self.decode(DecoderGrabber.self, from: data) | |
let defaultingDecoder = DefaultingDecoder(wrapping: decoderGrabber.decoder) | |
return try T(from: defaultingDecoder) | |
} | |
} | |
open class DefaultingJSONDecoder: JSONDecoder { | |
override open func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: Decodable { | |
let decoderGrabber = try super.decode(DecoderGrabber.self, from: data) | |
let defaultingDecoder = DefaultingDecoder(wrapping: decoderGrabber.decoder) | |
return try T(from: defaultingDecoder) | |
} | |
} | |
struct DecoderGrabber: Decodable { | |
let decoder: Decoder | |
public init(from decoder: Decoder) throws { | |
self.decoder = decoder | |
} | |
} | |
struct DefaultingDecoder: Decoder { | |
// swiftlint:disable:next force_unwrapping | |
static let enclosedTypeKey = CodingUserInfoKey(rawValue: "org.futurekit.DefaultingDecoder.enclosedType")! | |
// swiftlint:disable:next force_unwrapping line_length | |
static let enclosedTypeIsDefaultingKey = CodingUserInfoKey(rawValue: "org.futurekit.DefaultingDecoder.enclosedTypeIsDefaulting")! | |
let innerDecoder: Decoder | |
init(wrapping decoder: Decoder) { | |
innerDecoder = decoder | |
} | |
var codingPath: [CodingKey] { | |
return innerDecoder.codingPath | |
} | |
var userInfo: [CodingUserInfoKey: Any] { | |
return innerDecoder.userInfo | |
} | |
func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key: CodingKey { | |
let nestedContainer = try innerDecoder.container(keyedBy: type) | |
let container = DefaultingKeyedDecodingContainer(container: nestedContainer) | |
return KeyedDecodingContainer(container) | |
} | |
func unkeyedContainer() throws -> UnkeyedDecodingContainer { | |
return DefaultUnkeyedDecodingContainer(container: try innerDecoder.unkeyedContainer()) | |
} | |
func singleValueContainer() throws -> SingleValueDecodingContainer { | |
return DefaultSingleValueDecodingContainer(container: try innerDecoder.singleValueContainer()) | |
} | |
} | |
public struct DefaultSingleValueDecodingContainer: SingleValueDecodingContainer { | |
public var codingPath: [CodingKey] { | |
return innerContainer.codingPath | |
} | |
private let innerContainer: SingleValueDecodingContainer | |
public init(container: SingleValueDecodingContainer) { | |
innerContainer = container | |
} | |
public func decodeNil() -> Bool { | |
return innerContainer.decodeNil() | |
} | |
public func decode(_ type: Bool.Type) throws -> Bool { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Int.Type) throws -> Int { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Int8.Type) throws -> Int8 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Int16.Type) throws -> Int16 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Int32.Type) throws -> Int32 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Int64.Type) throws -> Int64 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: UInt.Type) throws -> UInt { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: UInt8.Type) throws -> UInt8 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: UInt16.Type) throws -> UInt16 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: UInt32.Type) throws -> UInt32 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: UInt64.Type) throws -> UInt64 { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Float.Type) throws -> Float { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: Double.Type) throws -> Double { | |
return try innerContainer.decode(type) | |
} | |
public func decode(_ type: String.Type) throws -> String { | |
return try innerContainer.decode(type) | |
} | |
public func decode<T>(_ type: T.Type) throws -> T where T: Decodable { | |
if T.self is DecodableWithDefault.Type { | |
if innerContainer.decodeNil() { | |
let metaType = (T.self as! DecodableWithDefault.Type) // swiftlint:disable:this force_cast | |
return (metaType.init() as! T) // swiftlint:disable:this force_cast | |
} | |
} | |
return try innerContainer.decode(type) | |
} | |
public func decode<T>(_ type: T.Type) throws -> T where T: DecodableWithDefault { | |
if innerContainer.decodeNil() { | |
return T() | |
} | |
return try innerContainer.decode(type) | |
} | |
} | |
public struct DefaultUnkeyedDecodingContainer: UnkeyedDecodingContainer { | |
var innerContainer: UnkeyedDecodingContainer | |
public init(container: UnkeyedDecodingContainer) { | |
innerContainer = container | |
} | |
public var codingPath: [CodingKey] { | |
return innerContainer.codingPath | |
} | |
public var count: Int? { | |
return innerContainer.count | |
} | |
public var isAtEnd: Bool { | |
return innerContainer.isAtEnd | |
} | |
public var currentIndex: Int { | |
return innerContainer.currentIndex | |
} | |
public mutating func decodeNil() throws -> Bool { | |
return try innerContainer.decodeNil() | |
} | |
public mutating func decode(_ type: Bool.Type) throws -> Bool { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Int.Type) throws -> Int { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Int8.Type) throws -> Int8 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Int16.Type) throws -> Int16 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Int32.Type) throws -> Int32 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Int64.Type) throws -> Int64 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: UInt.Type) throws -> UInt { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: UInt8.Type) throws -> UInt8 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: UInt16.Type) throws -> UInt16 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: UInt32.Type) throws -> UInt32 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: UInt64.Type) throws -> UInt64 { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Float.Type) throws -> Float { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: Double.Type) throws -> Double { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode(_ type: String.Type) throws -> String { | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode<T>(_ type: T.Type) throws -> T where T: Decodable { | |
if T.self is DecodableWithDefault.Type { | |
if try innerContainer.decodeNil() { | |
let metaType = (T.self as! DecodableWithDefault.Type) // swiftlint:disable:this force_cast | |
return (metaType.init() as! T) // swiftlint:disable:this force_cast | |
} | |
} | |
return try innerContainer.decode(type) | |
} | |
public mutating func decode<T>(_ type: T.Type) throws -> T where T: DecodableWithDefault { | |
if try innerContainer.decodeNil() { | |
return T() | |
} | |
return try innerContainer.decode(type) | |
} | |
public mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey { | |
let nestedContainer = try innerContainer.nestedContainer(keyedBy: type) | |
let container = DefaultingKeyedDecodingContainer(container: nestedContainer) | |
return KeyedDecodingContainer(container) | |
} | |
public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { | |
return DefaultUnkeyedDecodingContainer(container: try innerContainer.nestedUnkeyedContainer()) | |
} | |
public mutating func superDecoder() throws -> Decoder { | |
return DefaultingDecoder(wrapping: try innerContainer.superDecoder()) | |
} | |
} | |
public struct DefaultingKeyedDecodingContainer<K> : KeyedDecodingContainerProtocol where K: CodingKey { | |
public typealias Key = K | |
let innerContainer: KeyedDecodingContainer<K> | |
public init(container: KeyedDecodingContainer<K>) { | |
innerContainer = container | |
} | |
public init<C>(container: C) where C: KeyedDecodingContainerProtocol, C.Key == Key { | |
innerContainer = KeyedDecodingContainer(container) | |
} | |
public var codingPath: [CodingKey] { | |
return innerContainer.codingPath | |
} | |
public var allKeys: [K] { | |
return innerContainer.allKeys | |
} | |
public func contains(_ key: K) -> Bool { | |
return innerContainer.contains(key) | |
} | |
private func _decode<T>(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
if K.self is CodingKeyWithDefaults.Type { | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
} | |
if T.self is DecodableWithDefault.Type { | |
let metaType = (T.self as! DecodableWithDefault.Type) // swiftlint:disable:this force_cast | |
return (metaType.init() as! T) // swiftlint:disable:this force_cast | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} | |
public func decode<T>(_ type: T.Type, forKey key: K) throws -> T where T: Decodable { | |
let t = try _decode(type, forKey: key) | |
if T.self is KeyedDecodedProcessing.Type { | |
// swiftlint:disable:next force_cast | |
try (t as! KeyedDecodedProcessing).postDecode(container: self, forKey: key) | |
} | |
return t | |
} | |
public func decode(_ type: String.Type, forKey key: K) throws -> String { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Double.Type, forKey key: K) throws -> Double { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Float.Type, forKey key: K) throws -> Float { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: UInt64.Type, forKey key: K) throws -> UInt64 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: UInt32.Type, forKey key: K) throws -> UInt32 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: UInt16.Type, forKey key: K) throws -> UInt16 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: UInt8.Type, forKey key: K) throws -> UInt8 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: UInt.Type, forKey key: K) throws -> UInt { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Int64.Type, forKey key: K) throws -> Int64 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Int32.Type, forKey key: K) throws -> Int32 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Int16.Type, forKey key: K) throws -> Int16 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Int8.Type, forKey key: K) throws -> Int8 { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Int.Type, forKey key: K) throws -> Int { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decode(_ type: Bool.Type, forKey key: K) throws -> Bool { | |
if K.self is CodingKeyWithDefaults.Type { | |
if let t = try innerContainer.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} else { | |
return try innerContainer.decode(type, forKey: key) | |
} | |
} | |
public func decodeNil(forKey key: K) throws -> Bool { | |
return try innerContainer.decodeNil(forKey: key) | |
} | |
public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: K) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey { | |
let nestedContainer = try innerContainer.nestedContainer(keyedBy: type, forKey: key) | |
let container = DefaultingKeyedDecodingContainer<NestedKey>(container: nestedContainer) | |
return KeyedDecodingContainer<NestedKey>(container) | |
} | |
public func nestedUnkeyedContainer(forKey key: K) throws -> UnkeyedDecodingContainer { | |
return DefaultUnkeyedDecodingContainer(container: try innerContainer.nestedUnkeyedContainer(forKey: key)) | |
} | |
public func superDecoder() throws -> Decoder { | |
return DefaultingDecoder(wrapping: try innerContainer.superDecoder()) | |
} | |
public func superDecoder(forKey key: K) throws -> Decoder { | |
return DefaultingDecoder(wrapping: try innerContainer.superDecoder(forKey: key)) | |
} | |
} | |
extension KeyedDecodingContainerProtocol { | |
public var missingKeys: [Key] { | |
return self.allKeys.filter { !self.contains($0) } | |
} | |
public var containsKeys: [Key] { | |
return self.allKeys.filter { self.contains($0) } | |
} | |
private func _defaultingDecode<T>(_ type: T.Type, forKey key: Self.Key) throws -> T where T: Decodable { | |
if let t = try self.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
if Key.self is CodingKeyWithDefaults.Type { | |
// swiftlint:disable:next force_cast | |
if let t = try (key as! CodingKeyWithDefaults).defaultValue(type) { | |
return t | |
} | |
} | |
if T.self is DecodableWithDefault.Type { | |
let metaType = (T.self as! DecodableWithDefault.Type) // swiftlint:disable:this force_cast | |
return (metaType.init() as! T) // swiftlint:disable:this force_cast | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} | |
public func defaultingDecode<T>(_ type: T.Type, forKey key: Self.Key) throws -> T where T: Decodable { | |
let t = try _defaultingDecode(type, forKey: key) | |
if T.self is KeyedDecodedProcessing.Type { | |
// swiftlint:disable:next force_cast | |
try (t as! KeyedDecodedProcessing).postDecode(container: self, forKey: key) | |
} | |
return t | |
} | |
} | |
extension KeyedDecodingContainerProtocol where Key: CodingKeyWithDefaults { | |
private func _defaultingDecode<T>(_ type: T.Type, forKey key: Self.Key) throws -> T where T: DecodableWithDefault { | |
if let t = try self.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
if let t = try key.defaultValue(type) { | |
return t | |
} | |
return T() | |
} | |
private func _defaultingDecode<T>(_ type: T.Type, forKey key: Self.Key) throws -> T where T: Decodable { | |
if let t = try self.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
if let t = try key.defaultValue(type) { | |
return t | |
} | |
if T.self is DecodableWithDefault.Type { | |
let metaType = (T.self as! DecodableWithDefault.Type) // swiftlint:disable:this force_cast | |
return (metaType.init() as! T) // swiftlint:disable:this force_cast | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} | |
public func decode<T>(_ type: T.Type, forKey key: Self.Key) throws -> T where T: DecodableWithDefault { | |
if let t = try self.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
if let t = try key.defaultValue(type) { | |
return t | |
} | |
return T() | |
} | |
public func decode<T>(_ type: T.Type, forKey key: Self.Key) throws -> T where T: Decodable { | |
if let t = try self.decodeIfPresent(type, forKey: key) { | |
return t | |
} | |
if let t = try key.defaultValue(type) { | |
return t | |
} | |
let debugDescription = "No value associated with key \(key) (\"\(key.stringValue)\")." | |
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.codingPath, | |
debugDescription: debugDescription)) | |
} | |
} |
There is also a DecodableWithDefault protocol, that you should be able to use with 'custom' types that supply an init()
method.
But I had mixed success with it.
The CodingKeyWithDefaults seems to always work.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The thing that IS kind of annoying about Codeable, is that by default, the Swift compiler doesn't want to assign default values correctly, for when the JSON is not present. This is inherit in the Codeable implementation in the swift 4 compiler. The 'only' way to decode JSON with 'missing' properties, is to define them as Optional.
For example:
You want define a property
But the server sends you a JSON
{ "id": 1234 }
The swift synthesized 'Codeable' will throw an exception here when you call 'try decoder.decode(Model.self, from: data)``` using the above JSON. It's not 'smart' enough to set contentLength to 0. If you define things as
Now
decode
works, and throw, BUT you may not want to have an 'Optional' in your model, if you want to support default values. You COULD write your owninit(from decoder: Decoder)
method to deal with this, but of course if you want to custom handle ONE key, you have to custom decode all of them. Which sort of defeats the whole coolness of Codable.So... The way I solved this was creating a 'Decoder' type, (that I call) 'DefaultingJSONDecoder' and a new 'CodingKey' protocol called 'CodingKeyWithDefaults'.
So now I can define my type this way:
And then I use the 'DefaultingJSONDecoder()'
By default, DefaultingJSONDecoder will decode 'Int' values to 0. And decode
contentLength
to 100 when it's missing.If you want a 'custom' default value you need to define your own
public func defaultValue<T>(_ type: T.Type) throws -> T?
method. If you don't supply one, than it will set all the built in Int/Bool/Float/Double types to '0' .You can grab THAT over here: https://gist.github.com/mishagray/9a595806a15dca07ad6a9fd5867bf0ff
Which I have also been wanting to publish as it's own Open Source library, since it's useful outside of Realm.