Essentially I would like to parse the JSON received from a url and display the data inside of alert popup.
I am able to do this with a simple array below but don't know how this can be done with an JSON array from a URL.
Currently working:
let jsonObj:[String: Any] = ["error": [
"email": ["The email has already been taken."],
"phone": ["The phone has already been taken."]]
]
if let errorMsgs = jsonObj["error"] as? [String: [String]] {
let errMsg = errorMsgs.values.map { $0.reduce("", +) }.joined(separator: "\n")
print(errMsg)
self.presentAlert(withTitle: "Try again", message: errMsg)
}
JSON Array:
var structure = [Struct]()
private func fetchJSON() {
guard let url = URL(string: "url.com")
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "code=\(codeValue)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
self.structure = try JSONDecoder().decode([Struct].self,from:data)
}
catch {
print(error)
}
}.resume()
}
struct Struct: Decodable {
let id: Int
let reasons: String
}
Sample JSON Array:
[
{
"id": 1,
"reasons": "Test"
},
{
"id": 3,
"reasons": "Test"
}
]
How can I get the reasons string from the array to populate in the alert similar to above.
UPDATE:
To Print the records in the alert I have done the following but it only prints one record in the alert:
for item in self.structure {
print(item.reasons)
let errMsg = [item.reasons].compactMap { $0 }.joined(separator: "\n")
self.presentAlert(withTitle: "Try again", message: errMsg)
}
Here I have created example from your code.
First of all replace your Codable with:
// MARK: - Reason
struct Reason: Codable {
let id: Int
let reasons: String
}
typealias Reasons = [Reason]
Here is your server response in JSON String formate:
let json = """
[
{
"id": 1,
"reasons": "Test"
},
{
"id": 3,
"reasons": "Test"
}
]
"""
Then I have converted it to Data only for testing purpose:
let data = json.data(using: .utf8)!
And you can parse it with
do {
self.reasonsData = try JSONDecoder().decode(Reasons.self, from:data)
for item in self.reasonsData {
print(item.reasons) //This will print reasons.
}
}
catch {
print(error)
}
UPDATE:
If you want all reasons in one string then replace
for item in self.reasonsData {
print(item.reasons)
}
with
let errMsg = self.reasonsData.compactMap { $0.reasons }.joined(separator: "\n")
Related
Here is my structure for the json file
struct DataRespons: Codable {
let data: [String]
let status: String
}
JSON file url
{
"status": "success",
"data": [
"f7c75c1f-10ab-4298-9dc9-e80b7bd07dfd",
"6f5f6eeb-191d-4ad9-b5ef-6f61fd5fcefc",
"8008800880088008",
"64a3f5d0-37c7-4c30-8d0f-3b67fb5c8fde"
]
}
This is my class for JSON requests and decoding
I use these functions to get an array of links
I hope I wrote correctly what I want to receive, and if there are any questions, please contact me
#MainActor
class NetworkModel: ObservableObject {
#Published var listId: [String] = []
var statusList = ""
var statusUser = ""
#Published var userData = UserRespons(status: "??", data: UserData(id: "???", firstName: "???", lastName: "??", age: 4, gender: "???", country: "???"))
func getList() {
guard let url = URL(string: "some URL") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.addValue("bearer \(token)", forHTTPHeaderField: "Authorization")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Requst error",error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async {
do {
let decoded = try JSONDecoder().decode(DataRespons.self, from: data)
self.listId = decoded.data
self.statusList = decoded.status
} catch let error{
print("Error decode",error)
}
}
}
}
dataTask.resume()
}
I can't through index [String] to get each element
use this code to access the data elements of an array:
// ....
self.listId = decoded.data
for i in listId.indices {
print("--> listId[\(i)] = \(listId[i]) ")
}
print("--> listId[0] = \(listId[0]) ")
print("--> listId[1] = \(listId[1]) ")
// ....
Read the very basics of Swift, specially regarding arrays here:
https://docs.swift.org/swift-book/LanguageGuide/CollectionTypes.html
Essentially I have the following function:
func stagedCount() {
let url = "example.com"
let parameters: Parameters =
["person": "\(name)"]
Alamofire.request(url, method: .post, parameters: parameters).responseData(completionHandler : { response in
if let allObjects = response.result.value as? NSArray{
print("Array length is \(allObjects.count)")
}
}
)}
The JSON response from the URL looks like this:
[
{
"person": "Jake",
"hobby": "soccer"
},
{
"person": "Mary",
"hobby": "surfing"
}
]
I am getting the following response and no count is currently printing.
Cast from 'Data?' to unrelated type 'NSArray' always fails
How can I fix this to get a count of the amount of records in the array? The result I am looking for is 2
The error is pretty clear. responseData will return a JSON String data. You can not convert Data to an Array. What you need is to decode your json data into an array of person structures:
struct Person {
let person: String
let hobby: String
}
when decoding the response:
guard
let data = response.data,
let json = String(data: data, encoding: .utf8)
else { return }
print("json:", json)
do {
let people = try JSONDecoder().decode([Person].self, from: data)
print(people.count)
for person in people {
print(person)
}
} catch {
print(error)
}
I am trying to create a basic to-do application for command-line in Swift. Below is my function to add a new item to the to-do array, but the new entry keeps overwriting the old one, instead of creating a new one. In the end, there is only one entry in the todo.json file.
When I multiply the entries and .append statements manually it works, but probably my brain is too dead to figure it out at the moment.
struct TodoItem: Codable {
let name: String
}
var todoList = [TodoItem]()
func addToList(_ item: String) -> String {
let todoItem = TodoItem(name: item)
todoList.append(todoItem)
do {
let fileURL = try FileManager.default
.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent("example")
.appendingPathExtension("json")
let encoder = JSONEncoder()
try encoder.encode(todoList).write(to: fileURL)
} catch {
return "Error: \(error.localizedDescription)"
}
return "Item added: \(todoItem.name)"
}
Your code works fine. I think the problem rests in the fact that todoList is empty when you run this. But you can write code to retrieve the contents of the JSON:
var todoList: [TodoItem] = []
func retrieveToDoList() {
guard let data = try? Data(contentsOf: fileURL) else { return }
todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}
So, for example, consider:
retrieveToDoList()
addToList("foo")
addToList("bar")
addToList("baz")
print(todoList)
// if you want to check the json
let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)
That results in:
[MyApp.TodoItem(name: "foo"), MyApp.TodoItem(name: "bar"), MyApp.TodoItem(name: "baz")]
[
{
"name" : "foo"
},
{
"name" : "bar"
},
{
"name" : "baz"
}
]
And if I later do:
addToList("abc")
addToList("def")
addToList("hij")
I then get:
[
{
"name" : "foo"
},
{
"name" : "bar"
},
{
"name" : "baz"
},
{
"name" : "abc"
},
{
"name" : "def"
},
{
"name" : "hij"
}
]
So, every time the app starts up, just make sure to call retrieveToDoList before trying to append items or else the toDoList will be empty.
FYI, this is the code I used to generate the above. Hopefully it illustrates the idea.
struct TodoItem: Codable {
let name: String
}
class ViewController: UIViewController {
private var todoList: [TodoItem] = []
private let fileURL = try! FileManager.default
.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true)
.appendingPathComponent("example.json")
override func viewDidLoad() {
super.viewDidLoad()
retrieveToDoList()
addToList("foo")
addToList("bar")
addToList("baz")
print(todoList)
// if you want to check the json
let data = try! Data(contentsOf: fileURL)
let json = String(data: data, encoding: .utf8)!
print(json)
}
}
private extension ViewController {
func retrieveToDoList() {
guard let data = try? Data(contentsOf: fileURL) else { return }
todoList = (try? JSONDecoder().decode([TodoItem].self, from: data)) ?? []
}
#discardableResult
func addToList(_ item: String) -> String {
let todoItem = TodoItem(name: item)
todoList.append(todoItem)
do {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
try encoder.encode(todoList).write(to: fileURL)
} catch {
return "Error: \(error.localizedDescription)"
}
return "Item added: \(todoItem.name)"
}
}
I have API response with nested array . But I can't understand how can I remove whole Dict by filtering the value .
This is the response screenshot
https://imgur.com/XIDyfYX
here is the json Resonse :- https://del.dog/lofavofogo.json
I have tried this but I don't know how to get filter nested value and remove whole dict at specific index
How to remove pairs from dictionary at specific index - Swift?
I want to remove the dict where section name are "NA"
Here is the code :-
Model Class For API Response :-
class filterclass: NSObject {
var classesID : String?
var classname : String?
var section = [filterSections]()
init(json: [String: Any]) {
if let classname = json["class"] as? String {
self.classname = classname
}
if let classesID = json["classesID"] as? String {
self.classesID = classesID
}
print("classname",classname)
if let evUserGoing = json["classsection"] as? [[String: Any]] {
if self.section.count > 0
{
self.section.removeAll()
}
for evUser in evUserGoing {
// print("evUser",evUser)
let userGoing = filterSections(json: evUser)
self.section.append(userGoing)
}
for sec in section {
let section = sec.secctionname
let setionID = sec.sectionID
}
}
}
}
class filterSections: NSObject {
var sectionID : String?
var secctionname : String?
var isSelctedSection : Bool = false
init(json: [String: Any]) {
if let sectionID = json["sectionID"] as? String {
self.sectionID = sectionID
}
if let secctionname = json["section"] as? String {
self.secctionname = secctionname
}
print("sectioname",secctionname)
}
}
API POST Method TO hit API :-
func getClassSectionAPI() {
if ReusableClass.sharedInstance.isNetworkAvailable() == true
{
ReusableClass.sharedInstance.showActivityIndicator()
let UUid = LoginUserInfo.sharedInstance.uuid!
let dictionary = ["uuid":UUid,"device_id":devicetoken,"school_id":LoginUserInfo.sharedInstance.schoolId!, "user_type":LoginUserInfo.sharedInstance.usertype!]
print(dictionary)
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(dictionary) {
if let jsonString = String(data: jsonData, encoding: .utf8) {
// print(jsonString)
let cipher:String = CryptoHelper.encrypt(input:jsonString)!;
let NewEncryption = "data=\(cipher)"
print(NewEncryption)
let hmac_md5 = cipher.hmac(algorithm: .sha512, key: kHMACKey)
print("hmac",hmac_md5)
UserDefaults.standard.set(hmac_md5, forKey: Headerkey)
Singleton.sharedInstance.getWebservicesverify(params: NewEncryption, Methodname: KFilterClassSection, data: Stringnil)
{ (result) in
DispatchQueue.main.async {
ReusableClass.sharedInstance.hideActivityIndicator()
}
if result != nil
{
do {
let jsonData = try JSONSerialization.data(withJSONObject: result)
if let json = String(data: jsonData, encoding: .utf8) {
let Dict = function.convertToDictionary(text: json)! as NSDictionary
guard let data = Dict[KData] as? String
else
{
return
}
self.baseDict = data
}
}
catch {
}
guard let output = CryptoHelper.decrypt(input:self.baseDict)
else
{
return
}
print(output)
let mainDict = function.convertToDictionary(text: output)! as NSDictionary
let status = mainDict[KStatus] as! NSInteger
if(status == 1)
{
DispatchQueue.main.async {
print("Main dict",mainDict)
guard let messageArray = mainDict["data"] as? [[String: Any]] else{
return
}
if self.arrayClasSection.count > 0
{
self.arrayClasSection.removeAll()
}
print("Main dict",messageArray)
for arr in messageArray {
let obj = filterclass.init(json: arr)
if let index = self.arryFilterTemperary.index(where: { $0.classname == obj.classname }) {
// let filtered = self.arryFilterTemperary.filter { $0.classname == "NA" }
obj.section = self.arryFilterTemperary[index].section
self.arrayClasSection.append(obj)
for sec in self.arryFilterTemperary[index].section {
let section = sec.sectionID
let sectionName = sec.secctionname
self.NASection = sec.secctionname!
print(self.NASection)
self.selectedNASectionID = sec.sectionID!
// let test = self.arryFilterTemperary[index].section.filter { !$0.value.contains("") }
// print(test)
}
}
else
{
self.arrayClasSection.append(obj)
}
}
ReusableClass.sharedInstance.hideActivityIndicator()
self.tableFilter.reloadData()
}
}
I want to append the data to array but before appending I want to
filter that "NA" value dict from the array
Since this is your first question I go to some greater length in answering it than usual. Playgrounds are an exceptional way to demonstrate your problem, so you should always try to compose your questions in a form of one. I will post my answer directly from the Playground I have done.
With that out of the way lets get to the question. Your main problem seems to be that you tried an ill fated JSONSerialization "shortcutt" route. This looks cheap from the outside, but working with the unavoidable optionality of a [String:Any] comes at a high cost in a language like Swift. The way to go is the brilliant Codable protocol, at least in my opinion. Once you define your data structure properly Xcode has so much more possibilities to guide you through the APIs that writing your filter code becomes a piece of cake.
Enough of the ranting, let's get to the pizza.
import UIKit
let dataStr = """
{
"status":1,
"message":"Class and sections list",
"data":[
{
"classsection":[
{
"section":"A",
"sectionID":"1",
"classesID":"1"
},
{
"section":"B",
"sectionID":"3",
"classesID":"1"
}
],
"class":"First",
"classesID":"1"
},
{
"classsection":[
{
"section":"A",
"sectionID":"2",
"classesID":"2"
},
{
"section":"B",
"sectionID":"7",
"classesID":"2"
}
],
"class":"Second",
"classesID":"2"
},
{
"classsection":[
{
"section":"A",
"sectionID":"20",
"classesID":"15"
}
],
"class":"Third",
"classesID":"15"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"33",
"classesID":"22"
}
],
"class":"Pre Nursery",
"classesID":"22"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"34",
"classesID":"23"
},
{
"section":"A",
"sectionID":"35",
"classesID":"23"
},
{
"section":"B",
"sectionID":"36",
"classesID":"23"
},
{
"section":"C",
"sectionID":"37",
"classesID":"23"
}
],
"class":"Fourth four",
"classesID":"23"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"38",
"classesID":"24"
}
],
"class":"Fifth",
"classesID":"24"
},
{
"classsection":[
{
"section":"NA",
"sectionID":"39",
"classesID":"25"
}
],
"class":"sixth 6th",
"classesID":"25"
}
]
}
"""
struct Section: Codable {
let section, sectionId, classesId: String
enum CodingKeys: String, CodingKey {
case sectionId = "sectionID"
case classesId = "classesID"
case section
}
}
struct Class1: Codable {
let classsection: [Section]
let clazz, classesId: String
private enum CodingKeys: String, CodingKey {
case classsection
case clazz = "class"
case classesId = "classesID"
}
}
struct Response: Codable {
let status: Int
let message: String
let data: [Class1]
func filterSections(notMatching filterVal: String) -> Response {
let filteredData = data.map { (clazz) -> Class1 in
let filteredSections = clazz.classsection.filter { (sect) -> Bool in
sect.section != filterVal
}
return Class1(classsection: filteredSections, clazz: clazz.clazz, classesId: clazz.classesId)
}
return Response(status: status, message: message, data: filteredData)
}
}
let jsonData = dataStr.data(using:.utf8)!
do {
let res = try JSONDecoder().decode(Response.self, from: jsonData)
let filteredResponse = res.filterSections(notMatching: "NA")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
print(String(data:try encoder.encode(filteredResponse), encoding: .utf8)!)
} catch {
print(error)
}
As you can see your data structure is easily defined and the filtering code is really easy to understand once you wrap your head around the lambdas (which you should). The playground will output nicely formatted JSON as an answer, this way it is easy to check that your code does the right thing without all the messy parts of asynchronous communication (which are still nicely done in Swift).
Here's my last tipp of the day: Always try to isolate your problem as much as possible if you post a question on StackOverflow. I think your question carried too much legacy, you should whittle it down for the next one. This will improve your chances for a quick answer.
I'm am trying to return the Dictionary String values. It returns as:
categories: [yelp1.Zom2(categories: yelp1.Zom3(name: "Delivery")), yelp1.Zom2(categories: yelp1.Zom3(name: "Dine-out"))....]
How would I only return the name values such as "Delivery" and "Dine-Out"?
//yelp1 is just the name of the file
SAMPLE JSON DATA
{
"categories": [
{
"categories": {
"id": 1,
"name": "Delivery"
}
},
{
"categories": {
"id": 2,
"name": "Dine-out"
}
},
THESE ARE MY STRUCTS
struct Zom3:Codable{
let name:String
}
struct Zom2:Codable{
//let category_id:Int?
let categories: Zom3
}
struct Zom:Codable{
//let category_id:Int?
let categories: [Zom2]
}
I have tried to decode Zom3 because thats where the name value is located but it states key not found. It won't allow me to iterate through with a for loop to at least get each element of the dictionary individually.
override func viewDidLoad() {
super.viewDidLoad()
let urlName = "https://developers.zomato.com/api/v2.1/categories"
let url = URL(string: urlName)
var urlReq = URLRequest(url: url!)
urlReq.httpMethod = "GET"
urlReq.addValue("application/json", forHTTPHeaderField: "Accept")
urlReq.addValue(zomatoKey, forHTTPHeaderField: "user_key")
let task = URLSession.shared.dataTask(with: urlReq) { (data, response, error) in
guard let data = data else {return}
do {
let items = try JSONDecoder().decode( Zom.self, from: data)
print(items)
}
catch {
print(error)
}
}
task.resume()
}
From what you've written, it looks like your output is from your "print(items)" line. If that's true, then the object you've called "items" is the top level struct "Zom". To get the bottom level "name" I think you do need a for loop under "let items = try..."; (instead of "print(items)")
var arrayOfNames: [String] = []
let zoms = item.categories
for item in zoms {
let possibleName = item.categories?.name
if let name = possibleName {
arrayOfNames.append(name)
}
}
print(arrayOfNames)
If I haven't made any mistakes this should create an array of the names and print out "Delivery, Dine-Out"