Swift 4 Decodable parsing json data in array - ios

I have a problem with parsing data from web service, it seems that the decodable protocol couldn't parse this json
This is my parsing data using generics.
public func requestGenericData<T: Decodable>(urlString: String, httpMethod: String?, token: String!, completion: #escaping(T) ->()) {
let fullStringUrl = url + urlString
guard let url = URL(string: fullStringUrl) else { return }
guard let token = token else { return }
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/json", forHTTPHeaderField: "accept")
urlRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
urlRequest.httpMethod = httpMethod
URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if self.isInternetAvailable() {
guard let data = data else { return }
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
do {
let obj = try JSONDecoder().decode(T.self, from: data)
completion(obj)
} catch {
print("Error: \(String(describing: error))\n StatusCode: \(httpResponse.statusCode)")
}
}
}
} else {
showAlert(title: "No Internet Connect", message: "Please open your network and try again.", alertStyle: .alert, buttonTitle: "OK", buttonStyle: .default)
return
}
}.resume()
}
This is my model
struct JobWithCategory: Decodable {
let jobTypeID: Int
let jobCategoryID: Int
let name: String
let getJobs: [getJobs]
}
struct getJobs: Decodable {
let name: String
let description: String
}
struct JobCategories: Decodable {
let jobCategories: [JobWithCategory]
}
apiHelper.requestGenericData(urlString: "url/on/something/else", httpMethod: "GET", token: token) { (jobCategories: [JobCategories]) in
print(jobCategories)
}
Now i'm having with this issue on my console printed:
Error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
What do I missed or did something wrong with my implementation? Could someone help me out on this one, and so please elaborate why is this happening so I can have a good grasp on whats going on on my code.
Thanks in advance :)

Please read the error message
Expected to decode Array but found a dictionary instead
The expected side shows what you are doing (wrong) and the found side shows the actual type.
In terms of Decodable a dictionary is a struct. So it's
...{ (jobCategories: JobCategories) in

Because you use
[JobCategories]
as the completion T is inferred to array so
T.self = [JobCategories].self
not a dictionary , so try this
apiHelper.requestGenericData(urlString: "url/on/something/else",
httpMethod: "GET", token: token) { (jobCategories:JobCategories) in
print(jobCategories.jobCategories)
}

Related

Issues parsing json from imgut

I am able to parse and decode data coming from IMGUR website. However when I try to print out one of the properties from Data it doesn’t show anything. How do I fix this? Also I want to access the contents of images, specifically the links. How can I access the contents Of the image since it’s an array?
struct PhotoResponse:Codable {
let success: Bool
let status: Int
let data: [Data]
}
struct Data:Codable {
let id: String
let title: String
let views: Int
let link: String
let images: [Image]?
let animated: Bool?
}
struct Image: Codable {
let id: String
let imageDescription: String?
let link: String?
let size: Int
}
class NetworkService{
static let shared = NetworkService()
private let baseURL = "https://api.imgur.com/3/gallery"
private init() {}
func getJSON( searchName: String, completion: #escaping([Data]) -> Void){
let endPoints = baseURL + "/search/time/week/?q=\(searchName)"
guard let url = URL(string: endPoints ) else{
return
}
var request = URLRequest(url: url)
let headers = ["Authorization": ""]
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Failed to query database", error)
}
guard let data = data else {
print("Data not received\(String(describing: error))")
return
}
let decoder = JSONDecoder()
do{
let decodedJson = try decoder.decode(PhotoResponse.self, from: data)
DispatchQueue.main.async {
completion(decodedJson.data)
}
}catch let error{
print("Json failed to decode\(String(describing: error))")
return
}
}.resume()
}
}
NetworkService.shared.getJSON(searchName: "cat") { (photos) in
for photo in photos {
print(photo.title)
print(photo.id)
}
Swift already has a Data struct, try renaming yours to something else, like MyData. Do the same with Image.

SwiftUI: API URL fetch failed

I'm trying to retrieve data from an API url
This is what the implementation guide reads, so the URL should match this format:
The request for information in JSON format is submitted as a GET
operation to the endpoint:
http://digit-eyes.com/gtin/v2_0/?upc_code=x&app_key=x&signature=x&language=x&field_names=x
This is my function that fetches the data from the JSON and decodes it from JSON.
I've replaced the signature and API Key with x.
Signature is generated by combining the app_key and the barcode forming a hashed value.
func loadData() {
guard let url = URL(string: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=x&signature=x&language=en&field_names=description,brand,ingredients,image,upc_code") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, responce, error in
if let data = data {
if let decodedRepsonce = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async{
self.results = decodedRepsonce.results
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")") //This is the error I get
}.resume()
}
Here's what I get when I paste the URL into safari
I've tested the URL with: "https://itunes.apple.com/search?term=radiohead&entity=song" and it works. A noticeable difference is that this link downloads a JSON file, my URL doesn't.
I store the JSON into an array Results:
struct Result: Codable {
var description: String
var brand: String
var ingredients: String
var image: String
var upc_code: Int
}
Which is then displayed in the body:
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
self.indicator.padding()
List(self.results, id: \.upc_code) { item in
VStack(alignment: .leading) {
Text(item.brand)
.font(.headline)
Text(item.description)
}
}
}
EDIT
Dealing with nulls from the JSON data
To call loadData, I have an .onAppear on a VStack in the body.
.onAppear {
//let signiture = self.scannedCode.barcode.hashedValue("Ls75O8z1q9Ep9Kz0")
self.loadData(url: "https://www.digit-eyes.com/gtin/v2_0/?upcCode=5901905880016&language=en&app_key=/9nOS+obsRF5&signature=DiKl4lURenoNe53I0a/i3kiAkQQ=&language=en&field_names=description,ingredients,brand,image") { error, result in
if let err = error {
print(err)
}
}
}
}
This is in a struct outside of the body
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
}
I'm now getting:
valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "brand", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
In the JSON object, the brand name isn't always found, so it's sometimes null. I don't know how I can resume the decoder if a null is found.
Try this code to call in the body:
func loadData(url: String, completion: #escaping (Error?, Result?) -> Void) {
if let url = URL(string: url) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {return}
do {
let decoder = JSONDecoder()
let result: Result = try decoder.decode(Result.self, from: data)
completion(nil, result)
}
catch let e {
print(e)
completion(e, nil)
}
}
task.resume()
}
}
loadData(url: "https://google.com") { error, result in
if let err = error {
print(err)
}
}
Try to modify the struct as follows and add the other variables. I also noticed that upc_code is a String.
struct Result: Codable {
var description: String?
var brand: String?
var ingredients: String?
var image: String?
var upc_code: String?
var return_message: String?
var return_code: String?
}

Swift 5 Json parsing error "dataCorrupted"

I tried every solution but none of them resolved my problem getting below error while parsing. can anybody find the fault in this code
Error serializing json: dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Unable to convert data to string around character 2643." UserInfo={NSDebugDescription=Unable to convert data to string around character 2643.})))
struct Facts:Codable {
let title: String
let rows: [Rows]
}
struct Rows:Codable {
var title: String
var description: String
var imageHref: String
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://dl.dropboxusercontent.com/s/2iodh4vg0eortkl/facts.json"
guard let url = URL(string: jsonUrlString) else{return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do{
let facts = try JSONDecoder().decode(Facts.self, from: data)
print(facts)
}catch let jsonErr{
print("Error serializing json:", jsonErr)
}
}.resume()
}
}
// problem in response type and encoding. It should be application/json and //unicode but actually it is:
//response content-type: text/plain; charset=ISO-8859-1
struct Facts:Codable {
let title: String
let rows: [Rows]!
}
struct Rows:Codable {
var title: String?
var description: String?
var imageHref: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "https://dl.dropboxusercontent.com/s/2iodh4vg0eortkl/facts.json"
guard let url = URL(string: jsonUrlString) else{return}
// var request = URLRequest(url: url)
// request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") // the request is JSON
// request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept") // the expected response is also JSON
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard let string = String(data: data, encoding: String.Encoding.isoLatin1) else { return }
guard let properData = string.data(using: .utf8, allowLossyConversion: true) else { return }
do{
let facts = try JSONDecoder().decode(Facts.self, from: properData)
//dump(facts)
print(facts.title)
for row in facts.rows {
print(row.title ?? "no title")
print(row.description ?? "no description")
print(row.imageHref ?? "no img url")
print("---")
}
} catch let error {
print(error)
}
}.resume()
}
}
I have solved this problem by doing one extra step:
First convert the data in to string which we get in closures then again convert this string to data back and this will 100% work.
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
guard let request = requestObj.request else {
return
}
let task = session.dataTask(with: request) { (data, response, error) in
if let data = data {
let str = String(decoding: Data(data), as: UTF8.self)
**if let data = str.data(using: String.Encoding.utf8 ) {**
if error != nil {
if let err = error as NSError? {
failure(err)
}
}
if let httpResponse = response as? HTTPURLResponse {
let code = httpResponse.statusCode
switch code {
case HttpStatusCode.success:
success(data)
default:
failure(NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: "Something went wrong"]))
}
}
}
}
}
// execute the HTTP request
task.resume()

Strange JSON API response SWIFT

I am sending an HTTP POST request to an API in swift and it is supposed to respond with:
{
"results": [
{
"alternatives": [
{
"transcript": "how old is the Brooklyn Bridge",
"confidence": 0.98267895
}
]
}
]
}
However, I am receiving this through the print(jsonResponse) function:
Optional({
results = (
{
alternatives = (
{
confidence = "0.9688848";
transcript = "how old is the Brooklyn Bridge";
}
);
}
);
})
Is there any reason why the response is not arriving in the correct format as indicated in the API documentation? I need to Decode the response to obtain the "transcript" value. However, I am receiving the following error:
keyNotFound(CodingKeys(stringValue: "transcript", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"transcript\", intValue: nil) (\"transcript\").", underlyingError: nil))
Maybe my request isn't optimal... Here's my code, any help is appreciated!
let parameters = ["config": ["encoding": "FLAC", "sampleRateHertz": "16000", "languageCode": "en-AU"], "audio": ["uri":"gs://cloud-samples-tests/speech/brooklyn.flac"]]
guard let url = URL(string: "https://speech.googleapis.com/v1/speech:recognize?key=AIzaSyDqYpPQIabwF5L-WibBxtVsWRBrc8uKi4w") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: []) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
URLSession.shared.dataTask(with: request) { (data , response , error) in
guard let data = data else {return}
do {
let jsonResponse = (try? JSONSerialization.jsonObject(with: data, options: []))
print("\(jsonResponse)")
let course = try JSONDecoder().decode(Course.self , from : data)
print(course.transcript)
} catch {
print(error)
}
}.resume()
Here is my Course code block: Do I need to include the other components in the struct as well as the transcript?
struct Course: Decodable {
let transcript: String
enum CodingKeys: String, CodingKey {
case transcript = "transcript"
}
}
jsonResponse is an Optional Dictionary, and thus that's why it's debug description looks like what you printed rather than pure JSON as you were looking for. Your problem likely is that your Decodeable objects are not properly setup - as by the looks of it you only have one Course. You'll likely need two more Response which contains a list of Alternatives. And then in Alternative you have a list of Courses.
Structure your objects like this, and it should do the trick:
struct Response: Decodable {
let results: [Alternative]
}
struct Alternative: Decodable {
let alternatives: [Course]
}
struct Course: Decodable {
let transcript: String
let confidence: Float
}
And then swap this line:
let course = try JSONDecoder().decode(Course.self , from : data)
With this change:
let course = try JSONDecoder().decode(Response.self, from: data).results[0].alternatives[0]

Cannot convert value of type 'Dictionary<String, Any>?' to expected argument type 'Data'

I am still new to swift and i am trying to fetch json data and pass it to the next view as an object which i created. However, i am getting this error Cannot convert value of type 'Dictionary?' to expected argument type 'Data' when i try to user the decoder class. I am not sure what to do to fix it. I have tried changing Dictionary?' to Data in my completion handler but i am still getting errors.
This is my code :
Service call
class ServiceCall: NSObject, ServiceCallProtocol, URLSessionDelegate {
let urlServiceCall: String?
let country: String?
let phone: String?
var search: SearchResultObj?
init(urlServiceCall: String,country: String, phone: String){
self.urlServiceCall = urlServiceCall
self.country = country
self.phone = phone
}
func fetchJson(request: URLRequest, customerCountry: String, mobileNumber: String, completion: ((Bool, Dictionary<String, Any>?) -> Void)?){
let searchParamas = CustomerSearch.init(country: customerCountry, phoneNumber: mobileNumber)
var request = request
request.httpMethod = "POST"
request.httpBody = try? searchParamas.jsonData()
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
do {
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, Any>
let status = json["status"] as? Bool
if status == true {
print(json)
}else{
print(" Terrible failure")
}
} catch {
print("Unable to make an api call")
}
})
task.resume()
}
}
SearchViewModel
func searchDataRequested(_ apiUrl: String,_ country: String,_ phone:String) {
let service = ServiceCall(urlServiceCall: apiUrl, country: country, phone: phone)
let url = URL(string: apiUrl)
let request = URLRequest(url: url!)
let country = country
let phone = phone
service.fetchJson(request: request, customerCountry: country, mobileNumber: phone)
{ (ok, json) in
print("CallBack response : \(String(describing: json))")
let decoder = JSONDecoder()
let result = decoder.decode(SearchResultObj.self, from: json)
print(result.name)
// self.jsonMappingToSearch(json as AnyObject)
}
}
New error:
You are going to deserialize the JSON twice which cannot work.
Instead of returning a Dictionary return Data, this mistake causes the error, but there are more issues.
func fetchJson(request: URLRequest, customerCountry: String, mobileNumber: String, completion: (Bool, Data?) -> Void) { ...
Then change the data task to
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
if let error = error {
print("Unable to make an api call", error)
completion(false, nil)
return
}
completion(true, data)
})
and the service call
service.fetchJson(request: request, customerCountry: country, mobileNumber: phone) { (ok, data) in
if ok {
print("CallBack response :", String(data: data!, encoding: .utf8))
do {
let result = try JSONDecoder().decode(SearchResultObj.self, from: data!)
print(result.name)
// self.jsonMappingToSearch(json as AnyObject)
} catch { print(error) }
}
}
And you have to adopt Decodable in ServiceCall
class ServiceCall: NSObject, ServiceCallProtocol, URLSessionDelegate, Decodable { ...
Further I highly recommended to separate the class model from the code to retrieve the data.
The data returned from session's task can either be serialized with JSONSerialization or decode it with JSONDecoder
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
either
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, Any>
OR
let result = try decoder.decode([item].self,data!)
the second argument of the decode method expects a parameter of type Data not Dictionary
you have to only edit the completion of fetchJson to return Bool,Data instead of Bool,Dictionary,and remove JSONSerialization code from it

Resources