I have data from server like
[
{
"id": 1,
"name": "16",
"children": "",
"products": [
{...},
{...}
]
},
{
"id": 2,
"name": "17",
"children": "",
"products": [
{...},
{...}
]
}
]
so I save it like [[String: Any]], and Any is because there can be Int, String or Dict at the values.
The point is that "children" key can be NSConstantstring and can be casted to String, and also it can be NSArray and can be casted to [[String: Any]] too. So I need to find a way to detect type of that value. But all I tried caused error.
How can i fix this?
UPD
not much code)
inside alamofire response:
let data = responseJSON.result.value! as! [String: Any]
let subCategory = data["children"] as! [[String: Any]]
//check
for item in subCategory {
print(type(of: item["children"]!))//__NSArrayI or __NSCFConstantString
}
if I try something like print(type(of: item["children"] as! String)) it prints String if there is __NSCFConstantString, but if not - it crashes with error Could not cast value of type '__NSArrayI' (0x10934fe48) to 'NSString' (0x1083e8568)
UPD 2
there is no problem with data, all parsed and save correctly and printing out correctly too
You can just cast value from dictionary to needed type:
if let string = dictionary["children"] as? String {
// Do something with string
} else if array = dictionary["children"] as? [Any] {
// Do something with array
}
Related
json response:
"result": {
"user_images": [
{
"id": 113,
"user_id": "160",
"image": "1617349564.jpg",
"image_title": "33"
},
{
"id": 112,
"user_id": "160",
"image": "1617349541.jpg",
"image_title": "22"
},
{
"id": 111,
"user_id": "160",
"image": "1617349528.jpg",
"image_title": "11"
},
........
code: with this code i am getting response like above means all user_images array coming... but here i need image_title how to get that.. if i run for loop getting error.. pls do help
if let code = ((response.dict?["result"] as? [String : Any])){
let userImages = code["user_images"] as? [String : Any]
}
how to get image_title value from above array of dictionaries
Sol 1
if let code = response.dict?["result"] as? [String : Any] {
if let userImages = code["user_images"] as? [[String : Any]] {
for item in userImages {
print(item["image_title"])
}
}
}
Sol 2
if let code = response.dict?["result"] as? [String : Any] {
do {
let data = try JSONSerialization.data(withJSONObject: code)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode(Result.self, from: data)
let titles = res.userImages.map { $0.imageTitle }
print(titles)
}
catch {
print(error)
}
}
// MARK: - Result
struct Result: Codable {
let userImages: [UserImage]
}
// MARK: - UserImage
struct UserImage: Codable {
let id: Int
let userId, image, imageTitle: String
}
Code from #Sh_Khan
if let code = response.dict?["result"] as? [String : Any] {
//You're using "as?" which means you're casting as an optional type.
//One simple solution to this is here where it's unwrapped using "if let"
if let userImages = code["user_images"] as? [[String : Any]] {
// [String:Any] is a Dictionary
// [[String:Any]] is an Array of Dictionary
for item in userImages {
print(item["image_title"])
}
} else {
// We didn't unwrap this safely at all, do something else.
}
Let's dive into this a little bit. This structure is a JSON Object
{
"id": 113,
"user_id": "160",
"image": "1617349564.jpg",
"image_title": "33"
}
But it's only a JSON object when it stands alone. Adding a key, or in this example user_images makes it a dictionary. Notice that the [ is not wrapped around it. Meaning it's a standalone dictionary. If this was your object, and this alone, your original code would work, but you're dealing with an Array of Dictionaries.
"user_images":
{
"id": 113,
"user_id": "160",
"image": "1617349564.jpg",
"image_title": "33"
}
This line of code essentially means that you're expecting to get back that Array of Dictionary. Bear in mind, each value for the dictionary is not an array value, which is why you don't see something like this [[[String: Any]]] because the data isn't nested like that.
if let userImages = code["user_images"] as? [[String : Any]]
What's this about optionals?
An optional is basically a nil possible value that can be returned. Typically when working with JSON you cannot guarantee that you'll always receive a value for a given key. It's even possible for a key value pair to be completely missing. If that were to happen you'd end up with a crash, because it's not handled. Here are the most common ways to handle Optionals
var someString: String? //This is the optional one
var someOtherString = "Hello, World!" //Non-optional
if let unwrappedString1 = someString {
//This code will never be reached
} else {
//This code will, because it can't be unwrapped.
}
guard let unwrappedString2 = someString else {
//This code block will run
return //Could also be continue, break, or return someValue
}
//The code will never make it here.
print(someOtherString)
Furthermore, you can work with optionals by chain unwrapping them which is a nifty feature.
var someString: String?
var someInt: Int?
var someBool: Bool?
someString = "Hello, World!"
//someString is not nil, but an important distinction to make, if any
//fail, ALL fail.
if let safeString = someString,
let safeInt = someInt,
let safeBool = someBool {
//If the values are unwrapped safely, they will be accessible here.
//In this case, they are nil, so this block will never be hit.
//I make this point because of scope, the guard statement saves you from the
//scoping issue present in if let unwrapping.
print(safeString)
print(safeInt)
print(safeBool)
}
guard let safeString = someString,
let safeInt = someInt,
let safeBool = someBool {
//This will be hit if a value is null
return
}
//However notice the scope is available outside of the guard statement,
//meaning you can safely use the values now without them being contained
//to an if statement. Despite this example, they would never be hit.
print(safeString)
print(safeInt)
print(safeBool)
Below line of code is producing the error,
let response1 : NSMutableArray = NSMutableArray.init(array: (JSON.object as! NSMutableArray).value(forKey: "media_list") as! NSArray)
As the error says I understand its a cast exception, but I'm not able to modify the code to make it work. I'm kinda new to Swift, so any help would be appreciated.
Below is my JSON.object
So, I checked and this is my JSON.object
[["offset": 30119146, "file_size": 30119146, "filename": video_220120201129271580.mp4, "mediaPath": file:///Users/evuser/Library/Developer/CoreSimulator/Devices/B9B0232F-237D-4413-BB81-BD5FAC727305/data/Containers/Data/Application/401D5D91-4500-434A-98FE-BD416135A1C7/Documents/video_220120201129271580.mp4, "status": completed, "group_id": fKQ2Xd9bE0cXchsw, "createdDate": 2020/01/22 13:59:47, "_id": 5e27e4d3138c8801cd3c26ca, "user_id": 21, "mime_type": video/mp4, "dest_path": /video_220120201129271580.mp4, "resource_id": 3a743d84-eafe-41e5-9f4c-dece67598c32],
["offset": 6435018, "file_size": 6435018, "filename": video_220120201127525480.mp4, "mediaPath": file:///Users/evuser/Library/Developer/CoreSimulator/Devices/B9B0232F-237D-4413-BB81-BD5FAC727305/data/Containers/Data/Application/401D5D91-4500-434A-98FE-BD416135A1C7/Documents/video_220120201127525480.mp4, "status": completed, "group_id": ffoHuGL0Z17vOqY9, "createdDate": 2020/01/22 13:58:10, "_id": 5e27e472138c8801cd3c26c9, "user_id": 21, "mime_type": video/mp4, "dest_path": /video_220120201127525480.mp4, "resource_id": 50e34fd5-b488-4861-aedd-03ea1ed0d91c]]
It seems that JSON.object may not be an array. Or at least not mutable array. It will be hard for us to identify your issue without having a look into JSON.object. A quick fix may actually be
let response1 : NSMutableArray = NSMutableArray.init(array: (JSON.object as! NSArray).value(forKey: "media_list") as! NSArray)
but I would try to dig in a bit more. Try to check what exactly is going on and try to avoid old Objective-C Next Step (NS) objects. Do it step by step:
let response1: [Any]? = {
guard let mainArray = JSON.object as? [Any] else {
print("Outer object is not an array. Check type of \(JSON.object)")
return nil
}
var mutableVersionOfArray = mainArray // This already creates a mutable copy because we used "var" instead of "let"
guard let mediaList = mutableVersionOfArray.value(forKey: "media_list") as? [Any] else {
print("Inner object is not an array. Check type of \(mutableVersionOfArray.value(forKey: "media_list"))")
return nil
}
return mediaList
}()
But this code makes no sense to me. Looking at your code I expect that your JSON object looks similar to:
{
"media_list": [{}, {}]
}
in this case you are looking at dictionaries. Try the following:
let mediaList: [Any]? = {
guard let topDictionary = JSON.object as? [String: Any] else {
print("Outer object is not a dictionary. Check type of \(JSON.object)")
return nil
}
guard let mediaListItem = topDictionary["media_list"] else {
print("There is no media_list in payload")
return nil
}
guard let mediaList = mediaListItem as? [Any] else {
print("mediaList is not an array")
return nil
}
return mediaList
}
I hope you can see the difference between an array and a dictionary. Array has some N ordered elements in it while a dictionary has key-value pairs. So to access a value under key you call it as dictionary[key]. Your whole code if you are correct could simply be:
let response1 = (JSON.object as? [String: Any])?["media_list"] as? [Any]
but if it returns nil it may be a bit hard to debug what went wrong.
I need to create a dictionary from array with custom type for first index of the array.
Sample array : ["ABC","ZYZ","123"]
Required result : [{"name" : "ABC", "type:"A"},{"name" : "ZYZ", "type:"B"},{"name" : "123", "type:"B"}]
Note type A for first index.
My code
for url in urlArray {
urlDict["name"] = url
}
You can do a map, and then individually change the type of the first dictionary:
var dicts = urlArray.map { ["name": $0, "type": "B"] }
dicts[0]["type"] = "A"
Seeing how all your dictionary keys are all the same, and that you are sending this to a server, a Codable struct might be a better choice.
struct NameThisProperly : Codable {
var name: String
var type: String
}
var result = urlArray.map { NameThisProperly(name: $0, type: "B") }
result[0].type = "A"
do {
let data = try JSONDecoder().encode(result)
// you can now send this data to server
} catch let error {
...
}
I suppose you can use a high order function such as map or reduce
Here is an example using reduce
var array = ["ABC","ZYZ","123"]
var result = array.reduce([[String: String]](), { (previous, current) -> [[String: String]] in
let type = previous.count == 0 ? "A" : "B"
let dictForCurrent = [
"name": current,
"type": type
]
return previous + [dictForCurrent]
})
print(result)
The result:
[["type": "A", "name": "ABC"], ["type": "B", "name": "ZYZ"], ["name":
"123", "type": "B"]]
Use reduce to convert array to dictionary:
let resultDict: [String: String]
= array.reduce(into: [:]) { dict, url in
dict["name"] = url
}
The result will look like:
[
"name": URL1,
"name": URL2
]
Use map(_:) to convert each element of the array to dictionary like so,
let arr = ["ABC","ZYZ","123"]
let result = arr.map { (element) -> [String:String] in
var dict = [String:String]()
dict["name"] = element
if let char = element.first {
dict["type"] = String(char)
}
return dict
}
print(result)
since you are concern about the index, my approach will be using enumerated() which gives out the index
let array = ["ABC","ZYZ","123"]
var results: [[String: String]] = []
for (i, content) in array.enumerated() {
let type: String = i == 0 ? "A" : "B"
results.append(["name": content, "type": type])
}
print(result)
// [["type": "A", "name": "ABC"], ["name": "ZYZ", "type": "B"], ["type": "B", "name": "123"]]
i am parsing "switch_name" from switch array but i am getting nil value while parsing
{
"status": "true",
"result": {
"hubs": [
{
"hub_id": "1",
"user_id": "35",
"switch": [
{
"id": "4",
"hub_id": "1",
"switch_name": "Test2",
"user_id": "35",
"serial_no": "445112",
"topic_sense": "rer",
"device_room": "25",
"switch_type": "LIGHTS",
"types_of_relay_switch": "S"
}
],
"relay": []
}
],
"switchwithouhub": []
}
}
how i am parsing : -
let sName = jsonDict.value(forKeyPath: "result.hubs.switch.switch_name") as? [String]
i am getting nil value while parsing switch_name.
please help and suggest how can i parse JSON
You are trying to access the element of an arrays (hubs, switch) directly. You must provide the proper index to access the item.
let sName = jsonDict.value(forKeyPath: "result.hubs[0].switch[0].switch_name") as? String
UPDATE: You can use SwiftyJson for parsing json data.
import SwiftyJSON
do { let jsonData = try JSON(data: response.data) {
let names = jsonData["hubs"][0]["switch"].array.flatMap({ (switch) -> String in
return switch.name
})
}
catch {
print("Swifty Error")
}
I'm using AlamoFire to make a post call that is failing because the json is missing a field. The problem seems to be because of an optional variable I'm trying to put in the json to send to backend.
func toJSON() -> Dictionary<String, AnyObject> {
let subCategory = self.adCategory!.toJSON()
let images = self.adImages
var imgArray = [Dictionary<String, AnyObject>]()
for image in images! {
let img = image as! AdImage
imgArray.append(img.toJSON())
}
return [
"title": self.title as AnyObject,
"description": self.adDescription as AnyObject,
"category": subCategory as AnyObject,
"images": imgArray as AnyObject
]
}
Title, Description and images work fine. Category don't.
When I print the dictionary before calling Alamofire, category comes with '=' instead of ':'and also with ';', as you can see below:
"category": {
parent = {
slug = "weird-widgets";
};
slug = widgets;
This is the code toJSON() from the SubCategory:
func toJSON() -> Dictionary<String, Any> {
let category = self.category?.toJSON()
return [
"slug": self.slug as AnyObject,
"parent": category as AnyObject
]
}
Is there a correct way to wrap optional types to avoid this kind of issue? I tried several approaches but none of them is working.