Is it possible to use Decodable here?

I have a json file like this.

{
    "id": "1199786642468413448",
    "voting_status": "closed",
    "duration_minutes": 1440,
    "options": [
        {
            "position": 1,
            "label": "“C Sharp”",
            "votes": 795
        },
        {
            "position": 2,
            "label": "“C Hashtag”",
            "votes": 156
        }
    ],
    "end_datetime": "2019-11-28T20:26:41.000Z"
}

In the options array, the Dictionary Type is String:Any, Any is String or Int, So I cannot use Decodable here?

My code

public struct Poll:Decodable {
    public let id:String
    public let options:[[String:Any]]
    public let duration_minutes:Int?
    public let end_datetime:Date?
    public let voting_status:String?
    
    enum CodingKeys: String, CodingKey {
        case id
        case options
        case duration_minutes
        case end_datetime
        case voting_status
    }
    
    public init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        id = try values.decode(String.self, forKey: .id)
        
        do {
            options = try values.decode([[String:Int]].self, forKey: .options)
        } catch DecodingError.typeMismatch {
            options = try values.decode([[String:String]].self, forKey: .options)
        } 
        
        duration_minutes = try? values.decode(Int.self, forKey: .duration_minutes)
        end_datetime = try? values.decode(Date.self, forKey: .end_datetime)
        voting_status = try? values.decode(String.self, forKey: .voting_status)
    }
    
}

My code won't work. Any Suggestions?

You don't need to define a custom init(from:) in your case. end_datetime isn't quite in ISO8601 format, so you need to provide the format to the JSONDecoder instance, but that can be handled outside of the Poll struct. The rest of the properties can already be handled by the Decodable conformances provided by default in the Swift standard library.

import Foundation

struct Poll: Decodable {
    let id: String
    let votingStatus: VotingStatus
    let durationMinutes: Int
    let options: [Option]
    let endDateTime: Date

    enum VotingStatus: String, Decodable {
        case open, closed
    }

    struct Option: Decodable {
        let position: Int
        let label: String
        let votes: Int
    }

    enum CodingKeys: String, CodingKey {
        case id, options
        case votingStatus = "voting_status"
        case durationMinutes = "duration_minutes"
        case endDateTime = "end_datetime"
    }
}
  • You could use an enumeration (VotingStatus) to model voting_status, since it can presumably have a finite amount of values ("open", "closed", etc.).
  • You could use a struct (Option) to model options instead of falling back to a dictionary.
  • You could provide more Swifty~ properties to the Poll struct by using CodingKeys: for example via case votingStatus = "vating_status" you're able to place the decoded contents of the JSON voting_status field in the Swift votingStatus property.
let json = """
{
    "id": "1199786642468413448",
    "voting_status": "closed",
    "duration_minutes": 1440,
    "options": [{
        "position": 1,
        "label": "“C Sharp”",
        "votes": 795
    },{
        "position": 2,
        "label": "“C Hashtag”",
        "votes": 156
    }],
    "end_datetime": "2019-11-28T20:26:41.000Z"
}
"""

let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(formatter)

if
    let data = json.data(using: .utf8),
    let poll = try? decoder.decode(Poll.self, from: data)
{
    dump(poll)
}
2 Likes

Why

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
...

doesn't work? I don't understand, it seems it should work. :frowning:

I guess that's due to "end_datetime", which I named endDateTime in the struct instead of endDatetime. If you name it endDatetime you can remove the CodingKeys enum and use .convertFromSnakeCase.

1 Like
Terms of Service

Privacy Policy

Cookie Policy