Error when parsing JSON data (Swift 4 Playground) - swift4

My Swift Playground keeps returning
Error: The data couldn't be read because it isn't in the correct
format."
and I can't figure out what I'm doing wrong. Below is my code.
JSON Sample Data:
{
"meta": {
"name":"Tour of Honor Bonus Listing",
"version":"18.1.4"
},
"bonuses": [
{
"bonusCode":"AZ1",
"category":"ToH",
"name":"Anthem Tour of Honor Memorial",
"value":1,
"city":"Anthem",
"state":"AZ",
"flavor":"Flavor Text Goes Here"
}
]
}
Playground Code:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
struct JsonFile: Codable {
struct Meta: Codable {
let name: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
let url = URL(string: "http://www.tourofhonor.com/BonusData.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error: \(error.localizedDescription)")
PlaygroundPage.current.finishExecution()
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
print("Error: invalid HTTP response code")
PlaygroundPage.current.finishExecution()
}
guard let data = data else {
print("Error: missing data")
PlaygroundPage.current.finishExecution()
}
// feel free to uncomment this for debugging data
// print(String(data: data, encoding: .utf8))
do {
let decoder = JSONDecoder()
let posts = try decoder.decode([JsonFile].self, from: data)
print(posts.map { $0.meta.name })
PlaygroundPage.current.finishExecution()
}
catch {
print("Error: \(error.localizedDescription)")
PlaygroundPage.current.finishExecution()
}
}.resume()
I assume I have something in my Struct incorrect, but I can't figure out what it is.
(This paragraph is to make the submission tool happy because it says I have too much code and not enough other details. Apparently being direct and succinct is not compatible with the submission scanning function).

The struct is correct but the root object is not an array (remove the brackets)
let posts = try decoder.decode(JsonFile.self, from: data)
print(posts.bonuses.map{$0.name})

Related

Why is URLSession not returning data in playground?

I cannot understand why this URLSession is not working on my playground. The URL works fine using curl commands on my terminal, so I know it is active, but I cannot see any list of names printed on my console.
The only print I see is the one "called" after that one, it seems there must be some error, but I have no clue about it, no message. Issue must be around the URLSession but cannot get where.
UPDATE
I added an extension to data found on stack, using right after this code
let (data, response) = try await URLSession.shared.data(from: url)
data.printJson()
The data is printed, but still cannot print anything in the for loop, where it should be.
extension Data {
func printJson() {
do {
let json = try JSONSerialization.jsonObject(with: self, options: [])
let data = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
guard let jsonString = String(data: data, encoding: .utf8) else {
print("Invalid data")
return
}
print(jsonString)
} catch {
print("Error: \(error.localizedDescription)")
}
}
}
And
import Foundation
//-----------------------------------------------
//MARK: - model
//-----------------------------------------------
struct ResponseRandom: Codable {
let users: [User]
}
struct User: Codable, Identifiable {
let id: Int
let first_name: String
let email: String
// "avatar": "https://robohash.org/rationeetsit.png?size=300x300&set=set1"
let date_of_birth: String//"1958-07-18"
}
//-----------------------------------------------
//MARK: - class
//-----------------------------------------------
class HTTPRequest_randomUsers {
// #Published var users = [UserRandom]()
init() {
Task {
await loadData()
}
}
func loadData() async {
print("called")
let numberOfItems = 50
guard let url = URL(string: "https://random-data-api.com/api/v2/users?size=2&response_type=json") else {
fatalError("URL error")
}
do {
let (data, response) = try await URLSession.shared.data(from: url)
guard let response = response as? HTTPURLResponse else {
print("not valid response")
return}
guard response.statusCode == 200 else {
print("not 200 status")
return}
let decoded = try JSONDecoder().decode([User].self, from: data)
print("decoded")
await MainActor.run {
// users = decoded.users
for item in decoded {
print(item.first_name)
}
}
} catch {
print("error: \(error)")
}
}
}
//here my call
let c = HTTPRequest_randomUsers()
Network requests run asynchronously (i.e., finish later). But by default, simple Playgrounds will stop when they reach the end of their path of execution. One must tell the Playground to continue execution indefinitely in order for the asynchronous results to be retrieved successfully:
import PlaygroundSupport
and
PlaygroundPage.current.needsIndefiniteExecution = true

Data is missing from API call to Unsplashed using Swift [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
Problem:
I was following a tutorial and I cannot seem to get the data loaded when I get a API call from Unsplashed. I registered for an account and used my personal key.
I created my loadData function that should load the data from the API call:
func loadData() {
let key = "my key"
let url = "https://api.unsplash.com/photos/random/?count=30&client_id=\(key)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, error) in
guard let data = data else {
print("URLSession dataTask error", error ?? "nil")
return
}
print(data)
do{
let json = try JSONDecoder().decode([Photo].self,from: data)
print(json)
for photo in json {
DispatchQueue.main.async {
self.photoArray.append(photo)
}
}
}catch{
print("In error")
print(error.localizedDescription)
}
}.resume()
}
I present my data in the content view as follows:
import SwiftUI
struct ContentView: View {
#ObservedObject var randomImages = UnsplashData()
var body: some View {
Text("Hello, world!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I print the data before I use the JSON decoder and there seems to be data there, but when I go to get the JSON it states this error:
The data couldn’t be read because it is missing.
So is it the way I am using the API key? Or something else?
The photo structure is as follows:
struct Photo: Identifiable, Decodable {
var id: String
var alt_description: String
var urls: [String: String]
}
It's not the data that you're receiving, it's how you're decoding it that seems to be an issue.
For example, this simple playground example works just fine...
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
struct Sources: Decodable {
let raw: String
let full: String
let regular: String
let small: String
let thumbnail: String
}
struct Photo: Decodable {
let urls: [Sources]
}
let key = "don't post this, that's a bad idea"
let url = "https://api.unsplash.com/photos/random/?count=30&client_id=\(key)"
let session = URLSession(configuration: .default)
session.dataTask(with: URL(string: url)!) { (data, _, error) in
guard let data = data else {
print("URLSession dataTask error", error ?? "nil")
return
}
print(String(data: data, encoding: .utf8))
do{
let json = try JSONDecoder().decode([Photo].self,from: data)
print(json)
}catch{
print("In error")
print(error.localizedDescription)
}
}.resume()
If you change print(error.localizedDescription) to print(error) you'll generally get a more accurate description of the error

Swift loading JSON in extension of struct

Given this (partially pasted) struct:
struct InstrumentsSet: Identifiable, Decodable {
static func withJSON(_ fileName: String) -> InstrumentsSet? {
guard let url = Bundle.main.url(forResource: fileName, withExtension: "json", subdirectory: "Sets") else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
return try? JSONDecoder().decode(InstrumentsSet.self, from: data)
}
let name: String
let bpm: Double
let timeSignature: Int
var tracks: [Track]
}
And this extension:
extension InstrumentsSet {
struct Track: Identifiable, Decodable {
static func withJSON(_ fileName: String) -> InstrumentsSet.Track? {
guard let url = Bundle.main.url(forResource: fileName, withExtension: "json", subdirectory: "Sets") else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
//At this point data has the JSON loaded and decode makes it jump to the fatal error located at the call
return try? JSONDecoder().decode(InstrumentsSet.Track.self, from: data)
}
let instrumentType: InstrumentType
let startType: StartType
}
}
I can load successfully an instrument set from JSON with nested Tracks this way:
guard var instrumentSet1 = InstrumentsSet.withJSON("file-with-set") else {
fatalError("Error loading set JSON")
}
Next I'd like to add a track to the existing set by loading a json with just Track data:
guard let masterTrack = InstrumentsSet.Track.withJSON("file-with-track") else {
fatalError("Error loading track JSON")
}
When running this part I can see the JSON data loading successfully, but on this line return try? JSONDecoder().decode(InstrumentsSet.Track.self, from: data) the fatalError is triggered without further explanation. My guess is something is missing in the instrumentSet part since the function "InstrumentsSet.Track.withJSON" is called through the original InstrumentsSet struct so passing down just a track is not possible?
My question, given the struct and its extension, is it possible to load just the extension part (Track) of the struct into the let masterTrack?
Example Set JSON:
{
"name": "Set name",
"bpm": 124.00,
"timeSignature": 4,
"tracks": [
{
"instrumentType": "exsSampler",
"startType": "trigger"
}
]
}
Example Track JSON. (I've tried passing this as an Array, but still no success)
{
"instrumentType": "audioBuffer",
"startType": "global"
}
Thank you!

"No value associated with key CodingKeys" Error when trying to get data from API - Swift

Hi I am trying to make a pokedex app and I have previously in my code used the same API and was successful but now when I call a different link from the API I am getting this error:
keyNotFound(CodingKeys(stringValue: "root", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"root\", intValue: nil) (\"root\").", underlyingError: nil))
I am trying to get a description for the pokemon.
This is my current code that isn't working:
func loadFlavor() {
guard let url = URL(string: "https://pokeapi.co/api/v2/pokemon-species/1/") else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
return
}
do {
let result = try JSONDecoder().decode(SpeciesResults.self, from: data)
DispatchQueue.main.async {
for typeEntry in result.root
{
self.descriptionText.text = typeEntry.flavor_text_entries.flavor_text
}
}
}
catch let error {
print(error)
}
}.resume()
}
And these are my structs:
struct SpeciesResults: Codable {
let root: [PokemonFlavorResults]
}
struct PokemonFlavorResults: Codable {
let flavor_text_entries: Zerodic
}
struct Zerodic: Codable {
let flavor_text: String
}
Issue:
You don't need SpeciesResults model because flavor_text_entries object is already at the root level.
Use camel-case for defining your variables. In Codable you can use convertFromSnakeCase as the decoder's keyDecodingStrategy.
Solution:
So, the Codable models must be,
struct PokemonFlavorResults: Codable {
let flavorTextEntries: [Zerodic]
}
struct Zerodic: Codable {
let flavorText: String
}
And parse the JSON data like so,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let results = try decoder.decode(PokemonFlavorResults.self, from: data)
//use results here...
} catch {
print(error)
}

Swift is not printing or displaying name in App from a weather API?

if let jsonObj = jsonObj as? [String: Any],
let weatherDictionary = jsonObj["weather"] as? [String: Any],
let weather = weatherDictionary["description", default: "clear sky"] as?
NSDictionary {
print("weather")
DispatchQueue.main.async {
self.conditionsLabel.text = "\(weather)"
}
}
// to display weather conditions in "name" from Open Weather
"weather":[{"id":800,"main":"Clear","description":"clear sky","icon":"01n"}]
//No errors, but code is not printing or displaying in App.
I'm not sure how to help with your exact question unless you can provide some more code for context. However,
You might try using the built-in decoding that comes with Swift 4. Check it out here. Basically, you make a class that models the response object, like this:
struct Weather: Decodable {
var id: Int
var main: String
var description: String
var icon: String
}
Then decode it like so:
let decoder = JSONDecoder()
let weather = try decoder.decode(Weather.self, from: jsonObj)
And it magically decodes into the data you need! Let me know if that doesn't work, and comment if you have more code context for your problem that I can help with.
I put the complete demo here to show how to send a HTTP request and parse the JSON response.
Note, Configure ATS if you use HTTP request, rather than HTTPS request.
The demo URL is "http://samples.openweathermap.org/data/2.5/forecast?q=M%C3%BCnchen,DE&appid=b6907d289e10d714a6e88b30761fae22".
The JSON format is as below, and the demo shows how to get the city name.
{
cod: "200",
message: 0.0032,
cnt: 36,
list: [...],
city: {
id: 6940463,
name: "Altstadt",
coord: {
lat: 48.137,
lon: 11.5752
},
country: "none"
}
}
The complete demo is as below. It shows how to use URLSessionDataTask and JSONSerialization.
class WeatherManager {
static func sendRequest() {
guard let url = URL(string: "http://samples.openweathermap.org/data/2.5/forecast?q=M%C3%BCnchen,DE&appid=b6907d289e10d714a6e88b30761fae22") else {
return
}
// init dataTask
let dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
let name = WeatherManager.cityName(fromWeatherData: data)
print(name ?? "")
}
// send the request
dataTask.resume()
}
private static func cityName(fromWeatherData data: Data?) -> String? {
guard let data = data else {
print("data is nil")
return nil
}
do {
// convert Data to JSON object
let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
print(jsonObject)
if let jsonObject = jsonObject as? [String: Any],
let cityDic = jsonObject["city"] as? [String: Any],
let name = cityDic["name"] as? String {
return name
} else {
return nil
}
} catch {
print("failed to get json object")
return nil
}
}
}