How to search a JSON API and decode an array using AlamoFire - swift

I am searching a JSON API and I need to decode an array inside of it. I am using AlamoFire. Example: When I type in the letter "F" I want only the "F" word to display. I would like to search only in the array "name". Any help on this would be greatly appreciated!
This is my code below:
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
let url = "http://dummy.com/url"
Alamofire.request(url).responseData { (dataResponse) in
if let err = dataResponse.error {
print("Failed to contact", err)
return
}
guard let data = dataResponse.data else { return }
let dummyString = String(data: data, encoding: .utf8)
print(dummyString ?? "")
do {
let searchResult = try JSONDecoder().decode(SearchResults.self, from: data)
print("Result Count:", searchResult.id)
} catch let decodeError {
print("Failed to decode:", decodeError)
}
}
}
struct SearchResults: Decodable {
let id: String
}
My API array looks like this:
{
resultId: "1",
name: "Food",
},
{
resultId: "2",
name: "Movies",
}

Your result is in array format so you need to put your model like this:
let searchResult = try JSONDecoder().decode([SearchResults].self, from: data)
And for search, you can apply filters:
let filteredArray = searchResult.filter( { $0.name.contains("f") } )

As you said, your response is:
[
{ resultId: "1", name: "Food" },
{ resultId: "2", name: "Movies"}
]
So, your struct would be,
struct SearchResults: Decodable {
let resultId: String
let name: String
}
And you will use it as:
do {
let resultArray = try JSONDecoder().decode([SearchResults].self, from: data)
print("Result Count:", searchResult.id)
} catch let decodeError {
print("Failed to decode:", decodeError)
}
For searching, you should use filter, as:
let searchString = "F"
let filteredArray = resultArray.filter( { String($0.name.first!) == searchString } )
For matching first character = String($0.name.first!) == searchText
Check if name has search Text = $0.name.contains(searchText)
In total:
do {
let searchResult = try JSONDecoder().decode([SearchResults].self, from: data)
let filteredArray = resultArray.filter( { $0.name.contains(searchText) } )
} catch let decodeError {
print("Failed to decode:", decodeError)
}

Related

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.

iOS - Converting String to Double

So i am fetching from fire base config data and trying to manipulate it as so.
This is my struct :
struct FireBaseConfiguration: Codable {
var updateTime: String = ""
var iOSMinVersionForceUpdate: String = ""
var iOSMinVersionMessageUpdate: String = ""
var iOSMinFirmwareVersion: String = ""
var privacyPolicyUrlFireBase: String = ""
var termOfUseUrlFireBase: String = ""
}
This is my parser method:
func fireBaseConfigVersionMapParser(dataString: String, version: String) -> FireBaseConfiguration? {
var fireBaseConfiguration: FireBaseConfiguration?
let data = dataString.data(using: .utf8)!
do {
if let jsonArray = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? NSDictionary {
let model = jsonArray.object(forKey: version)
let data = try JSONSerialization.data(withJSONObject: model!, options: .prettyPrinted)
do {
let parsedModel = try JSONDecoder().decode(FireBaseConfiguration.self, from: data)
print("parsedModel is: \(parsedModel)")
fireBaseConfiguration = parsedModel
} catch let error{
print(error)
}
} else {
print("bad json")
}
} catch let error{
print(error)
}
return fireBaseConfiguration
}
And this is the implementation in the vc:
self.remoteConfig?.fetch(withExpirationDuration: 0, completionHandler: { [unowned self] (status, error) in
if status == .success {
self.remoteConfig?.activateFetched()
guard let configVersionMap = self.remoteConfig?.configValue(forKey: "iOSConfigVersionMap").stringValue else { return }
if let configModel = self.parser.fireBaseConfigVersionMapParser(dataString: configVersionMap, version: self.getCurrentAppVersion()!) {
print(configModel)
print(configModel.iOSMinVersionForceUpdate)
print(configModel.iOSMinVersionMessageUpdate)
var doubleForceUpdate = Double(configModel.iOSMinVersionForceUpdate)
var doubleMessageUpdate = Double(configModel.iOSMinVersionMessageUpdate)
print(doubleForceUpdate)
print(doubleMessageUpdate)
}
} else {
print("Config not fetched")
print("Error: \(error?.localizedDescription ?? "No error available.")")
}
})
so the prints are so:
FireBaseConfiguration(updateTime: "13/7/2018", iOSMinVersionForceUpdate: "1.0.2", iOSMinVersionMessageUpdate: "1.0.2", iOSMinFirmwareVersion: "1.0.1", privacyPolicyUrlFireBase: "https://www.name.com/corporate/privacy-policy", termOfUseUrlFireBase: "https://www.name.com/corporate/terms-of-use")
1.0.2
1.0.2
nil
nil
any ideas?
It is a simple String, but it's not actually a valid Double (Double values do not have two decimal places). So this is why it is failing.
What I think you actually need is the ability to compare version numbers, there are a number of other answers on the site that can show you how to do this:
Compare two version strings in Swift
Compare version numbers in Objective-C
So you can just keep your version numbers as a String

Swift - How to init an array in struct?

I'm not sure how to init the array in Struct. I'm not able to fetch data from array, meanwhile I was manage to get the result from object (platform.status).
Am I init it wrongly ?
Any ideas ?
Here is Network Request :
func fetchServicePlatform(token: String, _key: String) {
let selectedUrl = URL(string: "\(mainUrl)/get_service")
let parameters: [String: String] = ["_key": _key]
var serviceList = [ServiceList]()
URLSession.shared.dataTask(with: setupURLRequest(selectedURL: selectedUrl!, parameters: parameters, token: token, email: "test#gmail.com")) { (data, response, error) in
if let err = error {
print("Failed to fetch API: \(err.localizedDescription)")
}
guard let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else { return }
let platform = Platform(json: json)
if platform.status == "success" {
self.serviceList = platform.service_list
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
} catch let jsonErr {
print("Failed to fetch service platform: ", jsonErr.localizedDescription)
}
}.resume()
}
Here is JSON :
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
Here is Struct :
struct Platform {
let service_list: [ServiceList]
let status: String
init(json: [String: Any]) {
service_list = [ServiceList(json: json["service_list"] as? [String: Any] ?? [:])]
status = json["status"] as? String ?? ""
}
}
struct ServiceList {
let title: String
let description: String
let image: String
init(json: [String: Any]) {
title = json["title"] as? String ?? ""
description = json["description"] as? String ?? ""
image = json["image"] as? String ?? ""
}
}
In your data json["service_list"] is an array of dictionaries,
You Can try out.
struct Platform {
var service_list: [ServiceList] = []
var status: String
init(json: [String: Any]) {
if let jsonArray = json["service_list"] as? [[String: Any]] {
for service in jsonArray {
service_list.append(ServiceList(json: service))
}
}
else{
service_list = []
}
status = json["status"] as? String ?? ""
}
}
init an array in the struct
struct MyData {
var dataArray:[Any] = []
var latitude: Float
var longitude: Float
}
You have to unwrap the dictionary as an array of dictionaries and then loop through it with a map or flatmap, where you use the value $0 as the value
guard let serviceList = (json["service_list"] as? [[String: Any]])?.flatmap({ServiceList(withDictionary: $0)})
Go with the below approach, this way it is simpler
Create your structure with Codable
struct Platform: Codable {
let service_list: [ServiceList]
let status: String
enum CodingKeys: String, CodingKey {
case service_list, status
}
}
struct ServiceList: Codable {
let title: String
let description: String
let image: String
enum CodingKeys: String, CodingKey {
case title, description, image
}
}
Your json data object
let json = """
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
""".data(using: .utf8)!
Use the JSONDecoder to map the json object to structure
let decoder = JSONDecoder()
let platform = try! decoder.decode(Platform.self, from: json)
if platform.status == "pass"{
for service in platform.service_list{
print(service.title)
}
}

Decode an array inside JSON data only using Swift 4 Codable

I'm using Swift 4 Codable and I'm receiving this JSON from my web service:
{
"status": "success",
"data": {
"time": "00:02:00",
"employees": [
{
"id": 001,
"name": "foo"
},
{
"id": 002,
"name": "bar"
}
]
}
}
I want to decode only employees array into employee objects (the time property will only be saved once), but nothing works.
I read a lot of materials about Swift 4 Codable but don't get how can I decode this array.
EDIT: My employee class:
import Foundation
struct Employee: Codable {
var id: Int
var time: Date
enum CodingKeys: String, CodingKey {
case id = "id"
case time = "time"
}
}
The request:
Alamofire.SessionManager.default.request(Router.syncUsers)
.validate(contentType: ["application/json"])
.responseJSON { response in
if response.response?.statusCode == 200 {
guard let jsonDict = response as? Dictionary<String, Any>,
let feedPage = Employee(from: jsonDict as! Decoder) else {
return
}
guard let response = response.result.value as? [String: Any] else {
return
}
guard let data = response["data"] as? [String: Any] else {
return
}
guard let users = data["employees"] else {
return
}
guard let usersData = try? JSONSerialization.data(withJSONObject: users, options: .prettyPrinted) else {
return
}
guard let decoded = try? JSONSerialization.jsonObject(with: usersData, options: []) else {
return
}
let decoder = JSONDecoder()
guard let employee = try? decoder.decode(Employee.self, from: usersData) else {
print("errorMessage")
return
}
} else {
print("errorMessage")
}
}
When using Codable you cannot decode inner data without decoding the outer.
But it's pretty simple, for example you can omit all CodingKeys.
struct Root : Decodable {
let status : String
let data : EmployeeData
}
struct EmployeeData : Decodable {
let time : String
let employees : [Employee]
}
struct Employee: Decodable {
let id: Int
let name: String
}
let jsonString = """
{
"status": "success",
"data": {
"time": "00:02:00",
"employees": [
{"id": 1, "name": "foo"},
{"id": 2, "name": "bar"}
]
}
}
"""
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
for employee in result.data.employees {
print(employee.name, employee.id)
}
} catch { print(error) }
You can solve this kind of JSON parsing problem very easily with quicktype. Just paste in your JSON on the left and you'll get types and serialization/deserialization code on the right, for a variety of languages. Here are the types it produces for your JSON:
struct Employees: Codable {
let status: String
let data: EmployeesData
}
struct EmployeesData: Codable {
let time: String
let employees: [Employee]
}
struct Employee: Codable {
let id: Int
let name: String
}

How to parse a stringified array in iOS Swift

How to parse any stringified array such as "[\"Bob\", \"Tim\", \"Tina\"]" in Swift? It should return a JSON array such as ["Bob", "Tim", "Tina"].
Sorry if this is a duplicate question, but I could not find any answer for a generic stringified array where the structure of the array elements are not known.
Try doing it like this, Works for me every time:
let jsonText = "[\"Bob\", \"Tim\", \"Tina\"]"
var array: [String]?
if let data = jsonText.data(using: String.Encoding.utf8) {
do {
array = try JSONSerialization.jsonObject(with: data, options: []) as? [String]
if let myArray = array {
print(myArray)
}
} catch let error as NSError {
print(error)
}
}
It prints : ["Bob", "Tim", "Tina"]
Hope it helps!!
extension String
{
func decodeUrl() -> String
{
return self.removingPercentEncoding!
}
}
extension Data
{
func dataToJSON() -> Any? {
do {
return try JSONSerialization.jsonObject(with: self, options: [])
} catch let myJSONError {
print(myJSONError)
}
return nil
}
}
Usage :
if let data = your_stringified_array.decodeUrl().data(using: String.Encoding.utf8) {
if let jsonc = data.dataToJSON() {
print(jsonc)
}
}
Result is in AnyObject.