Home > Software design >  Swift "No value associated with key CodingKeys" Error when parsing JSON from NewsCatcher A
Swift "No value associated with key CodingKeys" Error when parsing JSON from NewsCatcher A

Time:03-22

Still new in Swift. I've searched through many similar questions asked about the subject before and for some reason still can't pinpoint where the issue is in how my structs are built or what I am doing wrong. I'd appreciate your help!

I am using newswatcher api in my swift application, and the data I fetch is built like this:

{
    "status": "ok",
    "total_hits": 10000,
    "page": 1,
    "total_pages": 10000,
    "page_size": 1,
    "articles": [
        {
            "title": "Pizza Pizza has teamed up with Mattel for the ultimate game night combo",
            "author": null,
            "published_date": "2022-03-18 20:34:17",
            "published_date_precision": "full",
            "link": "https://dailyhive.com/vancouver/pizza-pizza-mattel-canada-uno",
            "clean_url": "dailyhive.com",
            "excerpt": "Pizza and game nights are a match made in heaven. Pizza Pizza has partnered with Mattel Canada for the ultimate package deal.",
            "summary": "Pizza and game nights are a match made in heaven — even Pizza Pizza knows that. The Canadian chain has partnered with Mattel Canada for the ultimate package deal.\nReturning for another year is the pizza chain's UNO collab but this time it features a new limited-edition Pizza Pizza or Pizza 73 branded UNO deck with every featured UNO combo purchase.\nThe decks feature pizza art and a surprise bonus offer too.\n\n \nView this post on Instagram\n A post shared by Pizza Pizza (@pizzapizzaltd)\n\n 'For over 50 years, UNO has become a staple at game nights across the country, bringing families and friends together through gameplay,' said Jennifer Gileno, Head of Licensing and Retail Development for Mattel Canada.",
            "rights": null,
            "rank": 9543,
            "topic": "news",
            "country": "CA",
            "language": "en",
            "authors": [],
            "media": "https://images.dailyhive.com/20220318132250/pizza-pizza-uno-500x256.jpg",
            "is_opinion": false,
            "twitter_account": "https://dailyhive.com/vancouver/pizza-pizza-mattel-canada-uno",
            "_score": 14.017945,
            "_id": "feca5f5fe473e561bf0b8c11b01b87bf"
        }
    ],
    "user_input": {
        "q": "pizza",
        "search_in": [
            "title_summary"
        ],
        "lang": null,
        "not_lang": null,
        "countries": null,
        "not_countries": null,
        "from": "2022-03-15 00:00:00",
        "to": null,
        "ranked_only": "True",
        "from_rank": null,
        "to_rank": null,
        "sort_by": "relevancy",
        "page": 1,
        "size": 1,
        "sources": null,
        "not_sources": null,
        "topic": null,
        "published_date_precision": null
    }
}

I have created the following structs in order to decode the data:

struct ArticleModel: Codable {
    
    let totalPages: Int
    let articles: [Articles]
    let numOfResults: Int
    
    enum CodingKeys: String, CodingKey {
        case totalPages = "total_pages"
        case articles = "articles"
        case numOfResults = "total_hits"
    }
}

struct Articles: Codable {
    
    let id: String
    let articleTitle: String
    let date: String
    let url: String
    let content: String
    let author: String?
    let topic: String
    let imageUrl: String?
    
    enum CodingKeys: String, CodingKey {
        case id = "_id"
        case articleTitle = "title"
        case content = "summary"
        case author = "author"
        case url = "link"
        case date = "published_date"
        case topic = "topic"
        case imageUrl = "media"
    }
}

I am using Pagination in my app and my initial fetch is working great with no issue. However - when scrolling down to the bottom of the UITableView I fire another fetch request (for the next batch of data i.e. page 2 of data) and I get the following error in my console:

keyNotFound(CodingKeys(stringValue: "total_pages", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "total_pages", intValue: nil) ("total_pages").", underlyingError: nil))

The pagination works fine, the data-batches are retrieved as should.. but I don't understand why this error keeps popping and why it happens only when fetching from the 2nd time forward.

Edit #1: I can assure that no matter which query or page I fetch for - total_pages is always returned in the results and always has a value.

Edit #2: I tried making total_pages optional but then the error in the console changes to:

keyNotFound(CodingKeys(stringValue: "articles", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "articles", intValue: nil) ("articles").", underlyingError: nil)) which also doesn't make any sense because I am seeing the new results on the screen..

Edit #3: Here is the response I am getting back on the 2nd page:

{
    "status": "ok",
    "total_hits": 10000,
    "page": 2,
    "total_pages": 10000,
    "page_size": 1,
    "articles": [
        {
            "title": "Broadway & Beyond Hosts Webinar on Anti-Racist Stage Management Practices",
            "author": "Raven Brunner",
            "published_date": "2022-03-21 17:17:43",
            "published_date_precision": "full",
            "link": "https://playbill.com/article/broadway-beyond-hosts-webinar-on-anti-racist-stage-management-practices",
            "clean_url": "playbill.com",
            "excerpt": "The webinar will be led by veteran stage managers Narda E. Alcorn and Lisa Porter.",
            "summary": "Education News Education News Education News Education News Theatre Alternatives Industry News Industry News Industry News Education News Education News Education News Education News Education News Education News Industry News Industry News Industry News Education News Education News Industry News Industry News Education News Education News Industry News Education News Industry News Education News Industry News Industry News Education News Education News Industry News Education News Industry New",
            "rights": "playbill.com",
            "rank": 5215,
            "topic": "entertainment",
            "country": "US",
            "language": "en",
            "authors": [
                "Raven Brunner"
            ],
            "media": "https://assets.playbill.com/editorial/_1200x630_crop_center-center_82_none/Narda-E.-Alcorn-and-Lisa-Porter_HR.jpg?mtime=1647876883",
            "is_opinion": false,
            "twitter_account": "@playbill",
            "_score": 5.5872316,
            "_id": "7e297f463684c344e3bb9b70d6229fbf"
        }
    ],
    "user_input": {
        "q": "news",
        "search_in": [
            "title_summary"
        ],
        "lang": null,
        "not_lang": null,
        "countries": null,
        "not_countries": null,
        "from": "2022-03-15 00:00:00",
        "to": null,
        "ranked_only": "True",
        "from_rank": null,
        "to_rank": null,
        "sort_by": "relevancy",
        "page": 2,
        "size": 1,
        "sources": null,
        "not_sources": null,
        "topic": null,
        "published_date_precision": null
    }
}

In case it matters - here is how I detect the user scrolled all the way down:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
        let position = scrollView.contentOffset.y
        if position > (tableView.contentSize.height - 100 - scrollView.frame.size.height) {
            fetchNewsFromAPI() {
                DispatchQueue.main.async {
                    self.tableView.tableFooterView = nil
                }
            }
        }
    }

and this is the function that is responsible for fetching the data:

func fetchNewsFromAPI(completionHandler: @escaping () -> ()) {
        
        let alamofireQuery = AlamofireManager(from: "\(Constants.apiCalls.newsUrl)?q=news&page_size=\(amountToFetch)&page=\(currentPaginationPage)")
        
        if !alamofireQuery.isPaginating && currentPaginationPage <= totalPaginationPages {
            alamofireQuery.executeGetQuery(){
                (result: Result<ArticleModel,Error>) in
                switch result {
                case .success(let response):
                    self.currentPaginationPage  = 1
                    self.totalPaginationPages = response.totalPages
                    
                    self.newsArray.append(contentsOf: response.articles)
                    DispatchQueue.main.async {
                        self.dataSource.models = self.newsArray
                        self.tableView.reloadData()
                    }
                case .failure(let error):
                    print(error)
                }
                completionHandler()
            }
        }
    }

and this is the executeQuery function inside my Alamofire file:

func executeGetQuery<T>(completion: @escaping (Result<T, Error>) -> Void) where T: Codable {
        isPaginating = true
        AF.request(url, method: .get, headers: headers).responseData(completionHandler: { response in
            do {
                switch response.result {
                case .success:
                    completion(.success(try JSONDecoder().decode(T.self, from: response.data ?? Data())))
                    self.isPaginating = false
                case .failure(let error):
                    completion(.failure(error))
                }
            } catch let error {
                completion(.failure(error))
                self.isPaginating = false
            }
        })
    }

CodePudding user response:

The thing that comes to my mind is that you need to decode totalPages as optional.

let totalPages: Int?

CodePudding user response:

why it happens only when fetching from the 2nd time forward

Check JSON data of the 2nd time response. Cause according to the error message:

keyNotFound(CodingKeys(stringValue: "total_pages", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "total_pages", intValue: nil) ("total_pages").", underlyingError: nil))

, total_pages is missing.

It might be a backend bug, depending on whether it makes sense that an ArticleModel doesn't has a total_pages.

If it is intended, then make totalPages optional:

let totalPages: Int?
  • Related