Error on Serialization - arrays

I'm getting this error when I try to proceed on parsing the JSON. Does anyone know what I gotta do to fix it?
Error 1: Cannot subscript a value of type 'AudiobookJSON' (aka 'Array<Dictionary<String, Any>>') with an index of type 'String'
Error on Print
File Model: Audiobook.swift:
import Foundation
struct Audiobook : Codable {
let id: Int
let descricao: String
let urlImagem: String
init(dictionary: AudiobookJSON) {
self.id = dictionary["id"] as! Int//////ERROR MESSAGE ////////
self.descricao = dictionary["descricao"] as! String/////ERROR MESSAGE
self.urlImagem = dictionary["urlImagem"] as! String////ERROR MESSAGE
}
}
Folder Networking: APIClient.swift:
import Foundation
typealias AudiobookJSON = [[String: Any]]
struct APIClient {
static func getAudiobooksAPI(completion: #escaping ([Audiobook]?) -> Void) {
let url = URL(string: "https://alodjinha.herokuapp.com/categoria")
let session = URLSession.shared
guard let unwrappedURL = url else { print("Error unwrapping URL"); return }
let dataTask = session.dataTask(with: unwrappedURL) { (data, response, error) in
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
//let json = try JSONSerialization.jsonObject(with: unwrappedDAta, options: .allowFragments) as! [String:Any]
//let posts = json["data"] as? AudiobookJSON
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch {
print("Could not get API data. \(error), \(error.localizedDescription)")
}
}
dataTask.resume()
}
}

As I can see that AudiobookJSON is an array of key-value pairs that's why you are getting error: So you have to use codable like that:
First: you have to make Codable type struct like that(your codable struct variable names should be same as you are getting in response):
struct Audiobook: Codable {
let id: Int?
let descricao: String?
let urlImagem: String?
}
Second: when you get the response then parse directly using codale like that:
guard let unwrappedDAta = data else { print("Error unwrapping data"); return }
do {
let posts = try JSONDecoder().decode([Audiobook].self, from: unwrappedDAta)
print(posts)
completion(posts)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
You can directly use the response like:
for audio in posts {
print("audio.id")
print("audio.descricao")
print("audio.urlImagem")
}

Related

Return HTTP request data

I would like to return the var points from my method getAllPoints()
struct tempPoint: Decodable {
let idPoint:Int
let ptName:String
let ptLat:Double
let ptLong:Double
}
func getAllPoints(){
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
let jsonData = Data(jsonString.utf8)
do {
let points = try JSONDecoder().decode([tempPoint].self, from: jsonData)
} catch let error {
print(error)
}
print(jsonString)
}
}
}.resume()
}
}
Results that I got when I do print(points)
[
{"idPoint":6,"ptName":"Maison","ptLat":43.72623997050257,"ptLong":2.202591651830584},
{"idPoint":7,"ptName":"Wheelin","ptLat":42.75754326128173,"ptLong":8.137330631668685},
{"idPoint":8,"ptName":"Animoz","ptLat":45.76321863196126,"ptLong":7.137186047202841},
{"idPoint":9,"ptName":"Hesias","ptLat":45.767222865412144,"ptLong":6.132352002277385},
{"idPoint":10,"ptName":"Marcombes","ptLat":45.76018235160473,"ptLong":4.085466264251463},
{"idPoint":11,"ptName":"Leclan","ptLat":48.80950120948317,"ptLong":2.110623123039061},
{"idPoint":12,"ptName":"Cournon Skatepark","ptLat":39.74138613175866,"ptLong":4.2154977334348906}
]
I wonder if this is possible to return these at the end of my method.
I'm gonna use it in a pickerView and to do a CRUD that's why I would like to store them.
Thank You
use completion closure to "return" your points.
Try something like this:
func getAllPoints(completion: #escaping([tempPoint]) -> ()) { // <-- here
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let points = try JSONDecoder().decode([tempPoint].self, from: data)
completion(points) // <-- here return the results when done
return
} catch let error {
print(error) // <-- here todo deal with errors
}
}
completion([]) // <-- here todo deal with errors
}.resume()
} else {
completion([]) // <-- here todo deal with errors
}
}
and call the function like this:
getAllPoints() { results in
print("array of points: \(results)")
}
My understanding is that you wanted the api response at the position of calling your function. You can return your response using closures.
The typealias you are seeing in the below code are self defined data types. You can directly add closures in your function but to make it more simple for you I declare those closures with typealias as data types. You can read more about typealias and closures here.
After declaring our own closure types. We need to use them in our function as parameters.
struct TempPoint: Decodable {
let idPoint:Int
let ptName:String
let ptLat:Double
let ptLong:Double
}
typealias ApiResponse = ([TempPoint]) -> Void
typealias ApiError = (Error) -> Void
func getAllPoints(_ completion: #escaping ApiResponse, apiError: #escaping ApiError){
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
let jsonData = Data(jsonString.utf8)
do {
let points = try JSONDecoder().decode([TempPoint].self, from: jsonData)
completion(points)
} catch let error {
print(error)
apiError(error)
}
print(jsonString)
}
}
}.resume()
}
}
Our function after changes would look like above.
Now here is how we can use this function.
override func viewDidLoad() {
super.viewDidLoad()
getAllPoints { [weak self] (points) in
// you update you UI here, be sure to call in main thread when you are doing UI updates in closures.
print(points)
} apiError: { (error) in
print(error)
}
}
It would be efficient if things are loosely coupled which can be done with Delegate pattern.
protocol TempPointDelegate {
func didUpdatePoints(_ tempPoints: [tempPoint])
func didFailWithError(error: Error)
}
and in tempPoint Struct
struct tempPoint: Decodable {
let idPoint:Int
let ptName:String
let ptLat:Double
let ptLong:Double
}
var delegate: TempPointDelegate?
func getAllPoints(){
if let url = URL(string: "[MyWebService.com]") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
if let jsonString = String(data: data, encoding: .utf8) {
let jsonData = Data(jsonString.utf8)
do {
let points = try JSONDecoder().decode([tempPoint].self, from: jsonData)
self.delegate?.didUpdatePoints(points)
return
} catch let error {
self.delegate?.didFailWithError(error)
return
}
}
}
}.resume()
}
}
finally in your ViewController implement tempPointDelegate delegate
class MainViewController: tempPointDelegate{
override func viewDidLoad() {
super.viewDidLoad()
tempPoint.getAllPoints()
}
func didUpdatePoints(_ tempPoints: [tempPoint]) {
DispatchQueue.main.async {
//process the tempPoints here / call the func to handle tempPoints
}
}
func didFailWithError(error: Error) {
//process the error returned here.
}
}
First of all please name structs with starting uppercase letter (TempPoint).
Second of all the conversion to String and back to Data is pointless
In Swift 5.5 you can use async/await
func getAllPoints() async throws -> [TempPoint] {
guard let url = URL(string: "[MyWebService.com]") else { throw URLError(.badURL) }
let (data, _) = try await URLSession.shared.data(from : url)
return try JSONDecoder().decode([TempPoint].self, from: data)
}
And call it
Task {
do {
let points = try await getAllPoints()
} catch {
print(error)
}
}

Swift JSONDecoder error - Expected to decode Dictionary<String, Any> but found an array instead

I'm new to Swift 5.3 and having trouble retrieving my nested JSON data.
My JSON data result looks like this:
{
"sites":[
{
"site_no":"16103000",
"station_nm":"Hanalei River nr Hanalei, Kauai, HI",
"dec_lat_va":22.1796,
"dec_long_va":-159.466,
"huc_cd":"20070000",
"tz_cd":"HST",
"flow":92.8,
"flow_unit":"cfs",
"flow_dt":"2020-08-18 07:10:00",
"stage":1.47,
"stage_unit":"ft",
"stage_dt":"2020-08-18 07:10:00",
"class":0,
"percentile":31.9,
"percent_median":"86.73",
"percent_mean":"50.77",
"url":"https:\/\/waterdata.usgs.gov\/hi\/nwis\/uv?site_no=16103000"
}
]
}
My structs look like this:
struct APIResponse: Codable {
let sites: APIResponseSites
}
struct APIResponseSites: Codable {
let station_nm: String
let stage: Float
}
And my Decode SWIFT looks like this:
let task = URLSession.shared.dataTask(with: url, completionHandler: {
data, _, error in
guard let data = data, error == nil else {
return
}
var result: APIResponse?
do {
result = try JSONDecoder().decode(APIResponse.self, from: data)
}
catch {
print("Failed to decode with error: \(error)")
}
guard let final = result else {
return
}
print(final.sites.station_nm)
print(final.sites.stage)
})
And of course, I get an error that states:
Failed to decode with error:
typeMismatch(Swift.Dictionary<Swift.String, Any>,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"sites", intValue: nil)], debugDescription: "Expected to decode
Dictionary<String, Any> but found an array instead.", underlyingError:
nil))
I know it has to do with 'sites' returning an array (a single one) but I don't know how to fix it. Any help would be greatly appreciated.
The error message it is pretty clear you need to parse an array of objects instead of a single object.
Just change your root declaration property from
let sites: APIResponseSites
to
let sites: [APIResponseSites]
**1.** First "sites" is an array so replace
let sites: APIResponseSites
with
let sites: [APIResponseSites]()
**2.** As sites is a array collection, please print value like given below:
print(final.sites.first?.station_nm ?? "")
print(final.sites.first?.stage ?? 0.0)
Final code is here:
struct APIResponse: Codable {
let sites: [APIResponseSites]()
}
struct APIResponseSites: Codable {
let station_nm: String
let stage: Float
}
let task = URLSession.shared.dataTask(with: url, completionHandler: {
data, _, error in
guard let data = data, error == nil else {
return
}
var result: APIResponse?
do {
result = try JSONDecoder().decode(APIResponse.self, from: data)
}
catch {
print("Failed to decode with error: \(error)")
}
guard let final = result else {
return
}
print(final.sites.first?.station_nm ?? "")
print(final.sites.first?.stage ?? 0.0)
})

Getting the Picture dynamic from an WordPress Post

I have a WordPress post and this contains a post picture that is updated regularly. I would like to dynamically query this post picture in Swift but have not yet found a solution for it.
Could someone help me?
Here is my code for which I am requesting a regular image at the moment:
var coursesPicture = [CoursesPicture]()
// MARK: - GUID
struct GUIDURL: Codable {
let rendered: String
}
// MARK: - Courses
struct Course: Codable {
let guid, title: GUID
let content: Content
let links: Links
enum CodingKeys: String, CodingKey {
case guid, title, content
case links = "_links"
}
}
// MARK: - Content
struct Content: Codable {
let rendered: String
let protected: Bool
}
// MARK: - GUID
struct GUID: Codable {
let rendered: String
}
// MARK: - Links
struct Links: Codable {
}
// MARK: - CoursePicture
struct CoursesPicture: Codable {
let featuredMedia: Int
enum CodingKeys: String, CodingKey {
case featuredMedia = "featured_media"
}
}
public func fetchJSONPicture() {
let urlString = "https://ismaning.de/wp-json/wp/v2/media/4291"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, _, err) in
DispatchQueue.main.sync {
if let err = err {
print("Failed to get data from url:", err)
return
}
guard let data = data else { return }
do {
self.coursesPicture = [try JSONDecoder().decode(CoursesPicture.self, from: data)]
} catch let jsonErr {
print("Failed to decode:", jsonErr)
}
}
}
if let url = URL(string: "https://ismaning.de/wp-content/uploads/ismaning-willkommen-banner_app-ios.jpg") {
URLSession.shared.dataTask(with: url) { (data, urlResponse, error) in
if let data = data {
DispatchQueue.main.sync {
self.imageView.image = UIImage(data: data)
}
}
}.resume()
}
}
I simply query the image here via a URL. Nevertheless, it should be queried dynamically via a WordPress post. I also tried this JSON query but unfortunately I haven't found a solution yet
Thank you in advance for your help, as I have been looking for a solution for a while now
After discussion in the chat we found a working solution for this problem. If anyone else has this you can simply do this:
Create two structs, example Struct Response: Codable{}
Create two arrays which are of the struct types like: var arr:[Response] = []
In the ViewDidLoad you will call the getJsonData method with completion block.
When the data is loaded in the first array, you call the getJsonImage method with completion block.
Once you have the imageURL you can simply retrieve it by creating a URLSession to retrieve the image.
This is the solution code:
//Arrays
var firstArray:[FirstResponse] = []
var imgData:[ImgData] = []
override func viewDidLoad() {
super.viewDidLoad()
//Fetching the JSON data
fetchJSON {
//Fetching the JSON image
self.fetchJSONImage {
let imageUrl = self.imgData[0].link
self.imgView.downloaded(from: imageUrl)
}
}
}
//Retrieving the JSON data
func fetchJSON(completed: #escaping () -> ()){
var urlString = "HIDDEN"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
guard let url = URL(string: urlString) else {return}
//Create the URLSession
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else{
return
}
do{
//Sets the dataArray to JSON data
self.firstArray = [try JSONDecoder().decode(FirstResponse.self, from: data)]
//Complete task in background
DispatchQueue.main.async {
completed()
}
}
catch let jsonErr{
print(jsonErr)
}
}.resume()
}
//Retrieving the image link from JSON
func fetchJSONImage(completed: #escaping () -> ()){
let imgPath = firstArray[0].featured_media
var urlString = "HIDDEN-URL\(imgPath)"
urlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
guard let url = URL(string: urlString) else {return}
//Create the URLSession
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else{
return
}
do{
//Sets the dataArray to JSON data
self.imgData = [try JSONDecoder().decode(ImgData.self, from: data)]
//Complete task in background
DispatchQueue.main.async {
completed()
}
}
catch let jsonErr{
print(jsonErr)
}
}.resume()
}
}
//Struct for getting image ID
struct FirstResponse: Codable{
let featured_media: Int
}
//Struct for getting the image link
struct ImgData: Codable{
let link: String
}
//Extension
extension UIImageView {
//Download the image from the API
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleToFill) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard let image = UIImage(data: data)
else { return }
//Continue in background
DispatchQueue.main.async() {
self.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
}
}

parsing array of objects to array of string elements in swift JSON object

I am trying to parse the JSON object received from Web service which gives the result as JSON object of status and data.data again is a array of objects, from this object I want to take one element on the basis of which I have to fill a tableview.
web Service results comes as
{"status":1,"data":[{"service_id":"1","service_name":"Painter"},{"service_id":"2","service_name":"Plumber"},{"service_id":"3","service_name":"Electrician"},{"service_id":"4","service_name":"Handyman"},{"service_id":"5","service_name":"Carpenter"},{"service_id":"6","service_name":"Mason"}]}
parsing in swift I did as:--
created one class
class ABC: NSObject {
var service_name:String?
var service_id : Int?
init(service_name:String,service_id:Int) {
self.service_name = service_name
self.service_id = service_id
}
let myUrl = URL(string: "services.php");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json
{
let status=parseJSON["status"] as? Int
let newdata : NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
self.model=(newdata.value(forKey: "data") as? [ABC])!
My problem is I am getting an array of objects in self.model as service_name and service_id keys.Now I want to take out one array of strings that contains all the service_name object values.Its saying not able to convert NSArray to swift array.
As already mentioned by others use (De)codable. It's easy to use and very comfortable. No type cast and no literal keys.
Create two structs, declare the members as non-optional constants with camelCased names and omit the initializer.
struct Root : Decodable {
let status : Int
let data : [Service]
}
struct Service : Decodable {
let serviceName : String
let serviceId : String
}
Then decode the JSON in another class or struct
let myUrl = URL(string: "services.php")
var request = URLRequest(url: myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data!)
let status = json.status
let newdata = json.data
} catch { print(error))
}
task.resume()
I would recommend to drop JSONSerialization and use Codable protocol instead with CodingKeys.
Here is a simple example to see how it works.
struct Service : Codable {
let id : Int
let name : String
// keys
private enum CodingKeys: String, CodingKey {
case id = "service_id"
case name = "service_name"
}
}
...
// assuming our data comes from server side
let jsonString = "..."
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(Service.self, from: jsonData)
print("Getting service: \(service.id) \(service.name)")
} catch {
print("Unexpected error: \(error).")
}
More documentation here.
Use native Swift type Dictionary everywhere you use NSDictionary now
Then get certain value for key by specifing key in dictionary's subscript
if let model = newData["data"] as? [ABC] {
self.model = model
}
Anyway, I would suggest you to start using Codable instead of JSONSerialization
struct Response: Decodable {
let status: Int
let data: [ABC]
}
struct ABC: Decodable {
let serviceName: String
let serviceId: String // note that `serviceId` isn’t `Int` But `String`
}
and then decode your data using JSONDecoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(Response.self, from: data!)
self.model = response.data
} catch { print(error) }

apple change my JSON format after using URLSession.shared.dataTask

I am green to swift 3. In order to transfer data between mysql server and iOS, i try to connect my server and device by URLSession.Shared.dataTask.
but the format was changed.
from (the format showed in web)
[{"id":"1","names":"abc","pws_1":"password","pws_2":"pw"}]
to (the format show in Xcode)
Optional(<__NSSingleObjectArrayI 0x6200000039e0>(
{
names = abc;
id = 1;
"pws_1" = password;
"pws_2" = pw;
}
)
)
How can I receive data from this "JSONarray"?
P.S.
this is my code:
let url = URL(string: "http://")
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
if error != nil {
print(error)
} else {
do {
let json = try? JSONSerialization.jsonObject(with: data!, options: [])
if json != nil{
if let Jtwo = json as? [String: Any] {
if let names = Jtwo["names"] as? String {
print(names)
}else{
print(Jtwo)
}
}else{
print(json)
}
}else{
print("json nil")
}
//self.statusL.text = names
} catch let error as NSError {
print(error)
}
}
}).resume()
You can parse and access your JSON Array like this.
URLSession.shared.dataTask(with: request) {data, response, error in
if error == nil {
do {
let jsonObj = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [[String:Any]]
if let user = jsonObj.first {
print(user["names"])
print(user["id"])
print(user["pws_1"])
print(user["pws_2"])
DispatchQueue.main.async {
self.label.text = user["names"]
}
}
}
catch {
print(error)
}
}
}.resume()

Resources