Filter JSON in Swift 4 - json

I want to filter my JSON and I can't find a way.
My JSON :
{
"id": "3",
"nom": "Blancs sablons",
"description": "Plage gigantesque, très souvent des surfeurs à l'eau."
},
{
"id": "4", // id to search
"nom": "Autre nom", // text to print
"description": "Encore une description"
},
{
"id": "5",
"nom": "Nom différent",
"description": "Et la dernière description"
},
I want to be able to print 'Autre nom' by calling
print(Spot[4].description)
Where 4 is the id
So I tried this struct Spot with constructor :
import Foundation
import MapKit
struct Spot : Decodable {
let nom : String
let description : String
let id: String
init(nom: String, description: String, id: String, img1: String, latitude: String, longitude: String) {
self.nom = nom
self.description = description
self.id = id
self.img1 = img1
self.latitude = latitude
self.longitude = longitude
}
}
And this to decode JSON :
func getSpots(){
guard let downloadURL = URL(string: "http://dronespot.fr/getSpot.php") else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Oops Call for Help")
return
}
do {
let decoder = JSONDecoder()
let rates = try decoder.decode([Spot].self, from: data)
} catch {
print("Error after loading", error)
}
}.resume()
}
Any idea ?

You are getting an array of results back so you just need to select the one that you want out of the array.
In my test code I didn't find one with an id of 4.
You can filter the array using the filter high order function let rate = rates.filter { $0.id == "36" }
Here is the code I used in a Playground to test
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
struct Spot : Decodable {
let nom : String
let description : String
let id: String
}
func getSpots(){
guard let downloadURL = URL(string: "http://dronespot.fr/getSpot.php") else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Oops Call for Help")
return
}
do {
let decoder = JSONDecoder()
let rates = try decoder.decode([Spot].self, from: data)
let rate = rates.filter { $0.id == "36" }
print(rate)
} catch {
print("Error after loading", error)
}
}.resume()
}
getSpots()
PlaygroundPage.current.needsIndefiniteExecution = true

Related

JSON Decoder not working when inserting data into CoreData in Swift

My app consists of a ProductFamily entity with a name attribute and an array of PartDetail dictionaries defined as a one-to-many relationship in CoreData. For each ProductFamily, I can have many PartDetail entities (PartNumbers) but for each PartDetail, it can only be associated with one ProductFamily. My example has 5 ProductFamilies, each with an array of 5 PartDetail dictionaries. I'm struggling to get my JSON decoder correct. It's not importing any data into CoreData. You can clone my sample project here:
https://github.com/jegrasso19/ProductFinder-Test2.git
A sample of my JSON data looks like this:
[
{
"Product Family 1": [
{
"partNumber": "160-9013-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9104-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9105-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9108-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9109-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
},
{
"Product Family 2": [
{
"partNumber": "160-9113-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9114-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9115-900",
"orderable": false,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9116-900",
"orderable": true,
"pnDescription": "Part Number Description"
},
{
"partNumber": "160-9201-900",
"orderable": true,
"pnDescription": "Part Number Description"
}
]
}
]
My ProductFamilyJSON Decoder file and ProductFamilyProperties looks like this:
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
// This is where my code fails. When decoding the JSON file,
// it never goes into the while loop.
var productFamily = try ProductFamilyProperties(from: decoder)
while !rootContainer.isAtEnd {
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailInfo>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailInfo.self) {
partNumbers.append(partNumber)
}
}
productFamily.code = UUID().uuidString
productFamily.name = productFamilyKey.stringValue
productFamily.partNumbers = partNumbers
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}
import Foundation
struct ProductFamilyProperties : Decodable {
var code: String
var name: String
var partNumbers: Array<PartDetailInfo>
enum CodingKeys: String, CodingKey {
case code
case name
case partNumbers
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let rawCode = try? values.decode(String.self, forKey: .code)
let rawName = try? values.decode(String.self, forKey: .name)
let rawPartNumbers = try? values.decode(Array<PartDetailInfo>.self, forKey: .partNumbers)
guard let code = rawCode,
let name = rawName,
let partNumbers = rawPartNumbers
else {
throw myError.programError("Missing Data from Product Family")
}
self.code = code
self.name = name
self.partNumbers = partNumbers
}
var dictionaryValue: [String: Any] {
[
"code": code,
"name": name,
"partNumbers": partNumbers
]
}
}
In my ProductFamilyJSON file, it seems to quit at defining the productFamily variable, which is based on my ProductFamilyProperties. This is apparently wrong but I don't know what it should be defined as. This is my first iOS app I'm trying to develop and learn from. I've spent a while learning CoreData and I've seen so many examples but very few use NSBatchInsertRequest and everyone seems to do this a little differently. I would appreciate some insight on getting this to work. Thanks.
Here is my CoreDataManager class, which contains the NSBatchInsertRequest for reference.
import Foundation
import CoreData
class CoreDataManager: ObservableObject {
let persistentContainer: NSPersistentContainer
static var shared = CoreDataManager()
var viewContext: NSManagedObjectContext {
return persistentContainer.viewContext
}
private init() {
persistentContainer = NSPersistentContainer(name: "ProductFinderTest")
persistentContainer.loadPersistentStores { (description, error) in
if let error = error {
fatalError("Unable to initialize Core Data \(error)")
}
}
let directories = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
print(directories[0])
}
func newTaskContext() -> NSManagedObjectContext {
let taskContext = persistentContainer.newBackgroundContext()
taskContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
taskContext.undoManager = nil
return taskContext
}
}
extension CoreDataManager {
func fetchProductData() async throws {
guard let url = Bundle.main.url(forResource: "ProductFamilies", withExtension: "json"),
let jsonData = try? Data(contentsOf: url)
else {
throw myError.programError("Failed to receive valid response and/or Product Family data.")
}
do {
let jsonDecoder = JSONDecoder()
// ProductFamilyJSON uses this code
let productFamilyJSON = try jsonDecoder.decode(ProductFamilyJSON.self, from: jsonData)
let productFamilyList = productFamilyJSON.productFamilies
print("Received \(productFamilyList.count) Product records.")
print("Start importing product data to the store...")
try await importProductData(from: productFamilyList)
print("Finished importing product data.")
} catch {
throw myError.programError("Wrong Data Format for Product Families")
}
}
private func importProductData(from productList: [ProductFamilyProperties]) async throws {
guard !productList.isEmpty else { return }
let taskContext = newTaskContext()
taskContext.name = "importProductDataContext"
taskContext.transactionAuthor = "importProductData"
try await taskContext.perform {
let batchInsertRequest = self.productListBatchInsertRequest(with: productList)
if let fetchResult = try? taskContext.execute(batchInsertRequest),
let batchInsertResult = fetchResult as? NSBatchInsertResult,
let success = batchInsertResult.result as? Bool, success {
return
}
else {
throw myError.programError("Failed to execute ProductList batch import request.")
}
}
print("Successfully imported Product data.")
}
private func productListBatchInsertRequest(with productList: [ProductFamilyProperties]) -> NSBatchInsertRequest {
var index = 0
let total = productList.count
let batchInsertRequest = NSBatchInsertRequest(entity: ProductFamily.entity(), dictionaryHandler: { dictionary in
guard index < total else { return true }
dictionary.addEntries(from: productList[index].dictionaryValue)
index += 1
return false
})
return batchInsertRequest
}
func requestProductFamilies() -> NSFetchedResultsController<ProductFamily> {
var fetchedResultsController: NSFetchedResultsController<ProductFamily>!
let request: NSFetchRequest = ProductFamily.fetchProductFamilyRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
managedObjectContext: viewContext,
sectionNameKeyPath: nil,
cacheName: nil)
try? fetchedResultsController.performFetch()
return fetchedResultsController
}
func deleteProductData() async throws {
let taskContext = self.newTaskContext()
let fetchedResultsController = requestProductFamilies()
try fetchedResultsController.performFetch()
let productFamilies = (fetchedResultsController.fetchedObjects ?? []).map(ProductFamilyViewModel.init)
guard !productFamilies.isEmpty else {
print("ProductFamily database is empty.")
return
}
let objectIDs = productFamilies.map { $0.objectId }
print("Start deleting Product data from the store...")
try await taskContext.perform {
let batchDeleteRequest = NSBatchDeleteRequest(objectIDs: objectIDs)
guard let fetchResult = try? taskContext.execute(batchDeleteRequest),
let batchDeleteResult = fetchResult as? NSBatchDeleteResult,
let success = batchDeleteResult.result as? Bool, success
else {
throw myError.programError("Failed to execute Product Family batch delete request.")
}
}
print("Successfully deleted Product data.")
}
}
The problem was with how I was initializing the productFamily variable. I needed to initialize it with the actual values instead of as an empty variable. I also needed to move the nestedProductFamilyContainer inside the while loop. Here is the correct ProductFamilyJSON decoder file. In addition, I changed the partNumber attribute in my ProductFamily entity from NSSet to Array, which allowed more flexibility.
#vadian - I did remove the init(from:) and CodingKeys from ProductFamilyProperties as you suggested and it works just fine. Thanks for the input.
import Foundation
struct ProductFamilyJSON: Decodable {
// Struct that conforms with CodingKey so we can retrieve the product family name as a key
//
private struct JSONCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.init(stringValue: "\(intValue)")
self.intValue = intValue
}
}
// This is the dictionary that contains the JSON data
// The key is the ProductFamily name, and the value is an array of PartDetailInfo.
//
private(set) var productFamilies = [ProductFamilyProperties]()
init(from decoder: Decoder) throws {
var rootContainer = try decoder.unkeyedContainer()
while !rootContainer.isAtEnd {
let nestedProductFamilyContainer = try rootContainer.nestedContainer(keyedBy: JSONCodingKeys.self)
let productFamilyKey = nestedProductFamilyContainer.allKeys.first!
if var partNumberArrayContainer = try? nestedProductFamilyContainer.nestedUnkeyedContainer(forKey: productFamilyKey) {
var partNumbers = Array<PartDetailProperties>()
while !partNumberArrayContainer.isAtEnd {
if let partNumber = try? partNumberArrayContainer.decode(PartDetailProperties.self) {
partNumbers.append(partNumber)
}
}
let partNumbersSorted = partNumbers.sorted(by: { $0.partNumber < $1.partNumber })
let productFamily = ProductFamilyProperties(code: UUID().uuidString, name: productFamilyKey.stringValue, partNumbers: partNumbersSorted)
productFamilies.append(productFamily)
}
}
print(productFamilies)
}
}

Importing JSON from URL within a view

So, new to swift and was able to peice this together by reading and watching videos however i reached a point after countless hours searching for a solution..
Basically what the app does is scan's a qr code, parses the url it reads from the qr code to get a key, then I am appending that key to the api url, and i want to output the results from the api to the screen. however I am receiving an error Type '()' cannot conform to 'View' in xcode
Here is sample json data
[
{
"id": "160468",
"sport": "BASKETBALL",
"year": "2020",
"brand": "PANINI PRIZM",
"cardNumber": "278",
"playerName": "LaMELO BALL",
"extra": "",
"gradeName": "MINT",
"grade": "9",
"serial": "63585906",
"authDate": "1656406800",
"link": "https://www.example.com/certificate-verification?certificateNumber=63585906"
}
]
here is my contentview
import SwiftUI
import CodeScanner
extension URL {
var components: URLComponents? {
return URLComponents(url: self, resolvingAgainstBaseURL: false)
}
}
extension Array where Iterator.Element == URLQueryItem {
subscript(_ key: String) -> String? {
return first(where: { $0.name == key })?.value
}
}
struct Card: Decodable {
let sport: String
let year: String
let brand: String
let cardNumber: String
let playerName: String
let extra: String
let gradeName: String
let grade: String
let serial: String
}
struct ContentView: View {
#State var isPresentingScanner = false
#State var scannedCode: String = ""
var scannerSheet : some View {
CodeScannerView(
codeTypes: [.qr],
completion: { result in
if case let .success(code) = result {
self.scannedCode = code.string
self.isPresentingScanner = false
}
}
)
}
func getQueryStringParameter(url: String, param: String) -> String? {
guard let url = URLComponents(string: url) else { return nil }
return url.queryItems?.first(where: { $0.name == param })?.value
}
var body: some View {
VStack(spacing: 10) {
Image("logo-white")
.offset(y: -200)
if let urlComponents = URL(string: scannedCode)?.components,
let cert = urlComponents.queryItems?["certificateNumber"] {
//Text(cert)
let apihit = URL(string: "https://app.example.com/api.php?apikey=xxx&cert=\(cert)")!
//Text(apihit.absoluteString)
var request = URLRequest(url: apihit)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: apihit) { data, response, error in
if let data = data {
if let cards = try? JSONDecoder().decode([Card].self, from: data) {
print(cards)
} else {
print("Invalid Response")
}
} else if let error = error {
print("HTTP Request Failed \(error)")
}
}
}
Button("Scan QR Code") {
self.isPresentingScanner = true
}
.padding()
.background(Color(red: 0, green: 0, blue: 0.5))
.foregroundColor(.white)
.clipShape(Rectangle())
.cornerRadius(20)
.sheet(isPresented: $isPresentingScanner) {
self.scannerSheet
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have tried countless tutorials online, however none of them show how to do it within the view, which I believe is where it belongs because I don't get the actual json url until after I read the qr code..
To populate a link within a view, use Webkit.
First you need to import Webkit. Then, create a WebView which conforms to the UIViewRepresentable protocol.
Here is the Apple documentation. This is the easiest way to integrate web content into your app.
I hope this helps!

Swift weather data from Openweathermap API?

From openweathermap api, I am getting below response.
{
"cod":"200",
"message":0,
"cnt":40,
"list":[
{
"dt":1587643200,
"main":{
"temp":289.78,
"feels_like":283.61,
"temp_min":289.03,
"temp_max":289.78,
"pressure":1014,
"sea_level":1014,
"grnd_level":1010,
"humidity":41,
"temp_kf":0.75
},
"weather":[
{
"id":804,
"main":"Clouds",
"description":"overcast clouds",
"icon":"04d"
}
],
"clouds":{
"all":94
},
"wind":{
"speed":6.75,
"deg":2
},
"sys":{
"pod":"d"
},
"dt_txt":"2020-04-23 12:00:00"
},
{
"dt":1587654000,
"main":{
"temp":289.66,
"feels_like":284.44,
"temp_min":289.34,
"temp_max":289.66,
"pressure":1013,
"sea_level":1013,
"grnd_level":1009,
"humidity":47,
"temp_kf":0.32
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04d"
}
],
"clouds":{
"all":67
},
"wind":{
"speed":5.9,
"deg":357
},
"sys":{
"pod":"d"
},
"dt_txt":"2020-04-23 15:00:00"
}
Then I write the code below to get wind data for any specific daytime(dt). I get the jsonresponse to the Any "list". But I can't get the wind data. I get the error
"Value of type 'Any' has no subscripts".
Also, I can't understand how can I get the wind data for dt=1587643200 and dt=1587654000 separately.
if let list = jsonresponse["list"] as? Any {
let wind = list["wind"] as? [String : Any],
print(wind)
}
This is a super simple example, this question is similar to your problem. I would like that you learn about Codable protocol to simplify and improve your code because of this way is super creepy.
let url = URL(string: "https://samples.openweathermap.org/data/2.5/history/city?id=2885679&type=hour&appid=b1b15e88fa797225412429c1c50c122a1")!
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
guard let data = data else {
return
}
guard let dictionaryObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return
}
guard let list = dictionaryObj["list"] as? [[String: Any]] else {
return
}
if let first = list.first, let wind = first["wind"] {
print(wind)
}
}).resume()
Incase anyone further needs help, I wrote a small code segment—using Swift Codables—to get this done.
Note that I'm using Higher Order Functions in Swift to filter & print the required values. You can follow this tutorial if you need to know more about how they work..
let urlNew = "https://samples.openweathermap.org/data/2.5/history/city?id=2885679&type=hour&appid=b1b15e88fa797225412429c1c50c122a1"
struct WeatherResponse: Codable {
let message: String
let cod: String
let city_id: Int
let calctime: Double
let cnt: Int
let list: [WeatherCompositeObject]
struct WeatherCompositeObject: Codable {
let main: WeatherMainObject
let wind: WeatherWindObject
let clouds: WeatherCloudObject
let weather: [WeatherObject]
let dt: Int
struct WeatherMainObject: Codable {
let temp: Double
let humidity: Int
//You can add the other parameters as needed here
}
struct WeatherWindObject: Codable {
let speed: Double
let deg: Double
}
struct WeatherCloudObject: Codable {
let all: Int
}
struct WeatherObject: Codable {
let id: Int
let main: String
let description: String
}
}
}
class ApiContentDownloader {
func getCurrentWeather(url: String) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, urlResponse, error in
let parser = ContentParser()
let result = parser.parseData(data: data!)
if let result = result {
print("Weather: \(result.message)")
print("DT values: \(result.list.map({ $0.dt})) ")
print("Wind values: \(result.list.map({ $0.wind.speed})) ")
print("Weather descriptions: \(result.list.map({ $0.weather.map( {$0.description} )})) ")
}
else {
print("Oops... Error occured")
}
}.resume()
}
}
class ContentParser {
func parseData(data: Data) -> WeatherResponse? {
var result: WeatherResponse? = nil
do {
let json = try JSONDecoder().decode(WeatherResponse.self, from: data)
result = json
}
catch {
print(error.localizedDescription)
}
return result
}
}
let downloader = ApiContentDownloader()
downloader.getCurrentWeather(url: urlNew)

Swift Parsing decode 2 different json with 1 url api

Hi im new in swift and im kinda still learning, so i try to make login controller and parse a json data if it corrects it parse a json data with id and stuff and if login is failed than the json will show a kinda message. i already make a struct for all the value data that required but i got this error that said its nil.
so, this is the json if the login is success :
[
{
"id": 891,
"name": "User",
"email": "qdpim#immobisp.com",
"status": "1"
} ]
and this is the json if login is failed :
[
{
"message": "Login Failed..",
"status": "0"
} ]
so basicly it has a same url i guess? but i dont know im kinda stuck in here and i need help
struct login : Codable {
let id : Int
let name : String
let email : String
let status : String
let message : String
init(dictionary : [String : Any]) {
id = (dictionary ["id"] as? Int)!
name = (dictionary ["name"] as? String)!
email = (dictionary ["email"] as? String)!
status = (dictionary ["status"] as? String)!
message = (dictionary ["message"] as? String)!
}
enum CodingKeys : String, CodingKey {
case id = "id"
case name = "name"
case email = "email"
case status = "status"
case message = "message"
}
}
func Login() {
let Email = EmailField.text!
let Pass = PasswordField.text!
print(api)
guard let JsonUrl = URL(string: api) else {return}
URLSession.shared.dataTask(with: JsonUrl) { (data, response, error) in
guard let data = data else {return}
do{
let parsing = try JSONDecoder().decode([login].self, from: data)
print(parsing)
self.Loginnn = parsing
let stats = self.Loginnn.map { $0.status}
if stats.contains("1"){
print("Login Success")
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
}else if stats.contains("0") {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: "Please Try Again ", preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
// so basicly i wanna run this alert action by search status if its contains "0"
}
}
}catch{
print(error)
}
}.resume()
}
so when i try to test to failed my login, i doesnt show the message in my json in my log, instead it show this error
"keyNotFound(CodingKeys(stringValue: "id", intValue: nil),
Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index
0", intValue: 0)], debugDescription: "No value associated with key
CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").",
underlyingError: nil))"
i just wanna pop some message or alert if the login is failed because or wrong password or email.....so maybe can someone help me how to do it the best way?
You can declare Success and Failure response types as below,
struct LoginSuccess: Decodable {
var id: Int
var name: String
var email: String
var status: String
}
struct LoginFailure: Decodable {
var status: String
var message: String
}
and then use as,
guard let JsonUrl = URL(string: api) else { return }
URLSession.shared.dataTask(with: JsonUrl) { (data, response, error) in
guard let data = data else { return }
if let success = try? JSONDecoder().decode([LoginSuccess].self, from: data).first {
GlobalVariable.UserId = String(success.id)
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
} else if let failure = try? JSONDecoder().decode([LoginFailure].self, from: data).first {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: failure.message, preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}.resume()
In this situation I would use JSONSerialization to decode the data to a [[String: Any]] and look at the content to determine what kind of message it is.
In my code I have assumed the "status" item tells us if it was a successful login or not but one could for instance look for the presence of "id" or the count of elements in the dictionary as well to determine the type of response
do {
let result = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
if let response = result.first, let status = response["status"] as? String {
if status == "1" {
if let id = response["id"] as? Int {
let ids = String(id)
//...
}
} else {
if let message = response["message"] as? String {
print(message)
}
}
}
} catch {
print(error)
}
Below is my solution used in the code from your question. Note that I have simplified the Login struct since it is only used when login was successful
struct Login {
let id : Int
let name : String
let email : String
}
do {
let result = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
if let response = result.first, let status = response["status"] as? String {
if status == "1" {
//handle success
let login = Login(id: response["id"] as? Int ?? 0,
name: response["name"] as? String ?? "",
email: response["email"] as? String ?? "")
self.Loginnn = login
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
} else {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: "Please Try Again ", preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
} catch {
print(error)
}
The success response only contains the keys ("id", "name", "email", "status")
[ { "id": 891, "name": "User", "email": "qdpim#immobisp.com", "status": "1" } ]
and the failure response only contains the keys ("message", "status")
[ { "message": "Login Failed..", "status": "0" } ]
If you want to use the same struct for both JSON responses, you should make the properties optional
struct login : Codable {
var id: Int?
var name: String?
var email: String?
var status: String?
var message: String?
}
Also, since your keys are the same as your properties, you don't need enum CodingKeys or init for that matter if you use JSONDecoder().decode
You've already got an answer (or three) for this, but I want to show you how to do it without using JSONSerialization or speculative decoding.
So we have some LoginSuccess and LoginFailure types that you want to decode:
struct LoginSuccess: Decodable {
var id: Int
var name: String
var email: String
}
struct LoginFailure: Decodable {
var message: String
}
And we want to discriminate between them based on a status that is in the same container as the fields of those types. So we create an enum:
enum LoginResult: Decodable {
case success(LoginSuccess)
case failure(LoginFailure)
enum Keys: CodingKey {
case status
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
if try container.decode(String.self, forKey: .status) == "1" {
self = .success(try LoginSuccess(from: decoder))
} else {
self = .failure(try LoginFailure(from: decoder))
}
}
}
Note that the enum's init does not call decoder.decode(LoginSuccess.self). It passes the decoder it was given to the LoginSuccess initializer. Same with LoginFailure. This means those initializers will extract values from the same container as the status field.
Test:
let successData = #"[ { "id": 891, "name": "User", "email": "qdpim#immobisp.com", "status": "1" } ]"#.data(using: .utf8)!
print(try JSONDecoder().decode([LoginResult].self, from: successData))
// Output:
[__lldb_expr_1.LoginResult.success(__lldb_expr_1.LoginSuccess(id: 891, name: "User", email: "qdpim#immobisp.com"))]
let failureData = #"[ { "message": "Login Failed..", "status": "0" } ]"#.data(using: .utf8)!
print(try JSONDecoder().decode([LoginResult].self, from: failureData))
// Output:
[__lldb_expr_1.LoginResult.failure(__lldb_expr_1.LoginFailure(message: "Login Failed.."))]
Note that because your example data is wrapped in [...], I decoded arrays of LoginResult.

Swift: Unable to parse JSON response to model

I am getting data response from server like below:
{
"success": true,
"data": {
"cash": 0,
"newCash": 0,
"cashExpireAt": "2019-02-26T16:01:35.6451887+08:00",
"isEnabled": false,
"low_balance": true
}
}
//Below is Model Class
class WalletBalance: Mappable {
var isSuccess : Bool! = false
var data : WalletData?
required init?(map: Map) {
//Code here
}
func mapping(map: Map) {
isSuccess <- map["success"]
data <- map["data"]
}
}
class WalletData: Mappable {
var cash : Int! = 0
var newCash : Int! = 0
var cashExpireAt : String! = ""
var isEnabled : Bool! = false
var low_balance : Bool! = false
required init?(map: Map) {
//Code here
}
func mapping(map: Map) {
cash <- map["cash"]
newCash <- map["newCash"]
cashExpireAt <- map["cashExpireAt"]
isEnabled <- map["isEnabled"]
low_balance <- map["low_balance"]
}
}
//Below is code to call server api
Alamofire.request(url!, method: .get, parameters: nil, encoding: URLEncoding.default, headers: walletHeaders)
.validate(statusCode: 200..<600)
.responseObject { (response: DataResponse<WalletBalance>) in
switch response.result {
case .success:
if response.result.value == nil {
//Code here
} else {
completion(response.result.value!)
}
break
case .failure(let error):
print("error: ", error.localizedDescription)
break
}
}
I am getting error:
error: The operation couldn’t be completed. ObjectMapper failed to
serialize response.
I tried to call api using postman but its working fine in Postman.
Have you tried using Decodable instead of Mappable as suggested by the Alamofire documentation?
Use Decodable. It's much easier, more efficient, built-in and it generates much less code. The date can be decoded directly as Date
let jsonString = """
{
"success": true,
"data": {
"cash": 0,
"newCash": 0,
"cashExpireAt": "2019-02-26T16:01:35.6451887+08:00",
"isEnabled": false,
"low_balance": true
}
}
"""
struct Balance : Decodable {
let success : Bool
let data : Wallet
}
struct Wallet: Decodable {
let cash, newCash : Int
let cashExpireAt : Date
let isEnabled, lowBalance : Bool
}
let data = Data(jsonString.utf8)
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let result = try decoder.decode(Balance.self, from: data)
print(result)
} catch { print(error) }