1

I'm trying to parse JSON response using Codable but it gives me error. I tried to refer from the below stack overflow link but did not work. how do I parse Any in dictionary using swift

Below is my code, not sure where I'm wrong in this.

> enum JSONError: String,Error {
>         case NoData = "ERROR: no data"
>         case ConversionFailed = "ERROR: conversion from JSON failed"
>     }

struct Owner : Decodable {
    let full_name : String
    let html_url:String
    let follower:follower
}

struct follower : Decodable {
    let followers_url : String
}


func jsonParser() {
        let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
        guard let endpoint = NSURL(string: urlPath) else {
            print("Error creating endpoint")
            return
        }

        let request = NSMutableURLRequest(url:endpoint as URL)
        URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
            do {
                guard let data = data else {
                    throw JSONError.NoData
                }

                let jsonResponse = try JSONSerialization.jsonObject(with:
                    data)

                let entries = try! JSONDecoder().decode([Owner].self, from: jsonResponse as! Data)
                print(jsonResponse)
            } catch let error as JSONError {
                print(error.rawValue)
            } catch let error as NSError {
                print(error.debugDescription)
            }
            }.resume()
    }

I need to get 3 information from this response - full name, html url and followers. Link for web api

https://api.github.com/search/repositories?q=language:ruby

Please latest have a look at the code.

Below is the error message :

'__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). 2019-02-09 16:17:42.062971+0530 PhotoViewwer[13342:259997] Could not cast value of type '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580).

Thanks

4
  • Which error you are facing? Commented Feb 9, 2019 at 10:29
  • @vadian - Error message - '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). 2019-02-09 16:17:42.062971+0530 PhotoViewwer[13342:259997] Could not cast value of type '__NSDictionaryI' (0x102965a98) to 'NSData' (0x102964580). Commented Feb 9, 2019 at 10:48
  • There are many, many mistakes in the code, please see my answer. For example the JSONSerialization line is nonsense if you are going to use Decodable. This causes the error by the way. Commented Feb 9, 2019 at 10:49
  • @vadian - going via ur code so that I will come to know my mistakes. Thanks Vadian. Commented Feb 9, 2019 at 10:50

2 Answers 2

1

Please learn to read JSON. It's pretty easy. There are only two collection types, array ([]) and dictionary ({})

Your structs are wrong.

In the root dictionary of the JSON there is an array of dictionaries for key items.
In each dictionary there are keys full_name and owner (here is the location of the Owner struct).
A dictionary for key follower does not exist.

These structs represent the JSON correctly

struct Response : Decodable {
    let items : [Item]
}

struct Item : Decodable {
    let fullName : String
    let owner : Owner
}

struct Owner : Decodable {
    let htmlUrl : URL // URL strings can be decoded directly into URL
    let followersUrl : URL
}

Add a completion handler to your function and use an enum as result type. The failure case returns all real errors. An URLRequest is redundant. Just pass the URL. The JSONSerialization line is pointless.

The convertFromSnakeCase strategy converts snake_cased keys to camelCased struct members

enum Result {
    case success(Response), failure(Error)
}

func jsonParser(completion: @escaping (Result) -> Void) {
    let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
    URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
        if let error = error { completion(.failure(error)); return }
        do {
            let decoder = JSONDecoder()
            decoder.keyDecodingStrategy = .convertFromSnakeCase
            let entries = try decoder.decode(Response.self, from: data!)
            completion(.success(entries))
        } catch {
            completion(.failure(error))
        }
    }.resume()
}

And call it

jsonParser { result in
    switch result {
    case .success(let entries) : print(entries)
    case .failure(let error) : print(error)
    }
}

Basically never use NS... classes if there are native equivalents, here URL for NSURL and URLRequest for NS(Mutable)URLRequest

Edit:

In Swift 5 the syntax becomes more convenient using the native Result type. It is able to convert the throwing expression

enum Result {
    case success(Response), failure(Error)
}  

func jsonParser(completion: @escaping (Result<Response,Error>) -> Void) {
    let endpoint = URL(string:"https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc")!
    URLSession.shared.dataTask(with: endpoint) { (data, response, error) in
        if let error = error { completion(.failure(error)); return }

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase
        completion(Result{ try decoder.decode(Response.self, from: data!) })

    }.resume()
}
Sign up to request clarification or add additional context in comments.

2 Comments

above is working good but in one case it is failing.Incase full name is null it fails, how do I handle this case - I want full name to be nil instead of failing while parsing the json.
Declare the struct member as optional: let fullName : String?
1

You need

func jsonParser() {
    let urlPath = "https://api.github.com/search/repositories?q=language:ruby&sort=stars&order=desc"
    guard let endpoint = NSURL(string: urlPath) else {
        print("Error creating endpoint")
        return
    }

    let request = NSMutableURLRequest(url:endpoint as URL)
    URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
        do {

            let dec = JSONDecoder()
            dec.keyDecodingStrategy = .convertFromSnakeCase
            let entries = try dec.decode(Root.self, from:data!)
            print(entries)
        } catch {
            print(error)
        }
        }.resume()
}

struct Root : Decodable {
    let items:[Item]
}
struct Owner: Codable {
    let login: String
    let id: Int
    let nodeId: String
    let avatarUrl: String
    let gravatarId: String
    let url, htmlUrl, followersUrl: String
    let followingUrl, gistsUrl, starredUrl: String
    let subscriptionsUrl, organizationsUrl, reposUrl: String
    let eventsUrl: String
}

struct Item: Codable {
    let fullName : String
    let htmlUrl:String
    let owner: Owner
}

You shouldn't cast the response to data here

from: jsonResponse as! Data)

as it will crash

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.