Search code examples
iosjsonswiftcodabledecodable

Decoding JSON attribute of ambiguous type in iOS Swift


How would you write the decoding code for the following JSON:

{
   "identifier": "1",
   "issuer": "visa",
   "pattern": [5, [7, 9]]

}

To map it into the following model:

struct Card: Decodable {
    let identifier: String
    let issuer: String
    let pattern: [CardPattern]
}

enum CardPattern: Decodable {
    case prefix(Int)
    case range(start: Int, end: Int)
}

Notice how the pattern attribute in the json is a collection of two possible values:

  • Int indicating we should map into a CardPattern.prefix case
  • [Int] containing two values, indicating we should map into a CardPattern.range case (first value is start, second value is end)

Solution

  • Read about the singleValueContainer it should solve your problem. Here is a working snippet with testing...

    enum CardPattern: Codable {
        case prefix(Int)
        case range(start: Int, end: Int)
        
        init(from decoder: Decoder) throws {
            let singleContainer = try decoder.singleValueContainer()
            do {
                let prefix = try singleContainer.decode(Int.self)
                self = .prefix(prefix)
                return
            } catch {
                print("Not a prefix")
            }
            
            do {
                let range = try singleContainer.decode([Int].self)
                self = .range(start: range[0], end: range[1])
                return
            } catch {
                print("Not a range")
            }
            throw NSError(domain: "Unknown type", code: -1)
        }
        
        func encode(to encoder: Encoder) throws {
            var singleContainer = encoder.singleValueContainer()
            switch self {
            case .prefix(let value):
                try singleContainer.encode(value)
            case .range(start: let start, end: let end):
                let range: [Int] = [start, end]
                try singleContainer.encode(range)
            }
        }
        
    }
    
    
    let data = [CardPattern.prefix(10), CardPattern.range(start: 2, end: 9)]
    var encodedData: Data = Data()
    do {
        encodedData = try JSONEncoder().encode(data)
        print(encodedData)
    } catch {
        print("encode")
        print(error)
    }
    do {
        let decoded = try JSONDecoder().decode([CardPattern].self, from: encodedData)
        print(decoded)
    } catch {
        print("decode")
        print(error)
    }