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).
Related
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'm having the error 3840 "JSON text did not start with array or
object and option to allow fragments not set." on my application after
being on the same page for a while.
I'm calling a cloud code function from my Parse server, getting the answer and displaying it. It works fine, but after waiting a little bit the application crashes.
I'm calling my manager for the cloud code function:
ParseManager.sharedManager().fetchPrice(startDate: startDate, endDate: endDate, housingId: housing.objectId!) { (objects, error) in
if (objects != []) {
var price = 0
for object in objects! {
price += object["prix"] as! Int
}
price /= objects!.count
self.priceByNight.text = "Price: \(price)"
}
}
In my manager:
func fetchPrice(startDate: Date, endDate: Date, housingId: String, completion: PFResults?) {
let params = [
"startDate": startDate,
"endDate": endDate,
"housing": housingId
] as [String : Any]
PFCloud.callFunction(inBackground: "fetchPrice", withParameters: params) { (objects, error) in
if let error = error {
print(error)
completion?(nil, error)
return
}
if let objects = objects as? [PFObject] {
completion?(objects, nil)
}
}
}
This is what my function send me back:
{
"result": [{
"price": 10,
"createdAt": "2019-05-07T12:39:47.320Z",
"updatedAt": "2019-05-09T15:31:25.957Z",
"date": {
"__type": "Date",
"iso": "2019-05-20T12:00:00.000Z"
},
"idHousing": {
"__type": "Relation",
"className": "myOtherClassName"
},
"objectId": "XXXXXXXXXX",
"__type": "Object",
"className": "MyClassName"
}]
}
The problem was how I was checking what the manager was returning, checking it the wrong way made it try to do things with a null object.
The solution was to change if (objects != []) { to if let objs = objects { and it works now.
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 am new in swift and I am also trying to use Alamofire to call data from API. I am quite puzzled on how I will use the PUT Request to update data. I've read some solutions here in SO but I don't know how I will apply on my app. I am creating an Event App, the scenario should be, when the participant clicked the Check In Button, it will update the registered_flag to true meaning the participant will marked as Registered and the button will be changed to Check Out. I really don't know if my API Service is correct or not. Hope you could help me. Thank you so much.
JSON of the Event Participant Where in the registered_flag should be updated once checkInOutButton
{
"event_name": "Q & A",
"event_participants": [
{
"participant_id": "70984656-92bc-4c36-9314-2c741f068523",
"employee_number": null,
"last_name": "Surname",
"first_name": "FirstName",
"middle_name": null,
"display_name": "Surname, FirstName ",
"department_name": "Medical Informatics",
"position_name": "Application Developer",
"registered_flag": true,
"registered_datetime": "2018-09-13T08:54:40.150",
"registration_type": 1,
"delete_flag": false,
"manual_reg_flag": false,
"out_flag": false,
"out_datetime": null,
"classification": 6,
"others": "Guest"
}
}
JSON to update for check in
{
"registered_flag": true,
"registration_type": 1
}
updateType
enum UpdateParticipantType: String {
case checkIn = "Check In"
case checkOut = "Check Out"
}
APIService for UpdateParticipant
func updateParticipant(updateType: UpdateParticipantType,
participantID: String,
successBlock: #escaping ([Attendee]) -> Void,
failureBlock: #escaping (Error) -> Void)
{
let updateParticipantURL = URL(string: "\(REGISTER_PARTICIPANT_URL)/\(updateType)/\(participantID)")
Alamofire.request(updateParticipantURL!, method: .put).responseJSON { (response) in
print(response)
if let error = response.error
{
failureBlock(error)
print(error)
return
}
if let jsonArray = response.result.value as? [[String : Any]] {
for anItem in jsonArray {
if let eventparticipants = anItem["event_participants"] as? [[String : Any]] {
var extractedAttendees = [Attendee]()
for participants in eventparticipants{
let success = Attendee.init(JSON: participants)
extractedAttendees.append(success!)
}
successBlock(extractedAttendees)
}
}
}
}
}
As per Alamofire documentation:
let parameters: Parameters = [
"foo": "bar",
"baz": ["a", 1],
"qux": [
"x": 1,
"y": 2,
"z": 3
]
]
Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters)
For a given json
{
"registered_flag": true,
"registration_type": 1
}
let parameters: Parameters = [
"registered_flag": true
"registration_type": 1
]
If I have a dictionary "dic" like this:
{
"a": {
"b": Any
}
"c": {
"d": Any
}
}
If I want to change the value of key "b", I know I can do it like this:
dic["a"]["b"] = somethingNew
But If the key path is varible, how can I change the value according to the key path? is there some api like this:
dic.updateValueByKeyPath(path: ["a", "b"], updateValue: somethingNew)
Or the idea to achieve this purpose , thanks ~
I recently answered a Q&A that wanted to remove (rather than update) a nested value in a dictionary of nested dictionaries.
Remove nested key from dictionary
We can use a similar approach here: using a recursive method which visits your given key path by repeatedly attempting conversions of (sub-)dictionary values to [Key: Any] dictionaries themselves. The only restriction here is that the Key types much be the same for all dictionaries within the nested dictionary.
Implementation
Recursive "core" function updateValue(_:, inDict:, forKeyPath:) and public updateValue(_:, forKeyPath:) method form key paths on for [Key] (e.g. ["a", "b"] applied to your example):
/* general "key path" extension */
public extension Dictionary {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: [Key])
-> Value? {
let (valInd, newDict) = updateValue(value, inDict: self,
forKeyPath: Array(keyPath.reversed()))
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
fileprivate func updateValue(_ value: Value, inDict dict: [Key: Any],
forKeyPath keyPath: [Key]) -> (Value?, [Key: Any]) {
guard let key = keyPath.last else { return (value, dict) }
var dict = dict
if keyPath.count > 1, let subDict = dict[key] as? [Key: Any] {
let (val, newSubDict) = updateValue(value, inDict: subDict,
forKeyPath: Array(keyPath.dropLast()))
dict[key] = newSubDict
return (val, dict)
}
let val = dict.updateValue(value, forKey: key) as? Value
return (val, dict)
}
}
Less general public updateValue(_:, forKeyPath:) method (using the core function above) for keys that conforms to ExpressibleByStringLiteral; key paths on form my.key.path (e.g. "a.b" applied to your example):
/* String literal specific "key path" extension */
public extension Dictionary where Key: ExpressibleByStringLiteral {
public mutating func updateValue(_ value: Value, forKeyPath keyPath: String)
-> Value? {
let keyPathArr = keyPath.components(separatedBy: ".")
.reversed().flatMap { $0 as? Key }
if keyPathArr.isEmpty { return self.updateValue(value, forKey: "") }
let (valInd, newDict) = updateValue(value,
inDict: self, forKeyPath:keyPathArr)
if let dict = newDict as? [Key: Value] { self = dict }
return valInd
}
}
Example usage
We'll apply the methods above to the example from the linked thread.
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Using the ExpressibleByStringLiteral key path method to update the value of an existing key-value pair:
if let oldValue = dict.updateValue("nihongo",
forKeyPath: "countries.japan.language") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Removed old value: japanese
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
The same method used to add a new key-value pair at a given key path dictionary:
if let oldValue = dict.updateValue("asia",
forKeyPath: "countries.japan.continent") {
print("Removed old value: ", oldValue)
}
else {
print("Added new key-value pair")
}
print(dict)
/* Added new key-value pair
[
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917"
],
"language": "nihongo",
"continent": "asia"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
] */
We would get the same result as in the examples above if we used the general [Key] as key path method instead of the ExpressibleByStringLiteral one used above. Using the former, the calls would be changed into:
... = dict.updateValue("nihongo",
forKeyPath: ["countries", "japan", "language"]
... = dict.updateValue("asia",
forKeyPath: ["countries", "japan", "continent"]
Finally note that a call to updateValue using the [Key] as key path method will return nil also in case of an empty array being passed as argument ([]). This could possibly be changed to a throwing case, as a nil return above should tell us that a new key-value pair was added (as inspired by the updateValue(_:, forKey:) method in stdlib).
Performance?
The methods above will make use of some (sub-)dictionary copying along the way, but unless you're working with huge dictionaries, this shouldn't be an issue. Anyhow, no need to worry about performance until a profiler tells you have a bottleneck.