iosjsonswiftreddit

Decoding JSON response (post comments) from Reddit API


I get Reddit post comments from Reddit API (for one post from specific subreddit) in JSON, then parse JSON via Structs. When I try to output decoded comments I get an error:

Error decoding Json comments - typeMismatch(Swift.Dictionary<Swift.String, Any>,
Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))

Maybe I missed something in my structs or mismatched types in Repository getComments method. Please advise.

enum RequestURL {
    
    case top(sub: String, limit: Int)
    case postAt(sub: String, id: String)
    
    var url: String {
        switch self {
        case .top(let sub, let limit):
            return "https://www.reddit.com/r/\(sub)/top.json?limit=\(limit)"
        case .postAt(let sub, let id):
            return "https://www.reddit.com/r/\(sub)/comments/\(id).json"
        }
    }
}

class HTTPRequester {
        
        init() {}
        
        func getData (url: RequestURL, completion: @escaping(Data?) -> Void) {
            
            guard let url = URL(string: url.url) else {
                print("Error: Request URL is nil!")
                completion(nil)
                return
            }
            
            URLSession.shared.dataTask(with: url) {data,_,error in
                guard let jsonData = data else {
                    print(error ?? "Error")
                    completion(nil)
                    return
                }
                completion(jsonData)
            }.resume()
        }
    }


class Service {
    
    init() {}
    
    func decodeJSONComments(url: RequestURL, completion: (@escaping (_ data: CommentListing?) -> Void)) {
        
        HTTPRequester().getData(url: url) { jsonData in
            do {
                let postsResponse = try JSONDecoder().decode(CommentListing.self, from: jsonData!)
                print(postsResponse)
                completion(postsResponse)
            } catch {
                print("Error decoding Json comments - \(error)")
                completion(nil)
            }
        }
    }
}

class Repository {
    
    init() {}
    
    func getComments(sub: String, postId: String, completion: (@escaping ([RedditComment]) -> Void)) {
        Service().decodeJSONComments(url: RequestURL.postAt(sub: sub, id: postId)) { (comments: CommentListing?) in
            
            var commentsList = [CommentData]()
            commentsList = (comments?.data.children) ?? []
            
            let mappedComs = commentsList.map { (comment) -> RedditComment in
                
                return RedditComment(
                    id: comment.data.id,
                    author: comment.data.author,
                    score: comment.data.score,
                    body: comment.data.body)
            }
            completion(mappedComs)
        }
    }
}

class UseCase {
    
    func createComments(sub: String, postId: String, completion: (@escaping (_ data: [RedditComment]) -> Void)) {
        Repository().getComments(sub: sub, postId: postId) { (comments: [RedditComment]) in
            completion(comments)
        }
    }
}

UseCase().createComments(sub: "ios", postId: "4s4adt") { comments in
   print(comments)
}

JSON structure


Solution

  • You mentioned that you have the following error:

    typeMismatch(Swift.Dictionary<String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
    

    Let's deconstruct that:

    Here are two helpful things you can read from that:

    First, the debugDescription: Expected to decode Dictionary<String, Any> but found an array instead.

    This means that you are trying to decode a dictionary but the JSON contains an array. Note, that most normal types that you mark as Codable will encode to a dictionary.

    Second, the codingPath, which is in your case an empty array ([]), meaning this issue is right at the root type that you are trying to decode.

    Now let's have a look at the Postman response that you posted. Right in line 1, you can see that the outermost container (line 1) is an array.

    But when you are decoding, you are decoding a CommentListing, which uses a keyed container (dictionary).

    So to fix this, you have to decode an array of CommentListings.

    let postsResponse = try JSONDecoder().decode([CommentListing].self, from: jsonData!)