1
votes
import Foundation
import Combine

let liveSample = URL(string: "https://newsapi.org/v2/top-headlines?country=us&apiKey=<api key>")!

struct ArticleList: Codable {
    struct Article: Codable {
        struct Source: Codable {
            var id: String?
            var name: String?
        }
        var source: Source?
        var author: String?
        var title: String?
        var description: String?
        var url: URL?
        var urlToImage: URL?
        var publishedAt: Date?
        var content: String?
    }
    var status: String
    var totalResults: Int
    var articles: [Article]
}

struct Resource<T: Codable> {
    let request: URLRequest
}

extension URLSession {
    func fetchJSON<T: Codable>(for resource: Resource<T>) -> AnyPublisher<T, Error> {
        return dataTaskPublisher(for: resource.request)
            .map { $0.data }
            .decode(type: T.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

var subscriber: AnyCancellable?

var resource: Resource<ArticleList> =
    Resource<ArticleList>(request: URLRequest(url: liveSample))

subscriber?.cancel()
subscriber = URLSession.shared.fetchJSON(for: resource)
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("The publisher finished normally.")
        case .failure(let error):
            print("An error occured: \(error).")
        }
    }, receiveValue: { result in
        dump(result)
    })

I am using Xcode 12 RC generates error:

An error occured: typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "articles", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "publishedAt", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil)).

1
Please read the error message carefully. It's pretty clear. The value for key publishedAt in the array for key articles is a string. To decode the value to Date you have to add the .iso8601 date decoding stretegy. The default date decoding strategy expects a TimeInterval (aka a Double).vadian

1 Answers

4
votes

I can see you are directly using JSONDecoder() here, in your model var publishedAt: Date? is a date object.

You need to first configure your decoder for parsing date from string then use it.

let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601 // <------- set date decoding strategy explicitly 
extension URLSession {
    func fetchJSON<T: Codable>(for resource: Resource<T>) -> AnyPublisher<T, Error> {
        return dataTaskPublisher(for: resource.request)
            .map { $0.data }
            .decode(type: T.self, decoder: decoder)
            .eraseToAnyPublisher()
    }
}