1
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). Commented Nov 7, 2020 at 18:19

1 Answer 1

4

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()
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.