URL JSON model configuration and load error

Hello, I'm a beginner who started developing. Please understand the dirty coding.
There is an error when I try to configure url json model and create a view model and display it as a view. (use ForEach - List)
Would it be possible to get some help?

error msg:

typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: , debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))

my json:

{
  "pol": [
    {
      "title": "t1",
      "url": "url1",
      "image_url": "url.3749063.jpg",
      "contents": "c2",
      "date": "2022-10-29 08:02:12"
    },
    {
      "title": "title2",
      "url": "url2",
      "image_url": "url.3749062.jpg",
      "contents": "c2",
      "date": "2022-10-29 08:02:11"
    },
    {
      "title": "title3",
      "url": "url3",
      "image_url": "url.3749062.jpg",
      "contents": "c3",
      "date": "2022-10-29 08:02:11"
    }
  ],
  "eco": [
    {
      "title": "t1",
      "url": "url1",
      "image_url": "url.3749063.jpg",
      "contents": "c2",
      "date": "2022-10-29 08:02:12"
    },
    {
      "title": "title2",
      "url": "url2",
      "image_url": "url.3749062.jpg",
      "contents": "c2",
      "date": "2022-10-29 08:02:11"
    },
    {
      "title": "title3",
      "url": "url3",
      "image_url": "url.3749062.jpg",
      "contents": "c3",
      "date": "2022-10-29 08:02:11"
    }
  ],
  "soc": [
    {
      "title": "t1",
      "url": "url1",
      "image_url": "url.3749063.jpg",
      "contents": "c2",
      "date": "2022-10-29 08:02:12"
    },
    {
      "title": "title2",
      "url": "url2",
      "image_url": "url.3749062.jpg",
      "contents": "c2",
      "date": "2022-10-29 08:02:11"
    },
    {
      "title": "title3",
      "url": "url3",
      "image_url": "url.3749062.jpg",
      "contents": "c3",
      "date": "2022-10-29 08:02:11"
    }
  ]
}

NewsModel.swift:

import Foundation

struct NewsModel: Hashable, Codable {
    let pol: News
    let eco: News
    let soc: News
}

struct News: Hashable, Codable {
    let title: String
    let url: String
    let image_url: String
    let contents: String
    let date: String
}

NewsViewModel

import Foundation

class NewsViewModel: ObservableObject {
    @Published var news: [NewsModel] = []
    func fetch() {
        guard let url = URL(string: "--url input--") else {
            return
        }
        let task = URLSession.shared.dataTask(with: url) { [weak self ] data, _, error in
            guard let data = data, error == nil else {
                return
            }
            //convert JSON
            do {
                let news = try JSONDecoder().decode([NewsModel].self, from: data)
                DispatchQueue.main.async {
                    self?.news = news
                }
            }
            catch {
                print(error)
            }
        }
        task.resume()
    }
}

SampleView.swift

import SwiftUI

struct SampleView: View {
    @StateObject var newsViewModel = NewsViewModel()
    var body: some View {
        NavigationView() {
            List() {
                ForEach(newsViewModel.news, id: \.self) { news in
                    HStack(){
                        Text(news.eco.title)
                    }
                }
            }
            .foregroundColor(.white)
            .onAppear{
                newsViewModel.fetch()
            }
        }
    }
}

struct SampleView_Previews: PreviewProvider {
    static var previews: some View {
        SampleView()
    }
}

Sorry for the brief response (I’m on my phone) but I’m happy to elaborate if required once I’m at my laptop again.

The issue is that you are decoding the JSON as an array of NewsModel, whereas the JSON data is actually a NewsModel but where each property (’pol’ etc) is an array of News instead of just a single News.

The changes you need to make are:

  1. Replace [NewsModel].self with NewsModel.self in your call to JSONDecoder().decode
  2. Change pol, eco and soc from a type of News to a type of [News]
  3. Update your SwiftUI view code to either display the news in sections (one foreach for newsViewModel.pol, one for newsViewModel.eco etc). Or have all news items in one big list by having a ForEach iterate over newsViewModel.pol + newsViewModel.eco + newsViewModel.soc

Hopefully that helps, and feel free ask if you need any help implementing those steps or if any of them aren’t working for you.

1 Like

Spot-on answer from @stackotter.

In addition you may change the "date" field type from String to Date if you do this:

let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
decoder.dateDecodingStrategy = .formatted(formatter)

Also if "url" fields are real URL's (like "https://www.apple.com") you may change their types from String to URL.

2 Likes