NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

SOLVED: Handling Unexpected Date Type in JSON Decoder

Forums > SwiftUI

Hi all, I'm running into an issue with my JSON decoder and dates (dates really are hard!). When present in the JSON database, they show up in the format of "2011-04-17". My decoder looks like this:

func fetchResults() async {
        do {
            let url = URL(string: "_url_")!
            let (data, _) = try await URLSession.shared.data(from: url)

            let decoder = JSONDecoder()
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd"
            decoder.dateDecodingStrategy = .formatted(formatter)
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            if let decodedResponse = try? decoder.decode(Result.self, from: data) {
                self.result = decodedResponse
            }
        } catch {
            print("Download failed")
        }
    }

And my JSON-side struct looks like this:

    struct Result: Identifiable, Codable, Hashable {
        let id: Int
        let date: Date?
        let name: String?
    }

This all works great when the date appears as above, but crashes and burns when the date that returns is empty, in the form of "".

Xcode provides me with this error description: dataCorrupted(Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "Date", intValue: nil)], debugDescription: "Date string does not match format expected by formatter.", underlyingError: nil))

I suspect there is some missing error handling in my decoder for this sort of thing - how can I smartly update my decoder to ignore empty 'date' strings?

   

@Paul shares a story about a bad date he recently had:

This all works great when the date appears as above, but crashes and burns
when the date that returns is empty, in the form of "". [...snip....]
how can I smartly update my decoder to ignore empty 'date' strings?

Consider reading the Date in as a simple String.

Then in your Result object (ugh! Use a more descriptive name!) you may have a computed var that takes the String form of your date and converts it to a valid Swift Date object.

struct myUsefulObject:  Identifiable, Codable {
        let id: Int
        let date: String // <-- Read in as a string
        let name: String?

        var usefulDateFormat: Date? {   // <-- Make this optional to account for blank dates
        // Use the string format and transform it
         let usefulDate = // ----- transform your string into a valid date object here ---- // 
         return usefulDateFormat
        }

}

See -> More Horrible Date stories

   

You can also write a custom init to decode the values and an encode(to:) method to encode the values:

//I called this struct Person instead of Result because that's a BAAAAAAD name to use
struct Person: Identifiable, Codable, Hashable {
    let id: Int
    let name: String?
    let date: Date?

    private enum CodingKeys: String, CodingKey {
        case id, name, date
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.id = try container.decode(Int.self, forKey: .id)
        self.name = try? container.decodeIfPresent(String.self, forKey: .name)
        self.date = try? container.decodeIfPresent(Date.self, forKey: .date)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(id, forKey: .id)
        try container.encode(name ?? "", forKey: .name)
        if let date = date {
            //it's a better idea to use the new FormatStyle APIs here, but I couldn't be arsed
            let fmt = DateFormatter()
            fmt.dateFormat = "yyyy-MM-dd"
            try container.encode(fmt.string(from: date), forKey: .date)
        } else {
            try container.encode("", forKey: .date)
        }
    }
}

   

Brilliant, thanks for the help @Obelix and @roosterboy. Posting my updated code in case others find it useful.

My updated struct based on @Obelix's suggestions (now featuring an improved naming scheme!):

struct Episode: Identifiable, Codable, Hashable
    let id: Int
    let name: String?
    let airDate: String
    ...

    var usefulDateFormat: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        let usefulDate = formatter.date(from: airDate)
        return usefulDate
    }
}

When called in my real views, I'm structuring it something like this:

if let newDate = episode.usefulDateFormat {
    Text("\(newDate)")
}

I opted to use the "if let" to handle the optional Date? from the struct - this way the view can ignore it if there was an empty string rather than reporting out a default Date.now, or something similar.

@roosterboy, you're correct in highlighting a need for a more applicable date formatting strategy...my solution is US-focused and @twostraws would likely recommend I leverage Apple's FormatStyle tool to decode in a way that's more globally applicable. Work to go!

   

Hacking with Swift is sponsored by Essential Developer

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until October 1st.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.