Reputation: 91
I am having a minor issue parsing the JSON from reddit.
Simplified reddit JSON response:
{
"kind": "Listing",
"data": {
"after": "t3_c2wztu",
"before": null,
"children": [
{
"data": {
"all_awardings": [],
"approved_at_utc": null,
"approved_by": null
},
"kind": "t3"
}
],
"dist": 25,
"modhash": ""
}
}
I simplified the JSON as it can get 4000 lines long, mostly repeating children objects.
When we look at the second "data" key inside the "children" object, that's when we get all the needed data, like images, usernames, etc..
My structs in Swift are as follows: Seeing that the JSON is nested, I believe I need to create either nested structs or multiple structs to help drill down through the JSON. Am I correct I that?
struct Model: Decodable {
let data : Children?
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
struct Children: Decodable {
let data: [Child]?
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
//MARK - there is MORE than author and full name, we are just doing this for ease of parsing.
struct Child: Decodable {
let author : String?
let authorFullname : String?
enum CodingKeys: String, CodingKey {
case author = "author"
case authorFullname = "author_fullname"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
author = try values.decodeIfPresent(String.self, forKey: .author)
authorFullname = try values.decodeIfPresent(String.self, forKey: .authorFullname)
}
}
Now here is my URLSession call:
class NetworkManager {
static var shared: NetworkManager = { return NetworkManager() }()
private init(){}
public func getFrontPage(completion: @escaping (Model?) -> Void) {
guard let url = URL(string: "https://www.reddit.com/r/all/.json") else { fatalError("Invalid URL") }
URLSession.shared.dataTask(with: url) { data, response, err in
if(err != nil) {
completion(nil)
}
guard let httpResponse = response as? HTTPURLResponse else { fatalError("URL Response Error") }
switch httpResponse.statusCode {
case 200:
if let data = data {
do {
let data = try JSONDecoder().decode(Model.self, from: data)
completion(data)
} catch {
print("ERROR DECODING JSON \(err.debugDescription)")
}
}
break
default:
completion(nil)
break
}
}.resume()
}
And finally in ViewDidLoad:
var redditData = [Model]()
override func viewDidLoad() {
super.viewDidLoad()
NetworkManager.shared.getFrontPage { (items) in
// self.redditData = items?.data
print(items)
DispatchQueue.main.async {
// update the UI
}
}
}
Heres my current error I can't get past:
self.redditData = items?.data gives me an error:
Cannot assign value of type 'Children?' to type '[Model]'
Questions: 1. is my struct layout right? Don't worry about the actual data, like usernames and all, I shortened it for this post.
am I "drilling" down through the JSON correctly in my network manager function?
How do I fix my error in my viewController class?
Sorry for such a long topic!!
Upvotes: 2
Views: 475
Reputation: 12988
You've just got your types mixed up a little.
The type of your completion for getFrontPage
is (Model?) -> Void
So let's look at where you use it.
NetworkManager.shared.getFrontPage { (items) in
// items is Model? so...
// items?.data is of type Children?
// BUT self.redditData is of type [Model]
// self.redditData = items?.data
print(items)
DispatchQueue.main.async {
// update the UI
}
}
The model structs you showed didn't match the example JSON. Here's some structs that will work. I'm not familiar with the Reddit API so these may need to be tweaked.
struct Model : Decodable {
let kind: String
let data: ListingData
}
struct ListingData: Decodable {
let after: String
let before: String?
let children: [Child]
let dist: Int
let modhash: String
}
struct ChildData : Decodable {
let allAwardings: [String]
let approvedAtUtc: String?
let approvedBy: String?
}
struct Child: Decodable {
let data: ChildData
let kind: String?
}
You can avoid all the CodingKey
stuff if you use .convertFromSnakeCase
on JSONDecoder
. Just change getFrontPage
to decode like this:
let decoder = try JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let model = try decoder.decode(Model.self, from: data)
completion(model)
Now, just make sure the types match for self.redditData
and the field on the model you are interested in!
var self.redditData = [Child]()
NetworkManager.shared.getFrontPage { (model) in
self.redditData = model?.data.children ?? []
print(items)
DispatchQueue.main.async {
// update the UI
}
}
Hope this helps!
Upvotes: 3