Home > OS >  How to decode JSON value that can be either string or int
How to decode JSON value that can be either string or int

Time:05-05

Essentially I have the following JSON response I am decoding using the the following struct. What can be added to struct to make it work with a value that can either come through as a String or Int?

JSON Response:

[
  {
    "date": "2022-05-04",
    "code": 122312,
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]

Struct:

struct TestStruct: Decodable {
    let date: String
    let code: ??
    let notes: String
}

CodePudding user response:

Assuming however the data received, the TestStruct will have String type for code attribute the following snippet should decode it.


struct TestStruct: Decodable {
    let date: String
    let code: String?
    let notes: String

    enum CodingKeys: String, CodingKey {
        case date, code, notes
    }

    // custom decoding
    public init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.date = try container.decode(String.self, forKey: .date)
        self.notes = try container.decode(String.self, forKey: .notes)
        
        if let strCode = try container.decodeIfPresent(String.self, forKey: .code) {
            self.code = strCode
        } else if let intCode = try container.decodeIfPresent(Int.self, forKey: .code) {
            self.code = String(intCode)
        } else {
            self.code = nil
        }
    }
}

CodePudding user response:

You will require a custom decoding implementation.

If the property can truly be either a number or a string, the easiest way is to use an enum type.

Additionally, if you want to decode a date object you'll need to pass in a date decoding strategy to your decoder.

Throw this in a playground:

let data = """
[
  {
    "date": "2022-05-04",
    "code": 122312,
    "notes": "Take keys"
  },
  {
    "date": "2022-05-04",
    "code": "Gate: 2312231",
    "notes": "Take Box"
  }
]
""".data(using: .utf8)!

enum Code: Decodable {
    case int(Int)
    case string(String)
}

struct Item: Decodable {
    var date: Date
    var code: Code
    var notes: String

    enum CodingKeys: String, CodingKey {
        case date, code, notes
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.date = try container.decode(Date.self, forKey: .date)
        self.notes = try container.decode(String.self, forKey: .notes)

        if let value = try? container.decode(Int.self, forKey: .code) {
            self.code = .int(value)
        } else if let value = try? container.decode(String.self, forKey: .code) {
            self.code = .string(value)
        } else {
            let context = DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Unable to decode value for `code`")
            throw DecodingError.typeMismatch(Code.self, context)
        }
    }
}

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)

let items = try! decoder.decode([Item].self, from: data)

for item in items {
    print("date: \(item.date.description)")
    print("code: \(item.code)")
    print("notes: \(item.notes)")
    print()
}
  • Related