I tried this, but it raise an error:
do {
guard let firstValue = result.values.first else {
completion?(nil, NSError(domain: "MyDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No first value found"]))
return
}
let jsonData = try JSONSerialization.data(withJSONObject: firstValue, options: [])
let jsonStr = String(data: jsonData, encoding: .utf8)
//print(jsonStr!)
let decoder = JSONDecoder()
let decodedObject = try decoder.decode(T.self, from: jsonData)
completion?(decodedObject, nil)
} catch let decodingError as DecodingError {
print("Failed to decode JSON: \(decodingError)")
if let context = decodingError.context { // <----- HERE
print("Coding path: \(context.codingPath)")
}
} catch let error {
print("An error occurred: \(error)")
// handle the error here
}
Type of expression is ambiguous without more context
Expanding on Joakim post if you would like to use the syntax at your post you can extend DecodingError enum as follow:
extension DecodingError {
var context: Context {
let context: Context
switch self {
case let .dataCorrupted(ctxt):
context = ctxt
case let .keyNotFound(_, ctxt):
context = ctxt
case let .typeMismatch(_, ctxt):
context = ctxt
case let .valueNotFound(_, ctxt):
context = ctxt
#unknown default:
fatalError()
}
return context
}
var codingPath: [CodingKey] {
context.codingPath
}
var underlyingError: Error? {
context.underlyingError
}
}
Related
I'm receiving the following error when trying to destructure an object:
Cannot infer contextual base in reference to member 'utf8'
Here is the object:
{
ChallengeName = "CUSTOM_CHALLENGE";
ChallengeParameters = {
USERNAME = "3bd8b0e1-37c2-4922-8c08-f2903d1a66d5";
phone = "+13039318012";
};
Session = "AYABeLz0aQ7zXFus6wcPLxMd";
}
I get the above error when trying to destructure the object with this code:
let JSON = try JSONSerialization.jsonObject(with: data, options: [])
let jsonData = JSON.data(using: .utf8)! // this is where the error occurs
Perhaps there is something wrong with my struct?:
struct SessionData: Decodable {
var ChallengeName: String
var ChallengeParameters: String
var Session: String
}
I
Solution:
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, _, error in
guard let data = data, error == nil else {
return
}
do {
let response = try JSONDecoder().decode(SessionData.self, from: data)
print(response)
} catch {
print(error)
}
}
Below line of code is producing the error,
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
When print jsonData This code returns perfect result of array but getting this error,
Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)
As the error says I understand its a cast exception, but I'm not able to modify the code to make it work. I'm kinda new to Swift, so any help would be appreciated. Below is my
import Foundation
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<Any, Error>, Error) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error), Error.self as! Error)
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response")
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data")
return
}
do {
// Parse the data
let decoder = JSONDecoder()
let jsonData = try decoder.decode(APIService.self, from: data)
// print(jsonData)
// Back to the main thread
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
} catch let error {
completion(.failure(error),error)
}
}
dataTask?.resume()
}
}
Modify the completion block parameters, you already are returning the error inside the Result's .failure(Error) block so no need to repeat it again as another parameter in the completion parameter. Here's how you fix this:
Declaration:
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<CategoriesNewsData, Error>) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error))
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response") // Throw a custom error here too.
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data") // Throw a custom error here too.
return
}
do {
let decoder = JSONDecoder()
let jsonData = try decoder.decode(CategoriesNewsData.self, from: data)
DispatchQueue.main.async {
completion(.success(jsonData))
}
} catch let error {
completion(.failure(error))
}
}
dataTask?.resume()
}
}
Calling:
service.getPopularPosts { result in
switch result {
case .success(let categoriesNewsData):
print(categoriesNewsData)
case .failure(let error):
print(error)
}
}
I'm having an issue printing data from Weather Underground. My code works with other data sources, just not Weather Underground. I have even tried replacing the URL with actual data (i.e. https://api.wunderground.com/api/APIKEY/forecast/geolookup/forecast/q/94129.json"), but it doesn't print.
Any suggestions on what it could be?
import Foundation
import UIKit
class APIManager {
func weatherJSON(zip: String, completion: #escaping ([Weather]) -> Void) {
let baseUrlString = "https://api.wunderground.com/api/APIKEY/forecast/geolookup/forecast/q/\(zip).json"
guard let url = URL(string: baseUrlString) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil, let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] else { return }
// MARK: Print JSON
print(json)
var weatherList = [Weather]()
for item in json {
if let weather = Weather.create(from: item) {
weatherList.append(weather)
}
}
completion(weatherList)
} catch {
print("Uh oh. You have an error with \(zip)!")
}
}
task.resume()
}
}
EDIT: SOLVED
I have used the code posted below and am now seeing errors.
I'd suggest changing this to report errors:
enum WeatherError Error {
case badURL
case invalidJSON
}
func weatherJSON(zip: String, completion: #escaping ([Weather]?, Error?) -> Void) {
let baseUrlString = "https://api.wunderground.com/api/APIKEY/forecast/geolookup/forecast/q/\(zip).json"
guard let url = URL(string: baseUrlString) else {
completion(nil, WeatherError.badURL)
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil, let data = data else {
completion(nil, error)
return
}
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [[String: Any]] else {
completion(nil, WeatherError.invalidJSON)
return
}
// MARK: Print JSON
print(json)
var weatherList = [Weather]()
for item in json {
if let weather = Weather.create(from: item) {
weatherList.append(weather)
}
}
completion(weatherList, nil)
} catch let parseError {
print("Uh oh. You have an error with \(zip)!")
if let responseString = String(data: data, encoding: .utf8) {
print("responseString = \(responseString)")
}
completion(nil, parseError)
}
}
task.resume()
}
Then, when you call it, you can see what the error was
weatherJSON(zip: something) { weatherReports, error in
guard let weatherReports = weatherReports, error == nil else {
print(error)
return
}
// use weatherReports here
}
This won't solve your problem, but it will help you diagnose what the issue is.
i tried to get The (Name) value to a label
i used resultLabel.text = !(jsonResult["name"]) but it returns an error
Cannot subscript a value of type 'AnyObject' with an index of type 'String'
See my JSON
and does anybody know's how to get the data..
My code ....
if let url = URL(string: "http://www.omdbapi.com/?t=The+Raid&y=&plot=short&r=json") {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in // URLSession.shared().dataTask(with: url) { (data, response, error) is now URLSession.shared.dataTask(with: url) { (data, response, error)
if error != nil {
print(error)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject // Added "as anyObject" to fix syntax error in Xcode 8 Beta 6
print(jsonResult)
print(jsonResult["Title"])
resultLabel.text = (jsonResult["name"])
if let description = ((jsonResult["weather"] as? NSArray)?[0] as? NSDictionary)?["description"] as? String {
DispatchQueue.main.sync(execute: {
self.resultLabel.text = description
})
}
} catch {
print("JSON Processing Failed")
}
}
}
}
task.resume()
} else {
resultLabel.text = "Couldn't find weather for that city - please try another."
}
}
Casting the result of JSON deserialization to AnyObject is the worst you can do.
First of all the unspecified JSON type is Any and since the type is supposed to be a dictionary, cast it to [String:Any].
Further in Swift 3 the compiler must know all types which are subscripted
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: []) as! [String:Any]
let name = jsonResult["name"] as? String
print(name ?? "n/a")
if let weather = jsonResult["weather"] as? [[String:Any]], !weather.isEmpty {
if let description = weather[0]["description"] as? String {
DispatchQueue.main.async { // not sync !!
self.resultLabel.text = description
}
}
...
PS: As always, mutableContainers is meaningless in Swift and meaningless anyway if the values are only read.
I'm a C# developer convert to Swift tvOs and just starting to learn. I've made some progress, but not sure how to handle nested calls to json. The sources are from different providers so I can't just combine the query.
How do I wait for the inner request to complete so the TVSeries has the poster_path? Is there a better way to add the show to the collection and then process the poster path loading in another thread so it doesn't delay the UI Experience?
func downloadTVData() {
let url_BTV = NSURL(string: BTV_URL_BASE)!
let request_BTV = NSURLRequest(URL: url_BTV)
let session_BTV = NSURLSession.sharedSession()
//get series data
let task_BTR = session_BTV.dataTaskWithRequest(request_BTV) { (data_BTV, response_BTV, error_BTV) -> Void in
if error_BTV != nil {
print (error_BTV?.description)
} else {
do {
let dict_BTV = try NSJSONSerialization.JSONObjectWithData(data_BTV!, options: .AllowFragments) as? Dictionary<String, AnyObject>
if let results_BTV = dict_BTV!["results"] as? [Dictionary<String, AnyObject>]{
for obj_BTV in results_BTV {
let tvshow = TVSeries(tvDict: obj_BTV)
//for each tv series try to load a poster_path from secondary provider
if let str = obj_BTV["title"] as? String!{
let escapedString = str?.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())!
if let url = NSURL(string: self.SEARCH_URL_BASE + escapedString!) {
let request = NSURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error != nil {
print (error?.description)
} else {
do {
let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments) as? Dictionary<String, AnyObject>
if let results = dict!["results"] as? [Dictionary<String, AnyObject>] {
//iterate through the poster array
for obj in results {
if let path = obj["poster_path"] as? String {
tvshow.posterPath = path
break
}
}
}
} catch let error as NSError {
print(error.description)
}
}
}
task.resume()
}
}
self.tvSeries.append(tvshow)
}
dispatch_async(dispatch_get_main_queue()){
self.collectionView.reloadData()
}
}
} catch let error as NSError {
print(error.description)
}
}
}
task_BTR.resume()
}
Thanks for your help!
I would recommend breaking things apart into multiple methods, with callbacks to sequence the operations, and utilizing Swift's built-in throws error handling mechanism. Here's an example, not perfect, but might help as a starting point:
class TVSeries
{
let title: String
var posterPath: String?
enum Error: ErrorType {
case MalformedJSON
}
init(tvDict: [String: AnyObject]) throws
{
guard let title = tvDict["title"] as? String else {
throw Error.MalformedJSON
}
self.title = title
}
static func loadAllSeries(completionHandler: [TVSeries]? -> Void)
{
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: BTV_URL_BASE)!) { data, response, error in
guard let data = data else {
print(error)
completionHandler(nil)
return
}
do {
completionHandler(try fromJSONData(data))
}
catch let error {
print(error)
}
}.resume()
}
static func fromJSONData(jsonData: NSData) throws -> [TVSeries]
{
guard let dict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments) as? [String: AnyObject] else {
throw Error.MalformedJSON
}
guard let results = dict["results"] as? [[String: AnyObject]] else {
throw Error.MalformedJSON
}
return try results.map {
return try TVSeries(tvDict: $0)
}
}
func loadPosterPath(completionHandler: () -> Void)
{
guard let searchPath = title.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet()) else {
completionHandler()
return
}
let url = NSURL(string: SEARCH_URL_BASE)!.URLByAppendingPathComponent(searchPath)
NSURLSession.sharedSession().dataTaskWithURL(url) { [weak self] data, response, error in
defer { completionHandler() }
guard let strongSelf = self else { return }
guard let data = data else {
print(error)
return
}
do {
strongSelf.posterPath = try TVSeries.posterPathFromJSONData(data)
}
catch let error {
print(error)
}
}.resume()
}
static func posterPathFromJSONData(jsonData: NSData) throws -> String?
{
guard let dict = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments) as? [String: AnyObject] else {
throw Error.MalformedJSON
}
guard let results = dict["results"] as? [[String: AnyObject]] else {
throw Error.MalformedJSON
}
for result in results {
if let path = result["poster_path"] as? String {
return path
}
}
return nil
}
}
It might also be worth your time to look into something like RxSwift or Alamofire, which help you with these kinds of data-conversion / sequencing operations.