I am trying to implement two side by side UIPickerViews, one for Type and the other for Subtype. Each is an array of strings
var typePickerData: [String] = [String]()
var subtypePickerData: [String] = [String]()
Each is a pretty simple array of names:
typePickerData = ["Monitor","Keyboard","Mouse"]
When the viewDidLoad fires or when a new Type is selected, the app makes a remote db call and I get a response containing JSON of subtype names which I want to use to populate and reload the subtype picker.
I am stuck on converting the response into subtypePickerData
let decoder = JSONDecoder()
if let jsonResponse = try? decoder.decode([String].self, from: data) {
print("parse subtype response \(jsonResponse)")
subtypePickerData = jsonResponse
DispatchQueue.main.async {
self.subtypePicker.reloadAllComponents()
}
}
What am I doing wrong here converting the JSON response to subtypePickerData?
this is what I am getting from the remote call
result Optional(<__NSArrayM 0x281370e70>( { name = Monitor; },{ name = "AV Accessories"; },{ name = Computer; },{ name = "Monitor Stands"; },{ name = "Bracket/Mount"; },{ name = Screen; }
Here is my updated code after solving issue
let decoder = JSONDecoder()
if let jsonResponse = try? decoder.decode(Subtypes.self, from: data) {
SubtypeList = jsonResponse.result
self.subtypePickerData = SubtypeList.map{$0.Name}
DispatchQueue.main.async {
self.subtypePicker.reloadAllComponents()
}
}
Yor response seems to be not type of [String] but an array of custom objects. You first need to create a struct to decode your response data to.
struct NameContainer{
var name: String
}
then do:
//change decoding to String array to decoding array of custom object NameContainer
if let jsonResponse = try? decoder.decode([NameContainer].self, from: data) {
print("parse subtype response \(jsonResponse)")
subtypePickerData = jsonResponse.map{$0.name} // Map your objects to strings and assign them
DispatchQueue.main.async {
self.subtypePicker.reloadAllComponents()
}
}
Remarks:
Never use try? this will obfuscate all errors. Use a proper do/catch block, handle the error or mark your function throws and handle the error up the chain.
DispatchQueue.main.async {
self.subtypePicker.reloadAllComponents()
Bracket/Mount"; },{ name = Screen; } –
Related
I'm new in IOS programming.
I have a json array described with code below.
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as?
NSDictionary
print("json: \(String(describing: json))")
Output of code is;
json: Optional({
vendors = (
{
firm = "XXX firm";
id = 1;
"show_firm" = 1;
},
{
firm = "ZZZZZ firm";
id = 2;
"show_firm" = 1;
}
);
})
I want to add only firm values to another array like firms = ["XXX firm" , "ZZZZZ firm"]
How can I do that?
Any help would be greatly appreciated.
#witek bobrowski asked String(data: data!, encoding: .utf8) output.This output is below also. By the way json data comes from server as http post response.
json2: Optional("{\"vendors\":[{\"id\":\"1\",\"firm\":\"XXX firm\",\"show_firm\":\"1\"},{\"id\":\"2\",\"firm\":\"ZZZZZ firm\",\"show_firm\":\"1\"}]}")
I believe the best way to go is to decode the JSON and then add the firms value to an array.
struct model: Decodable{
var vendors: [decodingModel]
}
struct decodingModel: Decodable{
var firm: String
var id: Int
var show_firm: Int
}
let decoder = JSONDecoder()
do{
let result = try decoder.decode(model.self, from: jsonData)
let firmsArray = result.vendors.compactMap({$0.firm})
}catch{
print(error)
}
Since you have not posted your son structure, I can only assume you have a Json where vendor is an array of jsons. firmsArray is what you are looking for.
If this doesn't work is probably because of the wrong model and decodingModel. If you post your json structure, I will update the code so that you can properly decode your json
the best way is to create Decodable Model for your json as below:
struct Firm: Decodable {
let id: Int
let name: String
let showFirm: Int
enum CodingKeys: String, CodingKey {
case id
case name = "firm"
case showFirm = "show_firm"
}
}
I created this factory method to simulate your json response locally based on what you provided in the question
struct FirmFactory {
static func makeFirms() -> [Firm]? {
let json = [
[
"firm": "XXX firm",
"id": 1,
"show_firm": 1,
],
[
"firm": "ZZZZZ firm",
"id": 2,
"show_firm": 1,
],
]
// you should use the following code to decode and parse your real json response
do {
let data = try JSONSerialization.data(
withJSONObject: json,
options: .prettyPrinted
)
return try JSONDecoder().decode([Firm].self, from: data)
} catch {
print("error \(error.localizedDescription)")
return nil
}
}
}
now you will be able to map only the firm names as you request you can test like this
let firmNames = FirmFactory.makeFirms()?.map { $0.name }
print("firmNames \(firmNames)")
I answered my own question again. There are 2 answers given but i didn't use any of these in my code. May be these two answers are usable but because of i'm new in IOS i couldn't use any of them. At the end of long google search i solved my problem as below.
let vendors = json!["vendors"]! as! [[String : AnyObject]]
for firm in vendors {
let firm1 = firm["firm"]! as! String
self.providerArray.append(firm1)
}
I hope this answer solves someone else's problem like me.
I try to convert a JSON to an Array but I face some issue and do not know how to sort it out.
I use Swift 5 and Xcode 12.2.
Here is the JSON from my PHP query:
[
{
Crewcode = AAA;
Phone = 5553216789;
Firstname = Philip;
Lastname = MILLER;
email = "pmiller#xxx.com";
},
{
Crewcode = BBB;
Phone = 5557861243;
Firstname = Andrew;
Lastname = DEAN;
email = "adean#xxx.com";
}
]
And here is my Swift code :
let url: URL = URL(string: "https://xxx.php")!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}
else {
print("Data downloaded")
do {
if let jsondata = (try? JSONSerialization.jsonObject(with: data!, options: [])) {
print(jsondata)
struct Crew: Decodable {
var Code: String
var Phone: String
var Firstname: String
var Lastname: String
var email: String
}
let decoder = JSONDecoder()
do {
let people = try decoder.decode([Crew].self, from: jsondata as! Data)
print(people)
}
catch {
print(error)
}
}
}
}
}
task.resume()
When I run my code I get the following error:
Could not cast value of type '__NSArrayI' (0x7fff86b930b0) to 'NSData' (0x7fff86b911e8).
2020-12-09 14:52:48.988468+0100 FTL[57659:3019805] Could not cast value of type '__NSArrayI' (0x7fff86b930b0) to 'NSData' (0x7fff86b911e8).
Could not cast value of type '__NSArrayI' (0x7fff86b930b0) to 'NSData' (0x7fff86b911e8).
Should you have any idea to get it right, I thank you in advance for your assistance on this !
You are deserializing the JSON twice by mixing up JSONSerialization and JSONDecoder.
Delete the first one
if let jsondata = (try? JSONSerialization.jsonObject(with: data!, options: [])) {
– By the way the JSON in the question is neither fish nor fowl, neither JSON nor an NS.. collection type dump –
and replace
let people = try decoder.decode([Crew].self, from: jsondata as! Data)
with
let people = try decoder.decode([Crew].self, from: data!)
and the struct member names must match the keys otherwise you have to add CodingKeys
I am trying to parse the JSON object received from Web service which gives the result as JSON object of status and data.data again is a array of objects, from this object I want to take one element on the basis of which I have to fill a tableview.
web Service results comes as
{"status":1,"data":[{"service_id":"1","service_name":"Painter"},{"service_id":"2","service_name":"Plumber"},{"service_id":"3","service_name":"Electrician"},{"service_id":"4","service_name":"Handyman"},{"service_id":"5","service_name":"Carpenter"},{"service_id":"6","service_name":"Mason"}]}
parsing in swift I did as:--
created one class
class ABC: NSObject {
var service_name:String?
var service_id : Int?
init(service_name:String,service_id:Int) {
self.service_name = service_name
self.service_id = service_id
}
let myUrl = URL(string: "services.php");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json
{
let status=parseJSON["status"] as? Int
let newdata : NSDictionary = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
self.model=(newdata.value(forKey: "data") as? [ABC])!
My problem is I am getting an array of objects in self.model as service_name and service_id keys.Now I want to take out one array of strings that contains all the service_name object values.Its saying not able to convert NSArray to swift array.
As already mentioned by others use (De)codable. It's easy to use and very comfortable. No type cast and no literal keys.
Create two structs, declare the members as non-optional constants with camelCased names and omit the initializer.
struct Root : Decodable {
let status : Int
let data : [Service]
}
struct Service : Decodable {
let serviceName : String
let serviceId : String
}
Then decode the JSON in another class or struct
let myUrl = URL(string: "services.php")
var request = URLRequest(url: myUrl!)
request.httpMethod = "POST"// Compose a query string
let task = URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let json = try decoder.decode(Root.self, from: data!)
let status = json.status
let newdata = json.data
} catch { print(error))
}
task.resume()
I would recommend to drop JSONSerialization and use Codable protocol instead with CodingKeys.
Here is a simple example to see how it works.
struct Service : Codable {
let id : Int
let name : String
// keys
private enum CodingKeys: String, CodingKey {
case id = "service_id"
case name = "service_name"
}
}
...
// assuming our data comes from server side
let jsonString = "..."
let jsonData = jsonString.data(using: .utf8)!
do {
let jsonDecoder = JSONDecoder()
let user = try jsonDecoder.decode(Service.self, from: jsonData)
print("Getting service: \(service.id) \(service.name)")
} catch {
print("Unexpected error: \(error).")
}
More documentation here.
Use native Swift type Dictionary everywhere you use NSDictionary now
Then get certain value for key by specifing key in dictionary's subscript
if let model = newData["data"] as? [ABC] {
self.model = model
}
Anyway, I would suggest you to start using Codable instead of JSONSerialization
struct Response: Decodable {
let status: Int
let data: [ABC]
}
struct ABC: Decodable {
let serviceName: String
let serviceId: String // note that `serviceId` isn’t `Int` But `String`
}
and then decode your data using JSONDecoder
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let response = try decoder.decode(Response.self, from: data!)
self.model = response.data
} catch { print(error) }
I am attempting to use Swift 3 with (ObjectMapper) to map a players array from a given JSON response on a cut-down class object called "Player"; but am finding it difficult to map it.
// Sample JSON
{
"_meta": {
...
},
"fixtures": [{
...
}],
"players": [{
"name": "Smith",
"id": "15475-9524",
}]
}
However, I am finding it hard to get it to understand how to make it map properly as it always complains that it needs more context.
I am wanting my JSON consumer to get me the list of players then map all the players using Object Mapper into an array.
When I use
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: res)
It complains
Type of expression is ambiguous without more context
My class is as follows
class Player: NSObject, Mappable {
var name: String?
required init?(map: Map) {
super.init()
}
// Mappable
func mapping(map: Map) {
name <- map["name"]
}
}
I am using AlamoFire to consume the JSON.
Alamofire.request(url).responseJSON(completionHandler: {
response in
switch response.result {
case .success(let JSON):
guard let res = JSON as? [String:Any] else {
print ("Can't do this")
return
}
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: res)
print (players)
break
case .failure(let error):
print("** Request failed with error: \(error) **")
break
}
I don't quite understand how to use the ObjectMapper on the array I'm wanting to fetch.
Any assistance on this would be good.
I think you are confusing JSON Dictionary with Player's array.
Try this:
guard let res = JSON as? [String:Any] else {
print ("res:Can't do this")
return
}
guard let json_players = res["players"] as? [[String:Any]] else {
print ("json_players:Can't do this")
return
}
var players : Array<Player> = Mapper<Player>().mapArray(JSONArray: json_players)
I have a problem when trying to update an array of struct with the JSON's data. Here's my code:
This is how the struct is defined:
Struct
struct Shot {
var title: String
var desc: String
var img: String
init(title: String, desc: String, img: String) {
self.title = title
self.desc = desc
self.img = img
}
}
Here I try to update the array, but it doesn't work:
Code
func authDribbble() {
let endURL = "https://api.dribbble.com/v1/shots/"
let token = "***"
Alamofire.request(.GET, endURL, parameters: ["access_token" : token])
.responseJSON { response in
if let JSON = response.result.value {
for i in 0..<JSON.count {
let titleA: String = String(JSON[i]["title"])
let descA: String = String(JSON[i]["description"])
let imgA: String = String(JSON[i]["images"])
self.data += [Shot(title: titleA, desc: descA, img: imgA)]
}
}
}
}
The problem is that after the function is performed, the array stays void. What's wrong?
Thank you.
Working under the assumption that you've called this method as follows:
authDribbble()
print("my data: \(self.data)") // Prints empty array.
The issue is that Alamofire is performing the request asynchronously. That is, the work isn't done yet when your code proceeds to the next line (the print statement). You need to implement a callback mechanism. One way to do this is to pass a function as a parameter of your authDribbble() function. That would change the function to something like:
func authDribbble(completion: () -> Void) {
// create the url
// create parameters
Alamofire.request(.GET, url!, parameters: params)
.responseJSON { response in
guard let JSON = response.result.value as? [[String : AnyObject]] else {
// Wrong type in JSON response.
print(response.result.error)
}
self.data = JSON.map { dict in
guard let title = dict["title"] as? String,
desc = dict["description"] as? String,
img = dict["images"] as? String else {
// Handle error creating Shot from JSON
}
return Shot(title: title, desc: desc, img: img)
}
completion()
}
}
}
You would then replace the call to authDribbble() with:
authDribbble() {
print("data: \(self.data)") // Prints populated array.
}