can not convert json to struct object - swift

I want to parse JSON data into a struct object but i can't do it.
Actually the code is in different files but here i'm posting it as a whole.
Here is my code :
import Foundation
struct dataResponse: Decodable {
var results: [userData]
init(from decoder: Decoder) throws {
var results = [userData] ()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let route = try? container.decode(userData.self) {
results.append(route)
}
else {
_ = try? container.decode(dummyData.self)
}
}
self.results = results
}
}
private struct dummyData: Decodable { }
enum dataError: Error {
case dataUnavailable
case cannotProcessData
}
struct userData: Codable {
var avatar: String
var city: String
var contribution: Int
var country: String
var friendOfCount: Int
var handle: String
var lastOnlineTimeSeconds: Int
var maxRank: String
var maxRating: Int
var organization: String
var rank: String
var rating: Int
var registrationTimeSeconds: Int
var titlePhoto: String
}
struct dataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<[userData], dataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
// let dataresponse = try JSONDecoder().decode([userData].self, from: data)
// print(type(of: dataresponse))
// completionHandler(.success(dataresponse))
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject
print(jsonResult)
completionHandler(.success(jsonResult as! [userData]))
}
catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
here userData is my struct
the error says : Could not cast value of type '__NSDictionaryM' (0x7fff8fe2dab0) to 'NSArray' (0x7fff8fe2dd30).
I would appreciate if anyone helps, thanks.

You are making a very common mistake.
You are ignoring the root object which is a dictionary and causes the error.
struct Root: Decodable {
let status : String
let result: [UserData]
}
struct UserData: Decodable {
let avatar: String
let city: String
let contribution: Int
let country: String
let friendOfCount: Int
let handle: String
let lastOnlineTimeSeconds: Int
let maxRank: String
let maxRating: Int
let organization: String
let rank: String
let rating: Int
let registrationTimeSeconds: Int
let titlePhoto: String
}
Forget JSONSerialization and use only JSONDecoder
And it's not a good idea to return meaningless enumerated errors. Use Error and return the real error.
You get the array with dataresponse.result
struct DataRequest { // name structs always with starting capital letter
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Root, Error>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(error!))
print("-------bye-bye--------")
return
}
do {
print("-------entered--------")
let dataresponse = try JSONDecoder().decode(Root.self, from: data)
completionHandler(.success(dataresponse))
}
catch {
completionHandler(.failure(error))
}
}.resume()
}
}
And consider that if status is not "OK" the JSON response could be different.

Your problem is you are using the wrong struct. Create and use a Response struct for decoding. You need to keep your Types to have the first letter in capital to avoid confusion. You could use the JSONDecoder() maybe you are using something that doesn't have the correct format. Try the below code.
struct Response: Codable {
var result: [UserData]
}
enum DataError: Error {
case dataUnavailable, cannotProcessData
}
struct DataRequest {
let requestUrl: URL
init(){
self.requestUrl = URL(string: "https://codeforces.com/api/user.info?handles=abhijeet_ar")!
}
func getData(completionHandler: #escaping(Result<Response, DataError>) -> Void) {
URLSession.shared.dataTask(with: self.requestUrl) { (data,response, error) in
guard let data = data else {
completionHandler(.failure(.dataUnavailable))
return
}
do {
let dataresponse = try JSONDecoder().decode(Response.self, from: data)
completionHandler(.success(dataresponse))
} catch {
completionHandler(.failure(.cannotProcessData))
}
}.resume()
}
}
Note: Parameters in UserData always needs to right. Try with and Empty struct to check if everything else is working then proceed to adding the variable one-by-one.

Related

Fetch API Swift

Previously the API I was working on was as below
{
"kurumsicilno": 457.0,
"yillikizin": 30.0,
}
and I built my model as below
struct Leave: Decodable, Identifiable {
var id: Double? {
return registerNumber
}
let registerNumber: Double
let annualLeave: Double
enum CodingKeys: String, CodingKey {
case registerNumber = "kurumsicilno"
case annualLeave = "yillikizin"
}
}
This was my network function
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let result = try decoder.decode(Leave.self, from: safeData)
DispatchQueue.main.async {
completion(result)
}
} catch {
print(error)
}
}
}
}
task.resume()
But for some reason, they have changed the API to this.
{
"isSucceed": true,
"singleData": {
"sicilNo": "457",
"yillikIzin": "30",
},
How should I modify my model so that I can reach and fetch the data as before?
Just create a new root struct
struct Response : Decodable {
let isSucceed: Bool
let singleData: Leave
}
and you have to change the types and (one of) the CodingKeys
struct Leave: Decodable, Identifiable {
var id: String { // no Optional!!
return registerNumber
}
let registerNumber: String
let annualLeave: String
enum CodingKeys: String, CodingKey {
case registerNumber = "sicilNo"
case annualLeave = "yillikIzin" // is this really a capital `I`?
}
}
Finally change the decoding code
let result = try decoder.decode(Response.self, from: safeData)
DispatchQueue.main.async {
completion(result.singleData)
}
And you might have to manage the type change Double → String in your other code.

Swiift - Decoding json data from server

Im working on a project where I have to display data from a network call. The problem is Im having trouble decoding data the data that I received from the network call and storing it into structs variable to use for other calls. The deadline is coming close and Im not sure why my code does not work. This is json that I receive back
{"result":{"login":{"isAuthorized":true,"isEmpty":false,"userName":{"isEmpty":false,"name":{"firstName":"Jason","lastName":"Test","displayName":"Test, Jason","isEmpty":false,"fullName":"Jason Test"},"canDelete":false,"id":5793,"canModify":false},"username":"test#testable.com"},"parameters":{"isEmpty":false,"keep_logged_in_indicator":false,"username":"test#testable.com"}},"isAuthorized":true,"version":{"major":"2021","minor":"004","fix":"04","display":"2021.004.04","isEmpty":false},"isSystemDown":false,"timestamp":"2021-07-26T20:21:43Z","isSuccess":true}
This is the different struct that I made in my project
struct ApiResponse: Decodable {
let results: Results?
let isAuthorized: Bool?
let version: Version?
let isSystemDown: Bool?
let errors: [serverError]?
let timestamp: Date?
let isSuccess: Bool?
}
// MARK: - Result
struct Results: Decodable {
let login: Login
let parameters: Parameters?
}
// MARK: - Login
struct Login: Decodable {
let isAuthorized, isEmpty: Bool?
let userName: UserName
let username: String?
}
// MARK: - UserName
struct UserName: Decodable {
let isEmpty: Bool?
let name: Name
let canDelete: Bool?
let id: Int
let canModify: Bool?
}
// MARK: - Name
struct Name: Decodable {
let firstName, lastName, displayName: String
let isEmpty: Bool?
let fullName: String
}
// MARK: - Parameters
struct Parameters: Decodable {
let isEmpty, keepLoggedInIndicator: Bool?
let username: String?
enum CodingKeys: String, CodingKey {
case isEmpty
case keepLoggedInIndicator
case username
}
}
// MARK: - Version
struct Version: Decodable {
let major, minor, fix, display: String?
let isEmpty: Bool?
}
// Mark: - Error
struct serverError: Decodable {
let password: String?
}
The code that I am using to decode the json data is this
private func handleResponse<T: Decodable>(result: Result<Data, Error>?, completion: (Result<T, Error>) -> Void) {
guard let result = result else {
completion(.failure(AppError.unknownError))
return
}
switch result {
case .success(let data):
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print("Server JsonObject response: \(json)")
} catch {
completion(.failure(error))
}
let decoder = JSONDecoder()
// decodes the Server response
guard let response = try? decoder.decode(ApiResponse.self, from: data) else {
print("Something happen here")
completion(.failure(AppError.errorDecoding))
return
}
// Returns if an error occurs
if let error = response.errors {
completion(.failure(AppError.serverError(error)))
return
}
// Decodes the data received from server
if let decodedData = response.results {
completion(.success(decodedData as! T))
} else {
completion(.failure(AppError.errorDecoding))
}
case .failure(let error):
completion(.failure(error))
}
}
If anyone could help me understand why my code isnt working that would be much appreciated.
Your struct is wrong. Try using this?
struct YourAPIData {
struct Root: Codable {
struct Result: Codable {
struct Login: Codable {
let isAuthorized: Bool
let isEmpty: Bool
struct UserName: Codable {
let isEmpty: Bool
struct Name: Codable {
let firstName: String
let lastName: String
let displayName: String
let isEmpty: Bool
let fullName: String
}
let name: Name
let canDelete: Bool
let id: Int
let canModify: Bool
}
let userName: UserName
let username: String
}
let login: Login
struct Parameters: Codable {
let isEmpty: Bool
let keepLoggedInIndicator: Bool
let username: String
private enum CodingKeys: String, CodingKey {
case isEmpty
case keepLoggedInIndicator = "keep_logged_in_indicator"
case username
}
}
let parameters: Parameters
}
let result: Result
let isAuthorized: Bool
struct Version: Codable {
let major: String
let minor: String
let fix: String
let display: Date
let isEmpty: Bool
}
let version: Version
let isSystemDown: Bool
let timestamp: String
let isSuccess: Bool
}
}
and try using
do {
let APIData = try JSONDecoder().decode(YourAPIData.Root.self, from: jsonData)
} catch let jsonErr { print("Error: ", jsonErr) }
And if you would like to display your json (for testing, i suppose?)
if let data = jsonData, let body = String(data: jsonData, encoding: .utf8) {
print(body)
}
} else {
print(error ?? "Unknown error")
}

Decode Nested JSON REST Api Response in to Array of Structs

I am trying to parse the response of https://swapi.dev/api/people/ in to a List but I can't seem to get it work due to the nested response structure instead of a flat response.
This is my ContentView:
struct ContentView: View {
private let charactersURL = URL(string: "https://swapi.dev/api/people/")!
#State private var characters: [Character] = []
var body: some View {
NavigationView {
VStack {
List {
ForEach(characters, id: \.self) { character in
NavigationLink(destination: CharacterView(character: character)) {
Text(character.name)
}
}
}
.onAppear(perform: loadCharacter)
}
.navigationBarTitle("Swapi Api Client")
}
}
private func loadCharacter() {
let request = URLRequest(url: charactersURL)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let charactersResponse = try? JSONDecoder().decode([Character].self, from: data.results) {
withAnimation {
self.characters = charactersResponse.results
}
}
}
}.resume()
}
}
I have a CharactersResponse struct:
struct CharactersResponse {
let count: Int
let next: String
let previous: NSNull
let results: [Character]
}
and a Character struct:
struct Character: Decodable, Hashable {
let name: String
let height: Int
let mass: String
let hairColor: String
let skinColor: String
let eyeColor: String
let birthYear: String
let gender: String
let homeworld: String
let films: [String]
let species: [String]
let vehicles : [String]
let starships: [String]
let created: String
let edited: String
let url: String
}
The error I get is Value of type 'Data' has no member 'results' but I'm not sure how to fix it so that the CharactersResponse results are parsed in to an array of Character structs.
You are mixing up the raw data and the deserialized structs
Replace
if let charactersResponse = try? JSONDecoder().decode([Character].self, from: data.results) { ...
with
if let charactersResponse = try? JSONDecoder().decode(CharactersResponse.self, from: data) { ...
And adopt Decodable in CharactersResponse
struct CharactersResponse : Decodable { ...
You might also replace NSNull with an optional of the expected type
Never try? in a JSONDecoder context. catch a potential error and print it.
do {
let charactersResponse = try JSONDecoder().decode(CharactersResponse.self, from: data) { ...
} catch { print(error) }

Firebase - How do I read this map via embedded structs?

I am reading data from Firestore to be able to populate into expanding tableview cells. I have a really simple struct:
protocol PlanSerializable {
init?(dictionary:[String:Any])
}
struct Plan{
var menuItemName: String
var menuItemQuantity: Int
var menuItemPrice: Double
var dictionary: [String: Any] {
return [
"menuItemName": menuItemName,
"menuItemQuantity": menuItemQuantity,
"menuItemPrice": menuItemPrice
]
}
}
extension Plan : PlanSerializable {
init?(dictionary: [String : Any]) {
guard let menuItemName = dictionary["menuItemName"] as? String,
let menuItemQuantity = dictionary["menuItemQuantity"] as? Int,
let menuItemPrice = dictionary["menuItemPrice"] as? Double
else { return nil }
self.init(menuItemName: menuItemName, menuItemQuantity: menuItemQuantity, menuItemPrice: menuItemPrice)
}
}
And this is embedded in this struct:
protocol ComplainSerializable {
init?(dictionary:[String:Any])
}
struct Complain{
var status: Bool
var header: String
var message: String
var timeStamp: Timestamp
var email: String
var planDetails: Plan
var dictionary: [String: Any] {
return [
"status": status,
"E-mail": header,
"Message": message,
"Time_Stamp": timeStamp,
"User_Email": email,
"planDetails": planDetails
]
}
}
extension Complain : ComplainSerializable {
init?(dictionary: [String : Any]) {
guard let status = dictionary["status"] as? Bool,
let header = dictionary["E-mail"] as? String,
let message = dictionary["Message"] as? String,
let timeStamp = dictionary["Time_Stamp"] as? Timestamp,
let email = dictionary["User_Email"] as? String,
let planDetails = dictionary["planDetails"] as? Plan
else { return nil }
self.init(status: status, header: header, message: message, timeStamp: timeStamp, email: email, planDetails: planDetails)
}
}
However, I am not able to query any data from Firestore which looks like this:
Here is my query, although I am just reading all the files:
let db = Firestore.firestore()
var messageArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
self.messageArray = documentSnapshot!.documents.compactMap({Complain(dictionary: $0.data())})
for plan in self.messageArray {
print("\(plan.email)")
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
What am I doing wrong?
EDIT:
As suggested, here is the updated embedded struct:
import Foundation
// MARK: - Complain
struct Complain: Codable {
let eMail, message, timeStamp, userEmail: String
let status: Bool
let planDetails: PlanDetails
enum CodingKeys: String, CodingKey {
case eMail = "E-mail"
case message = "Message"
case timeStamp = "Time_Stamp"
case userEmail = "User_Email"
case status, planDetails
}
}
// MARK: - PlanDetails
struct PlanDetails: Codable {
let menuItemName: String
let menuItemQuantity: Int
let menuItemPrice: Double
}
Using quicktype.io, you can generate the struct. From there, all you need to do is run this tiny fragment of code within your response handler.
var compainArray = [Complain]()
func loadMenu() {
db.collection("Feedback_Message").getDocuments() { documentSnapshot, error in
if let error = error {
print("error:\(error.localizedDescription)")
} else {
guard let snapshot = documentSnapshot else {return}
for document in snapshot.documents {
if let jsonData = try? JSONSerialization.data(withJSONObject: document.data()){
if let converted = try? JSONDecoder().decode(Complain.self, from: jsonData){
self.compainArray.append(converted)
}
}
}
DispatchQueue.main.async {
self.testTable.reloadData()
}
}
}
}
Which will handle the looping, and mapping of certain variables. Let me know if you have any trouble with this.

Decodable not working with non empty array

I'm using this library which has created non-empty collections like arrays, dictionaries and strings. https://github.com/pointfreeco/swift-nonempty
When I decode to the non-empty array I get the following error
Swift.DecodingError.typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "poiList", intValue: nil)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
This is my structure
struct LocationCarModel: Codable {
// MARK: - Properties
var poiList: NonEmptyArray<PointOfInterest>
// MARK: - PointOfInterest
struct PointOfInterest: Codable {
var id: Int
var coordinate: Position
var fleetType: String
let numberPlate = "HCD837EC"
let model: String = "Tesla S"
let fuel: Double = 0.9
}
}
This is the response I'm getting https://fake-poi-api.mytaxi.com/?p2Lat=53.394655&p1Lon=9.757589&p1Lat=53.694865&p2Lon=10.099891
and this how I'm decoding it.
public extension Decodable {
static func parse(from item: Any?, strategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) -> Self? {
guard let data = self.data(from: item) else {
return nil
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = strategy
do {
let result = try decoder.decode(Self.self, from: data)
return result
} catch {
debugPrint(error)
return nil
}
}
private static func data(from item: Any?) -> Data? {
switch item {
case let data as Data:
return data
case let string as String:
return string.data(using: .utf8)
case .some(let item):
return try? JSONSerialization.data(withJSONObject: item, options: [])
case nil:
return nil
}
}
}
and the line to decode using the above function is
let model = LocationCarModel.parse(from: data)
Changing poiList to the standard swift array then no error occurs.
Any ideas how I can solve this issue? Any help would be appreciated. Thank you in advance.
You need to have your own init(from:) for the top struct since JSONDecoder doesn't understand NonEmpty and how to initialise it. Apart from the init I also added coding keys and a new error
enum DecodeError: Error {
case arrayIsEmptyError
}
struct LocationCarModel: Codable {
var poiList: NonEmpty<[PointOfInterest]>
enum CodingKeys: String, CodingKey {
case poiList
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let array = try container.decode([PointOfInterest].self, forKey: .poiList)
guard let first = array.first else {
throw DecodeError.arrayIsEmptyError
}
poiList = NonEmptyArray(first, array)
}
//...
}
You can try
struct Root: Codable {
let poiList: [PoiList]
}
// MARK: - PoiList
struct PoiList: Codable {
let id: Int
let coordinate: Coordinate
let fleetType: String
let heading: Double
}
// MARK: - Coordinate
struct Coordinate: Codable {
let latitude, longitude: Double
}
let res = try? JSONDecoder().decode(Root.self,from:data)
print(res)