Parsing JSON returning nil

I am parsing JSON from a subreddit in Swift. The URL is:

https://www.reddit.com/r/cat/top.json

The objects are returning nil (print me in console "Your TableView did not load data")

I feel like perhaps my nesting is incorrect or something with the network call, thanks.

struct GalleryData: Codable {
    let data: ListingData
}

struct ListingData: Codable {
    let children: [Child]
}

struct Child: Codable {
    let data: ChildData
}

struct ChildData: Codable {
    let subreddit: String
    
    let author: String
    let title: String
    let thumbnail: String
    let url_overridden_by_dest: String
    
}
import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    var dataArray = [GalleryData]()

    let baseURL = "https://www.reddit.com/r/"
    
    // START SEARCHBAR
    
    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var labelError: UILabel!
    
    var filteredArray = [GalleryData]() {
        
        didSet {
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        }
        
    }
    
    // END SEARCHBAR
    
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        tableView.delegate = self
        tableView.dataSource = self
        
        // START SEARCHBAR
        
        searchBar.delegate = self
        searchBar.placeholder = "Search here..."
        
        // END SEARCHBAR
        
        downloadJSON()
        
        tableView.tableFooterView = UIView(frame: CGRect.zero)
        
    }
    
    
    // START SEARCHBAR
    
    func error() {
        labelError.textColor = UIColor.white
        labelError.backgroundColor = UIColor.red
        labelError.text = "no records found"
    }
    func ok() {
        labelError.backgroundColor = UIColor.white
        labelError.text = ""
    }
    
    // END SEARCHBAR
    
    
}




extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        
        if filteredArray.count != 0 {
            ok()
            return filteredArray.count
        }
        else {
            error()
            return filteredArray.count
        }
        
        
        
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
        
        cell.textLabel?.text = filteredArray[indexPath.row].data.children[1].data.author.capitalized
        
        
        let view = UIView()
        view.backgroundColor = UIColor.systemGray4
        cell.selectedBackgroundView = view
        
        if indexPath.row % 2 == 0 {
            cell.backgroundColor = UIColor.systemGray6
        }
        else {
            cell.backgroundColor = UIColor.white
        }
        
        return cell
        
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        performSegue(withIdentifier: "showDetails", sender: self)
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? DetailViewController {
            destination.galleryDataDetail = filteredArray[(tableView.indexPathForSelectedRow?.row)!]
        }
    }
    
    
    func downloadJSON() {
        
        let url = URL(string: "\(baseURL)cat/top.json")
        
        URLSession.shared.dataTask(with: url!) { [unowned self] (data, response, error) in
            
            do {
                self.dataArray = try JSONDecoder().decode([GalleryData].self, from: data!)
                self.filteredArray = self.dataArray
            }
            catch {
                print("Your TableView did not load data")
            }
            
        }.resume()
        
    }
    
    
    
}


// START SEARCHBAR

extension ViewController: UISearchBarDelegate {
    
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        
        guard
            let searchText = searchBar.text
        else {
            return
        }
        
        if searchText == "" {
            filteredArray = dataArray
            return
        }
        
        filteredArray = dataArray.filter { $0.data.children[1].data.author.uppercased().contains(searchText.uppercased()) }
        
    }
    
    
    
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.text = ""
        downloadJSON()
        tableView.reloadData()
    }
    
    
}

// END SEARCHBAR

Let's start with the question. You said you have a problem with parsing JSON. That means, very likely I wouldn't need to care about you view. I'd only need to care about the code that actually parse the JSON. So you can actually strip the entire view controller, search bar, and all the UI shenanigans. You parse the JSON file in downloadJSON (easy enough), so you can separate that function into a new console project and just call it. So your question can become much smaller (and more attractive to the readers):

import Foundation

struct GalleryData: Codable {
  let data: ListingData
}
struct ListingData: Codable {
  let children: [Child]
}
struct Child: Codable {
  let data: ChildData
}
struct ChildData: Codable {
  let subreddit, author, title: String
  let thumbnail, url_overridden_by_dest: String
}

func downloadJSON() {
  let baseURL = "https://www.reddit.com/r/"
  let url = URL(string: "\(baseURL)cat/top.json")

  URLSession.shared.dataTask(with: url!) { (data, response, error) in
    do {
      let array = try JSONDecoder().decode([GalleryData].self, from: data!)
      print("Got data:", array)
    } catch {
      print("Your TableView did not load data")
    }
  }.resume()
}

downloadJSON()
// Prevent the program from terminating before the URLSession finishes
dispatchMain()

Now then anyone who can create an empty console Xcode project and run the (33-line) code and understand the (same) question sans the distraction.

Note that we use dispatchMain at the end. So this program won't terminate by itself, you'll need to make sure to kill it (ctrl+c) if you run the program outside of the Xcode environment.


First, note that catch is for catching errors that may arise. Since you fail the decoding process, it'll likely throw something from there. It is usually very useful to read the error for debugging purposes. So if you replace

print("Your TableView did not load data")

with

print("Data loading fails with error:", error)

It'll now print:

Data loading fails with error: typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array<Any> but found a dictionary instead.", underlyingError: nil))

It's a little verbose, but also good since it's packing a lot of information.

The error seems to be type-mismatch on coding path [] (i.e. top-level) with a handy description:

Expected to decode Array<Any> but found a dictionary instead.

Ok, so, at the top-level, the decoder is trying to decode an array, but found JSON dictionary. If you look closely at the JSON file, that is indeed the case. The top-level object is not an array of GalleryData, but a single GalleryData containing multiple Child.

So instead you need to do:

JSONDecoder().decode(GalleryData.self, from: data!)

and you should now get the data.


I notice this key: url_overridden_by_dest :scream::scream::scream:. That's no good. It's soooo unSwifty. Good thing that JSON decoder (and encoder) has an option to handle snake-case for you. So you can instead do this:

struct ChildData: Codable {
  let subreddit, author, title: String
  let thumbnail: String
  // Not snake case anymore!
  let urlOverriddenByDest: String
}

//
// When decoding:
//
let decoder = JSONDecoder()
// Let the decoder know we're dealing with snake case
decoder.keyDecodingStrategy = .convertFromSnakeCase
// The rest is the same
let data = try decoder.decode(GalleryData.self, from: data!)
let array = data.data.children
print("Got data:", array)

ou are missing the source property of the JSON alaskasworld

struct Images: Decodable {
    let images: [Image]
}

struct Image: Decodable {
    let source: Source
}

struct Source: Decodable {
    let url: URL
    let width: Int
    let height: Int 
}