How to decode this JSON data in Swift - json

I am trying to get the title from json data but I am getting an error. Here is my model object
struct SpaceNewsModel: Identifiable, Codable {
var id = UUID()
let title: String
let url: String
let source: String
}
And when I get to this point in my networking, I get an error.
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
if error != nil {
print(error!)
} else {
let decoder = JSONDecoder()
if let safeData = data {
do {
let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
print(astroNews[0].title)
} catch {
print("DEBUG: Error getting news articles \(error.localizedDescription)")
}
}
let httpResponse = response as? HTTPURLResponse
// print(httpResponse)
}
})
task.resume()
}
My main problem is with the line print(astroNews[0].title I am not formatting it right and I don't know how to access this data whether in an array or otherwise.
Here is the returned JSON data after making the request on the website postman.
[
{
"title": "Wales' new £2bn space strategy hopes",
"url": "https://www.bbc.co.uk/news/uk-wales-60433763",
"source": "bbc"
},
{
"title": "Could Port Talbot become a centre of space tech? Video, 00:02:02Could Port Talbot become a centre of space tech?",
"url": "https://www.bbc.co.uk/news/uk-wales-60471170",
"source": "bbc"
}
]
Normally there would be a title for each that I can access but here it is just the data. So to summarize, how would I get the first or second title in this JSON data? Because astroNews[0].title does not work.
EDIT: I want to clarify that if I were to decode an empty struct, I would not get any errors.
struct EMPTYSTRUCT: Codable {
}
But as soon as I add any variable like let title: String I get an error. I believe something is wrong in the formatting of the JSON because usually the JSON would look something this
{
"coord": {
"lon": -95.3633,
"lat": 29.7633
}
}
where I could name the struct "coord" however no such names exist in the returned JSON I'm working with. Only the variable names. The confusion is how would I construct my SpaceNewModel file to work with the returned JSON with no apparent object name.

With reference to this answer.
Just change your structure to
struct SpaceNewsModel: Identifiable, Codable {
let id = UUID()
let title: String
let url: String
let source: String
}
This is not the solution but a workaround; if you want to solve the issue, refer to this answer

Related

How would I print the title property in this returned JSON in Swift using URLSession and dataTask?

I am working with an api and getting back some strangely formatted JSON.
[
{
"title": "Wales' new £2bn space strategy hopes",
"url": "https://www.bbc.co.uk/news/uk-wales-60433763",
"source": "bbc"
},
{
"title": "Could Port Talbot become a centre of space tech? Video, 00:02:02Could Port Talbot become a centre of space tech?",
"url": "https://www.bbc.co.uk/news/uk-wales-60471170",
"source": "bbc"
},
]
As you can see, there is no object name I can latch on to. I've tried making a model like this
struct SpaceNewsModel: Identifiable, Codable {
var id = UUID()
let title: String
let url: String
let source: String
}
But once I get to using JSONDeocder() with the following code
let decoder = JSONDecoder()
if let safeData = data {
do {
let astroNews = try decoder.decode(SpaceNewsModel.self, from: safeData)
print(astroNews)
} catch {
print("DEBUG: Error getting news articles \(error.localizedDescription)")
}
}
I get the error DEBUG: Error getting news articles The data couldn’t be read because it isn’t in the correct format.
So how would you go about printing out each title, url, or source to the console? I've worked with JSON before and they are usually formatted differently.
I found a workaround for this. Removing the identifiable protocol lets me access the data. Like so
struct SpaceNewsModel: Codable {
let title: String
let url: String
let source: String
}
Then I can use decoder as normal
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
if error != nil {
print(error!)
} else {
let decoder = JSONDecoder()
if let safeData = data {
do {
let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
print(astroNews[0].title)
} catch {
print("DEBUG: Error getting news articles \(error)")
}
}
I did change let astroNews = try decoder.decode(SpaceNewsModel.self, from: safeData) to let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
Even with changing it to array type or not, as long as I had my SpaceNewsModel following the identifiable protocol, it would NOT work. It's a strange workaround, but it works for now.
in addition to
let astroNews = try decoder.decode([SpaceNewsModel].self, from: safeData)
use this common approach for your SpaceNewsModel. Having it Identifiable is very useful in SwiftUI.
struct SpaceNewsModel: Identifiable, Codable {
let id = UUID() // <-- here, a let
let title: String
let url: String
let source: String
enum CodingKeys: String, CodingKey { // <-- here
case title,url,source
}
}

The Data Couldn't Be Read Because It Isn't in The Correct Format?

I'm pretty sure my model is correct based on my data, I cannot figure out why I am getting the format error?
JSON:
{
"1596193200":{
"clientref":1,
"type":"breakfast"
},
"1596200400":{
"clientref":0,
"type":"lunch"
},
"1596218400":{
"clientref":2,
"type":"dinner"
}
}
model:
struct Call: Decodable {
let clientref: Int?
let type: String?
}
edit updated question with the code for decoding the json data from the URL:
class CallService {
static let shared = CallService()
let CALLS_URL = "url.com/Calls.json"
func fetchCalls(completion: #escaping ([Call]) -> ()) {
guard let url = URL(string: CALLS_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error.localizedDescription)
return
}
guard let data = data else {return}
do {
let call = try JSONDecoder().decode([Call].self, from: data)
completion(call)
} catch let error {
print("Failed to create JSON with error: ", error.localizedDescription)
}
}.resume()
}
}
I strongly suggest to learn how to debug: it includes where to look, what info to get, where to get them, etc, and at the end, fix it.
That's a good thing that you print the error, most beginner don't.
print("Failed to create JSON with error: ", error.localizedDescription)
=>
print("Failed to create JSON with error: ", error)
You'll get a better idea.
Second, if it failed, print the data stringified. You're supposed to have JSON, that's right. But how often do I see question about that issue, when it fact, the answer wasn't JSON at all (the API never stated it will return JSON), the author were facing an error (custom 404, etc.) and did get a XML/HTML message error etc.
So, when the parsing fails, I suggest to do:
print("Failed with data: \(String(data: data, encoding: .utf8))")
Check that the output is a valid JSON (plenty of online validators or apps that do that).
Now:
I'm pretty sure my model is correct based on my data,
Well, yes and no.
Little tip with Codable when debuting (and not using nested stuff): Do the reverse.
Make your struct Codable if it's not the case yet (I used Playgrounds)
struct Call: Codable {
let clientref: Int?
let type: String?
}
do {
let calls: [Call] = [Call(clientref: 1, type: "breakfast"),
Call(clientref: 0, type: "lunch"),
Call(clientref: 2, type: "dinner")]
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted]
let jsonData = try encoder.encode(calls)
let jsonStringified = String(data: jsonData, encoding: .utf8)
if let string = jsonStringified {
print(string)
}
} catch {
print("Got error: \(error)")
}
Output:
[
{
"clientref" : 1,
"type" : "breakfast"
},
{
"clientref" : 0,
"type" : "lunch"
},
{
"clientref" : 2,
"type" : "dinner"
}
]
It doesn't look like. I could only used an array to put various calls inside a single variable, and that's what you meant for decoding, because you wrote [Call].self, so you were expecting an array of Call. We are missing the "1596218400" parts. Wait, could it be a dictionary at top level? Yes. You can see the {} and the fact it uses "keys", not listing one after the others...
Wait, but now that we printed the full error, does it make more sense now?
typeMismatch(Swift.Array<Any>,
Swift.DecodingError.Context(codingPath: [],
debugDescription: "Expected to decode Array<Any> but found a dictionary instead.",
underlyingError: nil))
Fix:
let dictionary = try JSONDecoder().decode([String: Call].self, from: data)
completion(dictionary.values) //since I guess you only want the Call objects, not the keys with the numbers.
From the code you provided it looks like you are trying to decode an Array<Call>, but in the JSON the data is formatted as a Dictionary<String: Call>.
You should try:
let call = try JsonDecoder().decode(Dictionary<String: Call>.self, from: data)

Swift 4 json decode with top level array

While getting JSON data from my API, I can't get it to decode properly.
[
{
"success": "true",
"message": "testtt"
}
]
This is what my API output looks like.
As we can see, my PHP outputs the values as an top level array.
How can I read out this information in Swift 4?
let json = try JSONDecoder().decode([API].self, from: data)
returns:
success: "true", message: "testtt"
This is what the struct looks like:
struct API: Decodable{
let success: String
let message: String
init(jsont: [String: Any]){
success = jsont["success"] as? String ?? ""
message = jsont["message"] as? String ?? ""
}
}
But then I don't know how to read out this data further.
Any ideas?
There is no need to creat a custom initialiser. You just use the Array type [API].self when decoding your json:
struct API: Decodable{
let success: String
let message: String
}
let dataJSON = Data("""
[
{
"success": "true",
"message": "testtt"
}
]
""".utf8)
do {
if let result = try JSONDecoder().decode([API].self, from: dataJSON).first {
print(result.success)
print(result.message)
}
} catch {
print(error)
}
If you want to access make one more struct like
struct data: Decodable{
let API: [API]
}
Then in your program you must decode like below above
let json = try JSONDecoder().decode(data.self, from: data)
and access to them
data.API[i].success
data.API[i].message

Parsing JSON With Codable in Swift

I am attempting to parse JSON with codable in Swift. I have successfully done this before, but I have a more complicated JSON object with some arrays and I am having trouble.
Here is my JSON:
{
"data": [ {
"type":"player",
"id":"account.7e5b92e6612440349afcc06b7c390114",
"attributes": {
"createdAt":"2018-04-06T04:59:40Z",
"name":"bob",
"patchVersion":"",
"shardId":"pc-na",
"stats":null,
"titleId":"bluehole-pubg",
"updatedAt":"2018-04-06T04:59:40Z"
},
"relationships": {
"assets": {
"data":[]
},
"matches": {
"data": [
{"type":"match","id":"3e2a197a-1453-4569-b35b-99e337dfabc5"},
{"type":"match","id":"15f41d2f-9da2-4b95-95ca-b85e297e14b7"},
{"type":"match","id":"a42c496c-ad92-4d3e-af1f-8eaa2e200c2b"}
{"type":"match","id":"b6e33df5-4754-49da-9a0f-144842bfc306"},
{"type":"match","id":"5b357cd1-35fe-4859-a2d7-48f263120bbd"},
{"type":"match","id":"99fc5f81-c24c-4c82-ae03-cd21c94469c0"},
{"type":"match","id":"1851c88e-6fed-48e8-be84-769f20f5ee6f"},
{"type":"match","id":"e16db7ea-520f-4db0-b45d-649264ac019c"},
{"type":"match","id":"6e61a7e7-dcf5-4df5-aa88-89eca8d12507"},
{"type":"match","id":"dcbf8863-9f7c-4fc9-b87d-93fe86babbc6"},
{"type":"match","id":"0ba20fbb-1eaf-4186-bad5-5e8382558564"},
{"type":"match","id":"8b104f3b-66d5-4d0a-9992-fe053ab4a6ca"},
{"type":"match","id":"79822ea7-f204-47f8-ae6a-7efaac7e9c90"},
{"type":"match","id":"1389913c-a742-434a-80c5-1373e115e3b6"}
]
}
},
"links": {
"schema":"",
"self":"https://api.playbattlegrounds.com/shards/pc-na/players/account.7e5b92e6612440349afcc06b7c390114"
}
}],
"links": {
"self":"https://api.playbattlegrounds.com/shards/pc-na/players?filter[playerNames]=dchilds64"
},
"meta":{}
}
Here are the models I am using:
public struct PlayerResponse: Codable {
let data: [Player]
}
For Player:
public struct Player: Codable {
let type: String
let id: String
let attributes: Attributes
let relationships: Relationships
}
For Attributes:
public struct Attributes: Codable {
let name: String
let patchVersion: String
let shardId: String
let titleId: String
let updatedAt: String
}
For Relationships:
public struct Relationships: Codable {
let matches: Matches
}
For Matches:
public struct Matches: Codable {
let data: [Match]
}
For Match:
public struct Match: Codable {
let type: String
let id: String
}
Decoding as:
let players = try decoder.decode([Player].self, from: jsonData)
I have this function which runs my network request:
func getPlayerData(for name: String, completion: ((Result<[Player]>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "api.playbattlegrounds.com"
urlComponents.path = "/shards/\(regionShard.rawValue)/players"
let playerNameItem = URLQueryItem(name: "filter[playerNames]", value: "\(name)")
urlComponents.queryItems = [playerNameItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
print(url)
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/vnd.api+json", forHTTPHeaderField: "Accept")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonData = responseData {
let decoder = JSONDecoder()
do {
let players = try decoder.decode([Player].self, from: jsonData)
completion?(.success(players))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
The problem I am facing is that I get this error when I try to run the network request:
I think there is an issue with my codable structs, but I'm not sure. Could someone point me in the right direction to look for my error?
I suggest you build this up from the ground, since the errors of JSONDecoder (as with any compiler) get worse the more involved your structures are. Let's see how far we get:
Your Match struct is pretty sound:
public struct Match: Codable {
let type: String
let id: String
}
let decoder = JSONDecoder()
let mData = """
{"type":"match","id":"3e2a197a-1453-4569-b35b-99e337dfabc5"}
""".data(using:.utf8)!
let match = try! decoder.decode(Match.self, from:mData)
print(match)
No unexpected problems here. Shortening Matches a bit you already get your first error, rather an unexpected one
public struct Matches: Codable {
let data: [Match]
}
let mtchsData = """
{
"data": [
{"type":"match","id":"3e2a197a-1453-4569-b35b-99e337dfabc5"},
{"type":"match","id":"15f41d2f-9da2-4b95-95ca-b85e297e14b7"},
{"type":"match","id":"a42c496c-ad92-4d3e-af1f-8eaa2e200c2b"}
{"type":"match","id":"b6e33df5-4754-49da-9a0f-144842bfc306"},
{"type":"match","id":"5b357cd1-35fe-4859-a2d7-48f263120bbd"}
]
}
""".data(using:.utf8)!
do {
let mtches = try decoder.decode(Matches.self, from:mtchsData)
print(mtches)
} catch {
print(error)
}
will print the following error:
"dataCorrupted(Swift.DecodingError.Context(codingPath: [],
debugDescription: "The given data was not valid JSON.",
underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840
"Badly formed array around character 233."
UserInfo={NSDebugDescription=Badly
formed array around character 233.})))\n"
This is a trivial error, you are missing a comma on line 3 of the data Array. Adding that all will go well, but if it comes like this from your service you will have to fix it first.
I guess you get the idea and know your way about building up the structure successively. On the top level you will notice that your top level structure goes beyond an array of Player, it is actually a Dictionary with "data" as its sole key as you modelled correctly in PlayerResponse as #AnkitJayaswal pointed out already. That makes two errors already, those are the ones I managed to spot easily, but as I suggested before you should continue the build up of tests, that way you will know that the "lower" levels parse correctly and can concentrate on the problem at hand.
All of the above works easily in a Playground and there is no need to actually call the WebService in the process. Of course you will have to import Cocoa, but you already knew that. Anyway it always helps to reduce the level of complexity by splitting up your problem into smaller parts.
As I can see your whole player response is in key data. And your parsing player info with Player codable struct directly rather than data key which is used in PlayerResponse codable struct.
To resolve this update your code as:
let players = try decoder.decode(PlayerResponse.self, from: jsonData)
Hope this will solve your problem.

Looping through JSON object in Swift

I got this JSON object which I sent from my server to my Swift application.
{
"625289": {
"id": 1,
"subject": "Hello World"
},
"625277": {
"id": 2,
"subject":"Bye World!"
}
}
So i tried to get the subject for each result ("625289" and "625277") by doing as below in my Swift class:
struct Resultat : Decodable {
let subject: String
}
var result = [Resultat]()
let urlll = URL(string:"http://localhost:8888/api/pouet.php")
URLSession.shared.dataTask(with: urlll!) { (data, response, error) in
do {
print("coucoulol")
//print(response)
self.result = try JSONDecoder().decode([Resultat].self, from: data!)
print(self.result)
for eachTicket in self.result {
print(eachTicket.subject)
}
} catch {
print("error"+error.localizedDescription)
}
}.resume()
However, when I tried to execute the code, it says "The data couldn’t be read because it isn’t in the correct format." From what I understand, the loop for in the code is suffice to get the values in the arrays or maybe I'm wrong. Any help is appreciated, thanks.
The root object is a dictionary. You can decode the object to [String:Resultat]. The dictionary contains also dictionaries. An array is not involved.
struct Resultat : Decodable {
let subject: String
let id : Int
}
...
let result = try JSONDecoder().decode([String:Resultat].self, from: data!)
for (key, value) in result {
print(key, value.subject)
}
You can try using SwiftyJSON below
$0.0 = Key
$0.1 = value
let data = JSON(result)
data.dictionaryValue.forEach({
print($0.1)
})