Skip to content

Instantly share code, notes, and snippets.

@mishagray
Created November 13, 2017 16:44
Show Gist options
  • Save mishagray/9a595806a15dca07ad6a9fd5867bf0ff to your computer and use it in GitHub Desktop.
Save mishagray/9a595806a15dca07ad6a9fd5867bf0ff to your computer and use it in GitHub Desktop.
defines DefaultingJSONDecoder and CodingKeyWithDefaults
//
// 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))
}
}
@mishagray
Copy link
Author

mishagray commented Nov 13, 2017

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

    public class Model: Codable {
        public var id: Int = 0
        public var contentLength: Int = 0
   }

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

    public class Model: Codable {
        public var id: Int = 0
       public var contentLength: Int? = 0
   }

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 own init(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:

    public class Model: Codable {
        var id: Int = 0
        var contentLength: Int = 100

        public enum CodingKeys: String, CodingKeyWithDefaults {
            case id
            case contentLength

            //  you don't need to supply this method, if you are okay with '0' default values!
           //   the 'default' version of this method just calls defaultZeroValuesForNumberTypes(type)
            public func defaultValue<T>(_ type: T.Type) throws -> T? {
                switch self {
                case .contentLength where T.self is Int.Type:
                    return (Int(100) as! T) // swiftlint:disable:this force_cast
                default:
                    return self.defaultZeroValuesForNumberTypes(type)
                }
            }

        }
   }

And then I use the 'DefaultingJSONDecoder()'

let decoder: JSONDecoder = DefaultingJSONDecoder()
let t = try decoder.decode(Model.self, from: data)

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.

@mishagray
Copy link
Author

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