Search code examples
iosjsonswiftdecode

How to decode a JSON array with no name?


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


Solution

  • 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)
    }