I'm trying to decode a JSON I get from a server. But I always get this error:
The data couldn’t be read because it is missing.
I don't understand why I get this error, I looked and saw few answers but nothing solved my problem, I can't figure it out.
This is the response I get from the server (as shown in Postman):
{
"email": "new#user.com",
"username": "newuser",
"user_rating": 0,
"store_id": "",
"state": "",
"city": "",
"address": "",
"zip_code": 0,
"user_id": "user_id_test",
"created_on": 1585933800
}
This is my Swift code:
func getUser(id: String, completion: #escaping(User?, Error?) -> Void) {
let urlParams = "/?id=\(id)"
guard let url = URL(string: baseURL + "getUser" + urlParams) else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
_ = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let error = error {
completion(nil, error)
debugPrint(error) //TODO: Handle error.
return
}
guard let jsonData = data else { return }
print(String(bytes: jsonData, encoding: .utf8))
do {
let user = try self.decoder.decode(User.self, from: jsonData)
completion(user, nil)
} catch { print(error.localizedDescription) }
}).resume()
}
This line: print(String(bytes: jsonData, encoding: .utf8)) prints:
Optional("{\"email\":\"new#user.com\",\"username\":\"newuser\",\"user_rating\":0,\"store_id\":\"\",\"state\":\"\",\"city\":\"\",\"address\":\"\",\"zip_code\":0,\"user_id\":\"user_id_test\",\"created_on\":1585933800}")
Which looks okay to me.
This is the User Struct I'm trying to decode:
struct User: Codable {
let email: String!
let username: String!
let rating: Float!
let storeID: String?
let state: String?
let city: String?
let address: String?
let zipCode: Int
let uuid: String!
let createdOn: TimeInterval!
}
Related
I have the following JSON...
{
"id": "1000035148",
"petId": "3",
"ownerId": "1000",
"locationId": null,
"status": "Active",
“services”: [
{
"id": "5004",
“data”: 1,
“data1”: 0,
“data2": 63,
“data3": 0
}
]
}
And I'm only trying to return the following objects...
"id": "1000035148",
"petId": "3",
"ownerId": "1000",
"locationId": null,
"status": "Active"
How can I achieve this with the following code?
session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in
if let data = data {
do {
let jsonData = try JSONSerialization.jsonObject(with: data)
if let dictionary = jsonData as? [String: Any] {
if let nestedDictionary = dictionary["status"] as? [String: Any] {
for (key, value) in nestedDictionary {
print("Key: \(key), Value: \(value)")
}
}
}
print(jsonData)
} catch {
print("Error fetching data from API: \(error.localizedDescription)")
}
}
When I try to parse using the nestedDictionary = dictionary I get an error and it skips over the line. I'm confused on how to get just the key value pairs I want from the response.
Forget JSONSerialization and use Decodable with JSONDecoder:
struct DataModel: Decodable {
let id: String
let pedId: String?
let ownerId: String?
let locationId: String?
let status: String
}
do {
let dataModel = try JSONDecoder().decode(DataModel.self, from: data)
print("Status: \(dataModel.status)")
} catch ...
If you want to use JSONSerialization, note that status is not a dictionary, it's a String:
if let dictionary = jsonData as? [String: Any] {
if let status = dictionary["status"] as? String {
print("Status: \(status)")
}
}
I have looked through other threads regarding trying to parse JSON data where a JSON array has no name. From what I have found you need to use a unkeyedContainer but I'm not entirely sure from the examples I have seen how this works with the data model.
Below is a snippet of data from open charge api:
[
{
"IsRecentlyVerified": false,
"ID": 136888,
"UUID": "254B0B07-E7FC-4B4B-A37C-899BCB9D7261",
"DataProviderID": 18,
"DataProvidersReference": "0a9fdbb17feb6ccb7ec405cfb85222c4",
"OperatorID": 3,
"UsageTypeID": 1,
"AddressInfo": {
"ID": 137234,
"Title": "Ballee Road Park & Share",
"AddressLine1": "Ballee Road",
"Town": "Ballymena",
"Postcode": "BT42 2HD",
"CountryID": 1,
"Latitude": 54.844648,
"Longitude": -6.273606,
"AccessComments": "Ballee Road Park and Share, Ballymena",
"RelatedURL": "http://pod-point.com",
"Distance": 3.81818421833416,
"DistanceUnit": 2
},
"Connections": [
{
"ID": 191571,
"ConnectionTypeID": 25,
"Reference": "1",
"StatusTypeID": 50,
"LevelID": 2,
"Amps": 32,
"Voltage": 400,
"PowerKW": 22,
"CurrentTypeID": 20
},
It looks to me that the first [ and { have no attribute names which I belive is creating the error in xcode: "Error!: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))"
Here is my data model:
import Foundation
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
Here is my code:
//Find public chargers from local coordinates
func findPublicChargers(lat: Double, long: Double) {
//Use apiurl to pull all charge points that are currently in that area by adding lat and long into the api call &latitude=***&longitude=*****
let apiurl = "https://api.openchargemap.io/v3/poi/?output=json&countrycode=UK&maxresults=100&compact=true&verbose=false"
let urlString = "\(apiurl)&latitude=\(lat)&longitude=\(long)"
//print(urlString)
performRequest(urlString: urlString)
}
//Perform API Request - (London App Brewry code)
//Create the custom url
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
//print("Called")
//Create a URL Session
let session = URLSession(configuration: .default)
//Give the session a task
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
//let dataString = String(data: safeData, encoding: .utf8)
//print(dataString)
self.parseJSON(data: safeData)
print("Data: \(safeData)")
}
}
//Start the task
task.resume()
}
}
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(PublicCharger.self, from: data)
print("Data: \(decodedData.AddressInfo[0].Title)")
} catch {
print("Error!: \(error)")
}
}
struct AddressInfo: Decodable {
let Title: String
}
I have seen that in the data model you would need to include an unkeyed container element. I'm just not sure how this should be carried out in the data model. Any light on this would be much appreciated.
Try to change your PublicCharger data model to
struct PublicCharger: Decodable {
let AddressInfo: [AddressInfo]
}
And your parseJSON function to
func parseJSON(data: Data){
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([PublicCharger].self, from: data)
if !decodedData.isEmpty {
print("Data: \(decodedData[0].AddressInfo[0].Title)")
} else {
print("Empty result!")
}
} catch {
print("Error!: \(error)")
}
}
I am accessing an API and decoding the json response into a User object, but I am attempting to change the JSON API strcuture. If I return a basic JSON object using this code
let httpURL = "https://dev.test/api/user"
var request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let user = try JSONDecoder().decode(User.self, from: data)
DispatchQueue.main.async {
print(user.email)
}
} catch let jsonErr {
print(jsonErr)
}
}
task.resume()
and the following JSON
{
"id": 2,
"email": "test#example.com",
}
This works fine, but I want to changed the API to return a set of nested objects. For example
{
"data": {
"user": {
"id": 2,
"email": "test#example.com"
},
"notifications": [
{
"id": "123",
"notifiable_type": "App\\User"
}
]
}
}
How can I decode the User? I've tried several variations of this let user = try JSONDecoder().decode(User.self, from: data.data.user) and let user = try JSONDecoder().decode(User.self, from: data["data"]["user"])
bt
You can try
struct Root: Codable {
let data: DataClass
}
struct DataClass: Codable {
let user: User
let notifications: [Notification]
}
struct Notification: Codable {
let id, notifiableType: String
enum CodingKeys: String, CodingKey {
case id
case notifiableType = "notifiable_type"
}
}
struct User: Codable {
let id: Int
let email: String
}
let user = try JSONDecoder().decode(Root.self, from:data)
OR
do {
let con = try JSONSerialization.jsonObject(with:data, options: [:]) as! [String:Any]
let data = con["data"] as! [String:Any]
let user = data["user"] as! [String:Any]
let finData = try JSONSerialization.data(withJSONObject:user, options: [:])
let userCon = try JSONDecoder().decode(User.self, from:finData)
print(userCon)
}
catch {
print(error)
}
I need help with parsing json file called weather.json.
weather.json
{
"weatherinfo": {
"local": [
{
"country": "Korea",
"weather": "rainy",
"temperature": "20"
},
{
"country": "US",
"weather": "sunny",
"temperature": "30"
}
]
}
}
And here's my code
struct Weather: Decodable {
var weatherInfo: WeatherInfo?
}
struct WeatherInfo: Decodable {
let local: [Local]?
}
struct Local: Decodable {
let country: String?
let weather: String?
let temperature: String?
}
inside func viewDidLoad() in UIViewController
let decoder = JSONDecoder()
guard let path: String = Bundle.main.path( forResource: "weather", ofType: "json") else { return }
let jsonURL = URL(fileURLWithPath: path)
URLSession.shared.dataTask(with: jsonURL) { (data, response, error) in
guard let data = data else { return }
print("pass1")
do {
let weather = try decoder.decode(Weather.self, from: data)
print("parsing pass..")
print(weather) // Weather(weatherInfo: nil)
print(weather.weatherInfo?.local?[0].country) // nil
} catch let jsonErr{
print("Error: \(jsonErr)")
}
}.resume()
I succeed parsing but I can't get data from weather constant..
How can I get country value from that json file..?
Can anyone fix my code please?..
First of all URLSession for reading a file in the bundle is overkill. Just get the Data.
Second of all declare everything non-optional since you clearly know that all keys are available
struct Weather: Decodable {
let weatherinfo: WeatherInfo // note the lowercase spelling
}
struct WeatherInfo: Decodable {
let local: [Local]
}
struct Local: Decodable {
let country: String
let weather: String
let temperature: String
}
The countries are in the array local in weatherInfo
let url = Bundle.main.url(forResource: "weather", withExtension: "json")!
let data = try! Data(contentsOf: url)
let result = try! JSONDecoder().decode(Weather.self, from: data)
for location in result.weatherinfo.local {
print("In \(location.country) the weather is \(location.weather) and the temperature is \(location.temperature) degrees")
}
How to fix this error?
Cannot convert value of type '(ApiContainer, ) -> ()' to
expected argument type '(ApiContainer<>?, Error?) -> ()
Screenshot showing the error
JSON response from the server:
{
"meta": {
"sucess": "yes",
"value": "123"
},
"result": [
{
"name": "Name 1",
"postal_code": "PC1",
"city": "City 1",
"address": "01 Street"
},
{
"name": "Name 2",
"postal_code": "PC2",
"city": "City 2",
"address": "02 Street"
}
]
}
Structs
struct Client: Decodable {
let name: String
let postal_code: String
let city: String
}
struct Meta: Decodable {
let sucess: String
let value: String
}
struct ApiContainer<T: Decodable>: Decodable
let meta: Meta
let result: [T]
}
I have a function 'getAll' that is supposed to make a request and return the correspondent struct (ApiContainer where T can be for example Client)
func getAll() {
makeRequest(endpoint: "http://blog.local:4711/api/all", completionHandler:
{(response: ApiContainer<Client>, error) in
if let error = error {
print("error calling POST on /todos")
print(error)
return
}
print(result)
//self.tableArray = decodedData.result
DispatchQueue.main.async {
self.tableView.reloadData()
}
} )
}
Function makeRequest is called from getAll()
func makeRequest<T>(endpoint: String, completionHandler: #escaping (ApiContainer<T>?, Error?) -> ()) {
guard let url = URL(string: endpoint) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not create URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
completionHandler(nil, error!)
return
}
do {
let response = try JSONDecoder().decode(ApiContainer<T>.self, from: responseData)
completionHandler(response, nil)
}
catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
})
task.resume()
}
In case of generics you have to annotate the types in the closure explicitly to specify the static type of the generics
makeRequest(endpoint: "http://blog.local:4711/api/all",
completionHandler: { (container : ApiContainer<Client>?, error : Error?) in ...