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")
}
Related
I am pretty new to Swift and as an exercise I try to create an App with Swift 5, which should show you the Weather at a Location you searched for.
Right now I am trying to implement a function that can turn a local JSON file in a struct I am using.
The JSON file is located at the same directory as all the other files.
The JSON for testing purposes looks like this:
[
{
"id": 833,
"name": "Ḩeşār-e Sefīd",
"state": "",
"country": "IR",
"coord": {
"lon": 47.159401,
"lat": 34.330502
}
},
{
"id": 2960,
"name": "‘Ayn Ḩalāqīm",
"state": "",
"country": "SY",
"coord": {
"lon": 36.321911,
"lat": 34.940079
}
}
]
The struct:
struct Root: Codable {
let jsonWeather: [JsonWeather]?
}
struct JsonWeather: Codable {
let id: Int
let name: String
let state: String
let country: String
let coord: Coord
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
case state = "state"
case country = "country"
case coord = "coord"
}
}
The function that i am working on:
func loadJson(fileName: String) -> Void{
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let jsonData = try JSONDecoder().decode(Array<Root>.self, from: data)
print(type(of: jsonData)) // Array<Root>
print(jsonData) // [WeatherApp.Root(jsonWeather: nil), WeatherApp.Root(jsonWeather: nil)]
} catch {
print("error:\(error)")
}
}
}
After all I wanted to see how the result looks like and I noticed that each jsonWeather in Root is nil
So in the end I understand that the values are nil because I allow them to be nil by setting an optional in the Root struct, but I dont quite understand why they are turning nil because there is data given. Also I don't know how I would implement it without being an optional.
This is my first time using JSONDecoder and Optionals in Swift.
Could anyone point out what I did wrong (or understood wrong) and how to fix this issue
Since your JSON is just of array of objects, you don't need a top-level struct like Root.
Get rid of your Root struct, and just decode an array of JsonWeathers:
let jsonData = try JSONDecoder().decode([JsonWeather].self, from: data)
(Note that there is no difference between [JsonWeather].self and Array<JsonWeather>.self - I just chose to do it this way since it's shorter.)
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 trying to filter the array which consist of array and dictionary inside it. I want to filter based on the type of the service and then whose isPrimaryMailbox is Yes under attributes array.
This is what I have done:-
let services = Manager.realm().objects(Service.self).filter("type = %#", "MAILBOX")
let serviceWithPrimaryEmail = services.filter({$0.attributes.first?.value == "Yes"})
But this is showing the data which has isPrimaryMailbox value is No
Below is the json response :-
{
"data": {
"cust": [
{
"customerId": "2040349110",
"serv": [
{
"bill": "2010007656959",
"services": [
{
"type": "MOBILE",
"status": "ACTIVE",
"plan": {
"name": "Mobil"
},
"addOns": [
{
"status": "Active"
}
],
"hardware": [
{
"type": "HANDSET"
}
]
},
{
"type": "MOBILE",
"plan": {
"name": "Mobile Service"
},
"addOns": [
{
"status": "Active"
}
],
"hardware": [
{
"type": "HANDSET",
}
]
},
{
"type": "MAILBOX",
"plan": {
"name": "Service"
},
"attributes": [
{
"name": "mailboxSize",
"value": "1 GB"
},
{
"name": "isPrimaryMailbox",
"value": "Yes"
}
]
},
{
"type": "MAILBOX",
"status": "ACTIVE",
"plan": {
"name": "Service"
},
"attributes": [
{
"name": "mailboxSize",
"value": "1 GB"
},
{
"name": "isPrimaryMailbox",
"value": "No"
}
]
}
]
}
]
}
]
}
}
You can try this:
let serviceWithPrimaryEmail = services.filter({$0.attributes.[1].value == "Yes"})
// I used the index 1 because as I see in you JSON data, the isPrimaryMailBox is always in the second row.
Also, your JSON is badly formatted. If you are gonna use an associated Array, then why do you have to separate the key and value to a different key-value pair?
The attributes can simply be:
"attributes": {
"mailboxSize":"1 GB",
"isPrimaryMailbox":true
}
Using boolean instead of string "YES" or "NO"
So that when you filter, you simple say
let serviceWithPrimaryEmail = services.filter({$0.attributes["isPrimaryMailbox"]})
Or if you really prefer the string "YES" then:
let serviceWithPrimaryEmail = services.filter({$0.attributes["isPrimaryMailbox"] == "YES"})
I don't know the type of services object. But here is how you can get primary email service from your JSON:
typealias JSON = [String: Any]
let json = yourJSONDict as? JSON
let services = (((json["data"] as? JSON)?["cust"] as? JSON)?["serv"] as? JSON)?["services"] as? [JSON]
let primaryEmailService = services?.filter { service in
if (service["type"] as? String) == "MAILBOX" {
for attribute in service["attributes"] as? [[String: String]] ?? [] {
if (attribute["name"] == "isPrimaryMailbox") && (attribute["value"] == "Yes") {
return true
}
}
}
return false
}
.first
Edited:
If you don't want to use for loop, you can use filter and reduce only:
let primaryEmailService = services?
.filter {
let attributes = $0["attributes"] as? [[String: String]] ?? []
return ($0["type"] as? String) == "MAILBOX"
&& attributes.reduce(false, { $0 || (($1["name"] == "isPrimaryMailbox") && ($1["value"] == "Yes")) })
}
.first
Please note, that worse case complexity for both approaches are equal. But best case complexity for the first approach is better, because you don't have to loop over all attributes in reduce function. And Swift compiler isn't smart enough for now to stop reduce looping after the first match (false || true always gives true. There is no need to proceed if we have one true).
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
}
self.manager!.request( url+"FindNearestClasses", method: .post, parameters: requestDictionary, encoding: URLEncoding.httpBody,headers: headers
).responseJSON { response in
print(response.result.value)
}
The problem I have the json returned to me is like below. How can I parse it by number that is get array, and then after array each read first second third and fourth numbers.
[
[
"1",
"2",
"3",
4],
[
"5",
"6",
"7",
8]
]
let myjson = [ [ "1", "2", "3", "4"], [ "5", "6", "7", "8"] ]
let myInt = Int(myjson[0].first as! String)
/*
let i = 0
let j = 0
let myInt = Int(myjson[i][j] as! String)
i stands for the which array 0 or 1 in this case. j stands for the value inside that array.
*/
print(myInt)
This will print 1 as Int, if you want to loop through entire array then use for loop.
for myi in myjson{
for myj in myi {
print(myj) // this will print all the contents of both array.
}
}
To convert string to Int, use _ = Int(myjson[i][j] as! String)
The best way is to use a tool to map the response into swift objects, an example is Object mapper: https://github.com/tristanhimmelman/AlamofireObjectMapper
import AlamofireObjectMapper
let URL = "https://raw.githubusercontent.com/tristanhimmelman/AlamofireObjectMapper/d8bb95982be8a11a2308e779bb9a9707ebe42ede/sample_json"
Alamofire.request(URL).responseObject { (response: DataResponse<WeatherResponse>) in
let weatherResponse = response.result.value
print(weatherResponse?.location)
if let threeDayForecast = weatherResponse?.threeDayForecast {
for forecast in threeDayForecast {
print(forecast.day)
print(forecast.temperature)
}
}
}