Getting error when assigning JSON array value to variable - json

I would like to assign a value from JSON to a variable, the issue is Swift thinks I am passing an entire array and not just the JSON value of code to the variable.
I have the following structure and JSON decode function:
private func JSONFunction() {
guard let url = URL(string: "https://example.com/example/example"),
let nameValue = stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "name=\(nameValue)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let myData = try JSONDecoder().decode(codeStruct.self, from:data)
DispatchQueue.main.async {
codeNum = myData.code
print(codeNum)
}
}
catch {
print(error)
}
}.resume()
}
The following is the structure for decoding the JSON:
struct codeStruct: Codable {
let code: String
let line: String
let person: String
let maker: String
}
Error received:
typeMismatch(Swift.Dictionary,
Swift.DecodingError.Context(codingPath: [], debugDescription:
"Expected to decode Dictionary but found an array
instead.", underlyingError: nil))

Without looking at the json, if I were to guess, I would say that your incoming JSON is actually an array of codeStruct objects, for which you should change your line to
let myData = try JSONDecoder().decode([codeStruct].self, from:data)

Related

Issue with decoding json, "The data couldn’t be read because it isn’t in the correct format." [duplicate]

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)
})

Why am I getting an error when parsing response in swift

I am trying to call an API and I am trying to map the response to a model class. But each time, I am getting a an error that invalid data.
Api call is as follows.
let endpoint = "http://apilayer.net/api/live?access_key=baab1114aeac6b4be74138cc3e6abe79&currencies=EUR,GBP,CAD,PLN,INR&source=USD&format=1"
guard let url = URL(string: endpoint) else {
completed(.failure(.invalidEndPoint))
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let _ = error {
completed(.failure(.unableToComplete))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
completed(.failure(.invalidResponse))
return
}
guard let data = data else {
completed(.failure(.invalidData))
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let convertedValues = try decoder.decode([Converted].self, from: data)
completed(.success(convertedValues))
} catch {
completed(.failure(.invalidData))
}
}
task.resume()
}
Sample Response is as follows.
{
"success":true,
"terms":"https:\/\/currencylayer.com\/terms",
"privacy":"https:\/\/currencylayer.com\/privacy",
"timestamp":1610986207,
"source":"USD",
"quotes":{
"USDEUR":0.828185,
"USDGBP":0.736595,
"USDCAD":1.276345,
"USDPLN":3.75205
}
}
Model class
struct Converted: Codable {
let success: Bool
let terms, privacy: String?
let timestamp: Int?
let source: String?
let quotes: [String: Double]?
}
Can someone help me to understand where I am getting wrong?. Thanks in advance.
Error messages can sometimes help. If you catch the error and print it, it reads:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
So an array of Converted was expected when in fact a single object was returned. The solution is to update the expectation to match the actual response:
let convertedValues = try decoder.decode(Converted.self, from: data)

Tying to decode JSON without object path

I am a complete newby to Swift and have a JSON file that im looking to decode, bit its just an array of objects (strings directing to other JSON Paths) with no object definition:
Example of list.json:
[
"filepath1.json",
"filepath2.json",
"filepath3.json",
"filepath4.json",
"filepath5.json",
"filepath6.json",
"filepath7.json",
"filepath8.json",
"filepath9.json"
]
when trying to decode i am getting an error:
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
For the purpose of the example i have removed the web url (supplierURL) where the file is stored
example decoding that I'm using:
let supplierURL = "list.json"
func getSuppliers() {
let urlString = supplierURL
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJSON(supplierList: safeData)
}
}
task.resume()
}
}
func parseJSON(supplierList: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([SupplierList].self, from: supplierList)
print(decodedData)
} catch {
print(error)
}
}
I have also tried:
let decodedData = try decoder.decode(Array<SupplierList>.self, from: supplierList)
and
let decodedData = try decoder.decode(SupplierList[0].self, from: supplierList)
with no luck.
any assistance is appreciated 😊
Try:
let list = try decoder.decode([String].self, from: supplierList)
You didn't show SupplierList, but you should be able to construct it from that array.

JSON SWIFT, how to access the values

i have the following Json
USD {
"avg_12h" = "8252.96";
"avg_1h" = "8420.80";
"avg_24h" = "8253.11";
"avg_6h" = "8250.76";
rates = {
last = "8635.50";
};
"volume_btc" = "76.05988903";
}
where USD is a key found after searching in a json file, i want to access "avg_12h" value and assign it to a variable, what is the best way to do it.
import UIKit
/*URLSessionConfiguration.default
URLSessionConfiguration.ephemeral
URLSessionConfiguration.background(withIdentifier: <#T##String#>)
// create a URLSession instance
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)*/
/*create a URLSession instance*/
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
/*
The session.dataTask(with: url) method will perform a GET request to the url specified and its completion block
({ data, response, error in }) will be executed once response is received from the server.*/
let url = URL(string: "https://localbitcoins.com/bitcoinaverage/ticker-all-currencies")!
let task = session.dataTask(with: url) { data, response, error in
// ensure there is no error for this HTTP response
guard error == nil else {
print ("error: \(error!)")
return
}
// ensure there is data returned from this HTTP response
guard let content = data else {
print("No data")
return
}
/*JSONSerialization.jsonObject(with: content,
options: JSONSerialization.ReadingOptions.mutableContainers) as?
[String: Any] will parse the JSON data returned from web server into a dictionary*/
// serialise the data / NSData object into Dictionary [String : Any]
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
print("Not containing JSON")
return
}
let bolivares = "VES"
for (key, value) in json {
if key==bolivares {
print(value)
//ADD CODE TO ACCESS avg_12h and assign it to a value
}
}
}
// update UI using the response here
// execute the HTTP request
task.resume()
Assuming you are receiving the JSON as raw data and it hasn't been converted to an object yet, ou would want to do something like the following:
guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) as! [String:[String]] else { return }
let usd = jsonObject["USD"]
let avg_12h = usd["avg_12h"]
But this will only work based on some assumptions I've made about the JSON you've provided. Is there a way you can link to a paste of the full JSON file?
Create two simple structs to hold your data (I didn't add all fields here)
struct PriceInfo {
let avg12h: String
let avg1h: String
let rates: [Rate]
}
struct Rate {
let last: String
}
then after converting json you can map it to a dictionary of [String: PriceInfo] where the key is the currency code
do {
if let json = try JSONSerialization.jsonObject(with: content) as? [String: Any] {
let prices: [String: PriceInfo] = json.mapValues {
let dict = $0 as? [String: Any]
let avg12h = dict?["avg_12h"] as? String ?? ""
let avg1h = dict?["avg_1h"] as? String ?? ""
let rates = dict?["rates"] as? [String: String] ?? [:]
return PriceInfo(avg12h: avg12h, avg1h: avg1h, rates: rates.compactMap { rate in Rate(last: rate.value) } )
}
}
} catch {
print(error)
return
}
Try to use CodingKey, it will be more clearer and JSONDecoder().decode method. I assume that you use any JsonViewer

How to guarantee valid JSON in Swift 4?

I'm trying to work with JSON data returned from a service. The JSON is, according to JSON validators, valid and is very simple:
[{"ID":"SDS-T589863","TotalRisk":0.2458,"TotalScore":641.032}]
However trying to parse it in my Swift 4 code it is mysteriously (to me at least) invalid. Here's my attempt to parse it:
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// this is fine:
guard let ddd = String(bytes: responseData, encoding: String.Encoding.utf8) else {
print("can't")
return
}
print(ddd) // prints [{"ID":"SDS-T589863","TotalRisk":0.2458,"TotalScore":641.032}] happily
do {
// cannot serialize
guard let risk = try JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments])
as? [String: Any]
else {
print("error trying to convert data to JSON")
return
}
print(risk)
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
Assuming that I have no control over the JSON object or the format in which it is returned to me, is there a way to tell what is wrong with the JSON and perhaps format the response so that it can be serialized correctly?
You should cast your data to the [[String: Any]] type because you have array in response.
You are trying to cast to [String: Any], but you have an array of [String: Any] because your response enclosed in [] brackets.
Example:
let risk = try JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments]) as? [[String: Any]]
Or if you want to get just only one [String: Any] object from response you can write:
let risk = (try JSONSerialization.jsonObject(with: responseData, options: [JSONSerialization.ReadingOptions.allowFragments]) as? [[String: Any]])?.first
Or if your object can be an array or not an array (but it sounds a little bit strange) you could try to cast to several possible types.
The response type is array of json objects so you have to cast it to [[String: Any]]. Since you are using Swift 4, you can use Decodable type which maps the model to the response.
let task = URLSession().dataTask(with: urlRequest) { (data, response, error) in
// check for any errors
guard error == nil else {
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder()
let riskArray = try decoder.decode([Risk].self, from: responseData)
print(riskArray)
} catch {
print("error trying to convert data to Model")
print(error.localizedDescription)
}
}
task.resume()
You can define your Model struct like
struct Risk: Decodable {
let id: String
let totalRisk: Double
let totalScore: Double
enum CodingKeys: String, CodingKey {
case id = "ID"
case totalRisk = "TotalRisk"
case totalScore = "TotalScore"
}
}
You can read more about Decodable protocol here