2

I am trying to decode a JSON from URL but my code can not decode this JSON.

This is the json: json

and this is my model object :

struct User: Decodable, Identifiable {
    let name: String
    let birthdate: Date
    let id: Int
}

and this is my fetch users code:

 func fetchUsers() async throws -> [User]? {
        let endPoint = EndPoint.users.rawValue
        guard let url = URL(string: endPoint) else {
            print("Invalid URL")
            return nil
        }
        let urlRequest = URLRequest(url: url)
        do {
            let (json, _) = try await URLSession.shared.data(for: urlRequest)
            return try JSONDecoder().decode([User].self, from: json)
         } catch {
             print(error)
         }
    }
    // create user with async/await
}

when I debug I get this data result of URLSession: json data

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
agmcoder
  • 195
  • 1
  • 8

1 Answers1

1

The issue there is in your JSONDecoder dateDecodingStrategy. The default strategy is called deferredToDate which means it is expecting the timeIntervalSinceReferenceDate. What you need is a custom dateFormat to parse your ISO8601 date format. Check this How to convert a date string with optional fractional seconds using Codable in Swift?.

Just create a custom DateFormatter iso8601 with and without fractional seconds:

extension Formatter {
    static let iso8601withFractionalSeconds: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
        return formatter
    }()
    static let iso8601: DateFormatter = {
        let formatter = DateFormatter()
        formatter.calendar = Calendar(identifier: .iso8601)
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
        return formatter
    }()
}

And a custom dateDecodingStrategy:

extension JSONDecoder.DateDecodingStrategy {
    static let customISO8601 = custom {
        let container = try $0.singleValueContainer()
        let string = try container.decode(String.self)
        if let date = Formatter.iso8601withFractionalSeconds.date(from: string) ?? Formatter.iso8601.date(from: string) {
            return date
        }
        throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)")
    }
}

Now you can set your JSONDecoder dateDecodingStrategy properly:

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .customISO8601

And use that decoder to parse your JSON string. Note that if you use try? you are discarding the error which you should use to debug your issue:

    return try decoder.decode([User].self, from: json) 
} catch {
    print(error)
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • I have implemented these extensions but it does not work, – agmcoder Sep 25 '22 at 19:25
  • I had to make some changes. Your date format doesnt have the timezone. Check my last edit. Make sure your date string time zone is UTC otherwise remove those lines `formatter.timeZone = TimeZone(secondsFromGMT: 0)` from the dateFormatters. – Leo Dabus Sep 25 '22 at 19:25
  • I implemented your code but I am receiving the error bellow : Type 'Formatter' has no member 'iso8601' – agmcoder Sep 25 '22 at 19:42
  • This means you haven’t added the necessary code to your project. Or you need to declare them as public. – Leo Dabus Sep 25 '22 at 19:43
  • right now it is working, but I get this error by the print inside the catch: valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 59", intValue: 59), CodingKeys(stringValue: "name", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil)) – agmcoder Sep 25 '22 at 19:48
  • This means your name property should be optional String. Again add your JSON response string to your post. Please don’t add the answer code to your question – Leo Dabus Sep 25 '22 at 19:49
  • Btw you only need once catch for multiple try – Leo Dabus Sep 25 '22 at 19:54
  • 1
    it is working so good, I am so thank with you. – agmcoder Sep 25 '22 at 20:07