Swift 4 introduced the Codable protocol to decode/encode JSON objects. Codable is tightly integrated into the Swift toolchain. It is widely used on client-side and server-side Swift.

Sometimes, we meet challenges that require us to customize how we use Codable to suit different advanced scenarios. Here are three hard challenges I’ve met when using Codable and my clean code solutions.


Multiple Value Types for the Same Key

A hypothetical API

Consider a hypothetical API that sometimes returns a String and sometimes returns an Int for the key id:

[
	    {
	      "id": "XA134RRW",
	      "name": "Bob"
	    },
	    {
	      "id": 3003,
	      "name": "John"
	    }

MultipleTypes.json by Eric Yang

The most common and straightforward solution is using an Enum to map to the different cases. Do we have the clean code solution to avoid the boilerplate rawValue it introduces?

The clean code solution

To reduce the boilerplate and make clean code, I’m using the PropertyWrapper:

// MARk: LosslessStringCodable
	public typealias LosslessStringCodable = LosslessStringConvertible & Codable

	// MARK: DynamicDecoder
	public struct DynamicCoder<T: LosslessStringCodable> {
	    public static func decode(from decoder: Decoder) throws -> T? {
	        do {
	            return try T(from: decoder)
	        } catch {
	            // Handle different types for the same key error
	            func decode<T: LosslessStringCodable>(_: T.Type) -> (Decoder) -> LosslessStringCodable? {
	                return { try? T.init(from: $0) }
	            }
	            let types: [(Decoder) -> LosslessStringCodable?] = [
	                decode(String.self),
	                decode(Bool.self),
	                decode(Int.self),
	                decode(Double.self)
	            ]

	            guard let rawValue = types.lazy.compactMap({ $0(decoder) }).first,
	                let value = T.init("\(rawValue)") else {
	                    return nil
	            }

	            return value
	        }
	    }
	}

	// MARK: CodableValue
	@propertyWrapper
	public struct CodableValue<T: LosslessStringCodable>: Codable {
	    public let wrappedValue: T

	    public init(wrappedValue: T) {
	        self.wrappedValue = wrappedValue
	    }

	    public init(from decoder: Decoder) throws {
	        let value: T = try DynamicCoder.decode(from: decoder)!
	        self.init(wrappedValue: value)
	    }

	    public func encode(to encoder: Encoder) throws {
	        var container = encoder.singleValueContainer()
	        try container.encode(wrappedValue)
	    }
	}

#programming #ios #mobile #technology #swift

3 Small Solutions to Help With Swift Codable
2.10 GEEK