Sorting an array of dictionaries in Swift 2 - ios

I've read many articles on SO and I'm still stumped! I have an array of dictionaries, see below:
var myArray = [[String:AnyObject]]()
myArray.append([
"caseNumber" : "12349",
"formType" : "Advanced",
"caseStatus" : "Approved",
"caseDetails" : "blah blah",
"caseLUD" : NSDate(),
"friendlyName" : "name1"
])
myArray.append([
"caseNumber" : "12345",
"formType" : "Standard",
"caseStatus" : "On-Hold",
"caseDetails" : "blah blah",
"caseLUD" : NSDate(),
"friendlyName" : "name2"
])
myArray.append([
"caseNumber" : "12342",
"formType" : "Normal",
"caseStatus" : "Rejected",
"caseDetails" : "blah blah",
"caseLUD" : NSDate(),
"friendlyName" : "name3"
])
This data will go into a tableView and I want the user to be able to decide the sort order, either by caseNumber or my caseLUD (date). But I can't figure out how to re-order the array by values. Any ideas, please? All help appreciated.

To sort by caseNumber:
myArray.sortInPlace { ($0["caseNumber"] as! String) < ($1["caseNumber"] as! String) }
Note, this uses caseNumber as a String which could lead to surprising results (for example "12345" < "2"). You might either want to store caseNumber as an Int, or convert it to an Int when sorting:
myArray.sortInPlace { Int($0["caseNumber"] as! String) < Int($1["caseNumber"] as! String) }
To sort by caseLUD:
myArray.sortInPlace { ($0["caseLUD"] as! NSDate).compare($1["caseLUD"] as! NSDate) == .OrderedAscending}
.OrderedAscending will give you earliest date first, and .OrderedDescending will give you latest date first.
If you stored your data in a Struct instead of a Dictionary, you could avoid the messy typecasting.
struct CaseRecord {
var caseNumber: Int
var formType: String
var caseStatus: String
var caseDetails: String
var caseLUD: NSDate
var friendlyName: String
}
var myArray = [CaseRecord]()
myArray.append(CaseRecord(
caseNumber : 12345,
formType : "Advanced",
caseStatus : "Approved",
caseDetails : "blah blah",
caseLUD : NSDate(),
friendlyName : "name1"
)
)
myArray.append(CaseRecord(
caseNumber : 124,
formType : "Standard",
caseStatus : "On-Hold",
caseDetails : "blah blah",
caseLUD : NSDate(),
friendlyName : "name2"
)
)
myArray.append(CaseRecord(
caseNumber : 13,
formType : "Normal",
caseStatus : "Rejected",
caseDetails : "blah blah",
caseLUD : NSDate(),
friendlyName : "name3"
)
)
myArray.sortInPlace { $0.caseNumber < $1.caseNumber }
myArray.sortInPlace { $0.caseLUD.compare($1.caseLUD) == .OrderedDescending }

Related

Get multiple strings from JSON

So the JSON variable let json = JSON(nearbyChargingSites.jsonString!) contains the current data.:
{
"timestamp" : 1626902257093,
"superchargers" : [
{
"location" : {
"lat" : 63.325319,
"long" : 10.305137
},
"total_stalls" : 19,
"distance_miles" : 10.064082000000001,
"type" : "supercharger",
"site_closed" : false,
"available_stalls" : 15,
"name" : "Leinstrand, Norway - Klett"
},
{
"location" : {
"lat" : 63.466445999999998,
"long" : 10.91766
},
"total_stalls" : 16,
"distance_miles" : 11.838984999999999,
"type" : "supercharger",
"site_closed" : false,
"available_stalls" : 16,
"name" : "Stjørdal, Norway"
},
{
"location" : {
"lat" : 63.734355000000001,
"long" : 11.281487
},
"total_stalls" : 12,
"distance_miles" : 31.206503999999999,
"type" : "supercharger",
"site_closed" : false,
"available_stalls" : 11,
"name" : "Levanger, Norway"
},
{
"location" : {
"lat" : 62.832030000000003,
"long" : 10.009639999999999
},
"total_stalls" : 20,
"distance_miles" : 44.117753,
"type" : "supercharger",
"site_closed" : false,
"available_stalls" : 17,
"name" : "Berkåk, Norway"
}
],
"congestion_sync_time_utc_secs" : 1626902199,
"destination_charging" : [
{
"distance_miles" : 23.366278999999999,
"name" : "Bårdshaug Herregård",
"location" : {
"lat" : 63.299208,
"long" : 9.8448650000000004
},
"type" : "destination"
},
{
"distance_miles" : 38.390034,
"name" : "Fosen Fjordhotel",
"location" : {
"lat" : 63.959356999999997,
"long" : 10.223908
},
"type" : "destination"
},
{
"distance_miles" : 46.220022999999998,
"name" : "Falksenteret",
"location" : {
"lat" : 63.293301999999997,
"long" : 9.0834460000000004
},
"type" : "destination"
},
{
"distance_miles" : 54.981445000000001,
"name" : "Væktarstua",
"location" : {
"lat" : 62.908683000000003,
"long" : 11.893306000000001
},
"type" : "destination"
}
]
}
I use SwiftyJSON and tries to get the superchargers latitude, longitude and name, like this:
let jsonName = json["superchargers"]["name"]
let jsonLat = json["superchargers"]["location"]["lat"]
let jsonLong = json["superchargers"]["location"]["long"]
When trying to print any of those, all of them return nil.
Any ideas what I am doing wrong, and how to do this?
The reason I want to do this is because I want to add them as annotation to a MKMapView.
The first error is that the JSON initializer you are using will create a single JSON string object, it will not parse the string as JSON data:
Instead of:
let json = JSON(nearbyChargingSites.jsonString!)
you need to use:
let json = JSON(data: dataFromJSONString)
Second you need to iterate over the superchargers array to collect all the values
Try something like:
if let dataFromString = nearbyChargingSites.jsonString!.data(using: .utf8, allowLossyConversion: false) {
let json = try! JSON(data: dataFromString,options: .allowFragments)
for supercharger in json["superchargers"].arrayValue {
let jsonName = supercharger["name"].stringValue
let jsonLat = supercharger["location"]["lat"].doubleValue
let jsonLong = supercharger["location"]["long"].doubleValue
}
}
Please note that the above code does not perform error handling and will crash if values are missing from JSON.

filter array of json in swift

How can i filter an array of json in swift having only certain key value pairs?
my array looks like:
[{
"status" : "true",
"score" : "3",
"correct" : "3",
"chapter" : "34",
"answer" : "342432",
"solutionText" : "abcd",
}, {
"status" : "true",
"score" : "0",
"correct" : "2",
"chapter" : "35",
"answer" : "35854",
"solutionText" : "abc",
}]
i want to get an array of json output having only status, chapter & correct key value pairs.
Like:
[{
"status" : "true",
"correct" : "3",
"chapter" : "34",
}, {
"status" : "true",
"correct" : "2",
"chapter" : "35",
}]
Considering this is your JSON
var myJSON = """
[{
"status" : "true",
"score" : "3",
"correct" : "3",
"chapter" : "34",
"answer" : "342432",
"solutionText" : "abcd"
}, {
"status" : "true",
"score" : "0",
"correct" : "2",
"chapter" : "35",
"answer" : "35854",
"solutionText" : "abc"
}]
"""
Simply create a Decodable struct like this
typealias MyArray = [MyObject] // Use this to decode
struct MyObject: Codable {
let status, correct, chapter: String
}
And use it like this
//Usage
var myJSONData = myJSON.data(using: .utf8)! // converting the JSON to data
let objArray = try! JSONDecoder().decode(MyArray.self, from: myJSONData) // decoding the json data into an object
//how to access
print(objArray.count)// number of elements in my array
print(objArray.first!) // getting the first object
let myObject = obj[0] // also getting the first object by index
myObject.chapter
myObject.correct
myObject.status
Read about Codable here .
If it's already in an object, you can try
filteredArray = myArray.map { ["status": $0.status, "correct": $0.correct, "chapter": $0.chapter] }
You can try
do {
let res = try JSONDecoder().decode([Root].self, from:data)
}
catch {
print(error)
}
struct Root: Codable {
let status, correct, chapter: String
}
Correct json
[{
"status" : "true",
"score" : "3",
"correct" : "3",
"chapter" : "34",
"answer" : "342432",
"solutionText" : "abcd"
}, {
"status" : "true",
"score" : "0",
"correct" : "2",
"chapter" : "35",
"answer" : "35854",
"solutionText" : "abc"
}]
It's more suitable to make
status a bool not string
correct & chapter to be integers
so json look like
[{
"status" : true,
"score" : "3",
"correct" : 3,
"chapter" : 34,
"answer" : "342432",
"solutionText" : "abcd"
}, {
"status" : true,
"score" : "0",
"correct" : 2,
"chapter" : 35,
"answer" : "35854",
"solutionText" : "abc"
}]
you can also make other values like that if you need , then your model will look like
struct Root: Codable {
let status: Bool
let correct, chapter: Int
}
Edit:
let data = try content.rawData() // this inside do block
where content is of type JSON
try Decodable of swift
class MyObjectsClass : Decodable {
var objects : [Objects]?
}
class Objects : Decodable {
var status : String?
var correct : String?
var chapter : String?
}
While Decoding
let decodedValue = try? JSONDecoder.decode(MyObjectsClass.self, from : data)
// where data from API calls

getting error when trying to send [JSON] as parameter value in Moya

I'm trying to post a http request and I have to deal with nested json.
I've tried to send my parameter as
[[String: Any]]
and
[JSON]
, but I get error in both situation.
this is my code:
var accountTitlesForNewProject = [JSON]()
for indexCounter in 0 ..< self.accountTitles.count {
let _accountTitle = self.accountTitles[indexCounter]
if !isChecked[indexCounter] {
continue
}
var _accountTitleJSON = JSON()
_accountTitleJSON["name"] = _accountTitle["name"]
_accountTitleJSON["description"] = _accountTitle["description"]
var _codes = [JSON]()
for (_, _code) in _accountTitle["accounting_codes"] {
let _codeJSON = JSON(dictionaryLiteral: ("type", _code["type"]), ("code", _code["code"]), ("level", _code["level"]))
_codes.append(_codeJSON)
}
_accountTitleJSON["accounting_codes"] = JSON(_codes)
accountTitlesForNewProject.append(_accountTitleJSON)
}
print(accountTitlesForNewProject)
self.addProject(projectName: projectName, stateId: stateId, cityId: cityId, accountTitles: accountTitlesForNewProject, successHandler: successHandler)
this is result of print:
[{"accounting_codes" : [
{"level" : 1,
"type" : 1,
"code" : "694"
},
{"level" : 2,
"type" : 1,
"code" : "312"
},
{"level" : 3,
"type" : 1,
"code" : "336"
}],
"name" : "بنفشه صفوی",
"description" : "لابد حرف و سخنی و خنده‌ای و رفت. کنه‌ای بود. درست یک پیرمرد. یک ساعت به ماهی سه چهار هفته بیش‌تر دوام نکرد.."
},
{"accounting_codes" : [
{"level" : 1,
"type" : 1,
"code" : "977"
},
{"level" : 2,
"type" : 1,
"code" : "568"
},
{"level" : 3,
"type" : 1,
"code" : "178"
}],
"name" : "آزاده میرزاده",
"description" : "و دیگه خسته شده‌ام. دلم می‌خواد قضیه به همین سادگی تمام می‌شود. و بعد چند سال سابقه دارد و چند نفری از اولیای."
}]
and this is addProject function where problem occurs:
private func addProject(projectName: String, stateId: Int, cityId: Int, accountTitles: [JSON], successHandler: #escaping (String) -> ()) {
let authenticator: TGAccessTokenProvider = TGOpenAuthentication.getTGOpenAuthenticator()
authenticator.getAccessToken(successHandler: { (_accessToken) in
self.projectProvider.request(.store(accessToken: _accessToken, projectName: projectName, stateId: stateId, cityId: cityId, accountTitles: accountTitles), completion: { ...
}
I got error when self.projectProvider.request executes and this is error message:
"Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (_SwiftValue)'"
what's the problem?
as mentioned here, I solved my problem this way, cus my object property defined as:
private var accountTitles = [JSON]()
so this is the working code:
var accountTitlesForNewProject = [Any]()
for indexCounter in 0 ..< self.accountTitles.count {
let _accountTitle = self.accountTitles[indexCounter]
if !isChecked[indexCounter] {
continue
}
var _codes = [Any]()
for (_, _code) in _accountTitle["accounting_codes"] {
let _codeJSON: [String: Any] = ["type": _code["type"].intValue,
"code": _code["code"].stringValue,
"level": _code["level"].intValue]
_codes.append(_codeJSON)
}
let _accountTitleJSON: [String: Any] = ["name": _accountTitle["name"].stringValue,
"description": _accountTitle["description"].stringValue,
"accounting_codes": _codes]
accountTitlesForNewProject.append(_accountTitleJSON)
}

Firebase query order

I have multiple posts and each post have it's own comments. I want to query all posts, ordered by their time stamps. I tried:
let query = Firebase(url: path)
let query.queryOrderedByChild("commentDate").observeEventType(.ChildAdded)
{ (queryResponse, cancelBlock) { ....
"commentDate" are integers on server. At this query I get only the first object
let query = Firebase(url: path)
query.queryOrderedByChild("commentDate").queryLimitedToFirst(100).observeEventType(.ChildAdded) { (queryResponse, cancelBlock) { ...
I get only the first object as well...
The single solution I have at the moment is to query all :
query.queryOrderedByChild("commentDate").observeEventType(.Value) { (queryResponse, cancelBlock) in ...
But using this I must sort the array before showing to user.
Any ideas how can I do to get them sorted on server?
Edit
Representation of firebase data:
{
"-KGRZmSIDXz5hHkelthQ" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGRa5skGzA1GAG09Lno" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGVr-tti1zr1M1QLlHL" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : {
"-KGX1_rFSBLmQJ7QzCRc" : {
"commentBody" : "something",
"commentDate" : 1.461933727259896E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGX1r5Lnhv9YbQre6as" : {
"commentBody" : "something",
"commentDate" : 1.461933797884702E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGXVGKA0SYm-vRs6zsv" : {
"commentBody" : "something",
"commentDate" : 1.461941507496439E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
},
}
}
Firebase can query nested children if you know the entire path (so something like dimensions/length). It cannot handle dynamic elements in that path under each child (like your $commentid/commentDate).
The only way to get this working is to pull the necessary data up to a level where you can query it. For example, if you keep track of the lastCommentDate for each blog post:
{
"-KGRZmSIDXz5hHkelthQ" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGRa5skGzA1GAG09Lno" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"comments" : "",
},
"-KGVr-tti1zr1M1QLlHL" : {
"something1" : "783151",
"something2" : "21",
"something3" : "wjeicisje ejej",
"firstCommentDate": 1.461933727259896E9,
"lastCommentDate": 1.461941507496439E9,
"comments" : {
"-KGX1_rFSBLmQJ7QzCRc" : {
"commentBody" : "something",
"commentDate" : 1.461933727259896E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGX1r5Lnhv9YbQre6as" : {
"commentBody" : "something",
"commentDate" : 1.461933797884702E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGXVGKA0SYm-vRs6zsv" : {
"commentBody" : "something",
"commentDate" : 1.461941507496439E9,
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
},
}
}
With this structure you can query blog posts by their last comment date:
ref.orderByChild('lastCommentDate').limitToLast(10).on(...
Your data structure looks fine, so a simple query will return the results you want
let commentsRef = ref.childByAppendingPath("-KGVr-tti1zr1M1QLlHL/comments")
commentsRef.queryOrderedByChild("commentDate").observeEventType(.ChildAdded, withBlock: {
snapshot in
let nodeData = snapshot.value
print(nodeData!)
})
The above will return each child node, one at a time in the proper order.
If you have a small amount of nodes, you can just read them in at one time and iterate over the snapshot.children like this
usersRef.queryOrderedByChild("commentDate").observeEventType(.Value, withBlock: {
snapshot in
for child in snapshot.children {
print(child)
}
})
Both of the above will print out each comment node ordered by commentDate
The above will work only if you are interested in retrieving the comments within each post. If not, you'll need to change your Firebase structure and move the comments to separate node, like this
posts
post_01
post_02
etc
and a separate comments node that's refers to the post the comment belongs to. Since you want to query for say the comments for post_02, and want them ordered by commentDate, combine the post number and date into a single child.
comments
"-KGX1_rFSBLmQJ7QzCRc" : {
"for_post_and_commentDate": "post_02_1.461933727259896E9",
"commentBody" : "something",
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
"-KGX1r5Lnhv9YbQre6as" : {
"for_post_and_commentDate": "post_02_1.461933797884702E9",
"commentBody" : "something",
"commentOwnerImageView" : "something",
"commentOwnerName" : "something"
},
and then modify the query
usersRef.queryOrderedByChild("for_post_and_commentDate")
queryStartingAtValue("post_02_0").queryEndingAtValue("post_02_xxx").observeEventType(.Value, withBlock: {
You'll have to figure out your commentDate format as it would be easier to craft starting and ending timestamps if they were stored: 20160430100621 as a yyyymmddhhmmss format, then your timestamps could be queried like this:
starting: post_02_0
ending: post_02_99999999999999
This also gives you the flexibility to query for comments for post_02 that happened yesterday, or last week.

Rails & Mongoid unique results

Consider the following example of mongo collection:
{"_id" : ObjectId("4f304818884672067f000001"), "hash" : {"call_id" : "1234"}, "something" : "AAA"}
{"_id" : ObjectId("4f304818884672067f000002"), "hash" : {"call_id" : "1234"}, "something" : "BBB"}
{"_id" : ObjectId("4f304818884672067f000003"), "hash" : {"call_id" : "1234"}, "something" : "CCC"}
{"_id" : ObjectId("4f304818884672067f000004"), "hash" : {"call_id" : "5555"}, "something" : "DDD"}
{"_id" : ObjectId("4f304818884672067f000005"), "hash" : {"call_id" : "5555"}, "something" : "CCC"}
I would like to query this collection and get only the first entry for each "call_id", in other words i'm trying to get unique results based on "call_id".
I tried to use .distinct method:
#result = Myobject.all.distinct('hash.call_id')
but the resulting array will contain only the unique call_id fields:
["1234", "5555"]
and I need all the other fields too.
Is it possible to make a query like this one?:
#result = Myobject.where('hash.call_id' => Myobject.all.distinct('hash.call_id'))
Thanks
You cannot simply return the document(or subset) by using the distinct. As per the documentation it only returns the distinct array of values based on the given key. But you can achieve this by using map-reduce
var _map = function () {
emit(this.hash.call_id, {doc:this});
}
var _reduce = function (key, values) {
var ret = {doc:[]};
var doc = {};
values.forEach(function (value) {
if (!doc[value.doc.hash.call_id]) {
ret.doc.push(value.doc);
doc[value.doc.hash.call_id] = true; //make the doc seen, so it will be picked only once
}
});
return ret;
}
The above code is self explanatory, on map function i am grouping it by key hash.call_id and returning the whole doc so it can be processed by reduce funcition.
On reduce function, just loop through the grouped result set and pick only one item from the grouped set (among the multiple duplicate key values - distinct simulation).
Finally create some test data
> db.disTest.insert({hash:{call_id:"1234"},something:"AAA"})
> db.disTest.insert({hash:{call_id:"1234"},something:"BBB"})
> db.disTest.insert({hash:{call_id:"1234"},something:"CCC"})
> db.disTest.insert({hash:{call_id:"5555"},something:"DDD"})
> db.disTest.insert({hash:{call_id:"5555"},something:"EEE"})
> db.disTest.find()
{ "_id" : ObjectId("4f30a27c4d203c27d8f4c584"), "hash" : { "call_id" : "1234" }, "something" : "AAA" }
{ "_id" : ObjectId("4f30a2844d203c27d8f4c585"), "hash" : { "call_id" : "1234" }, "something" : "BBB" }
{ "_id" : ObjectId("4f30a2894d203c27d8f4c586"), "hash" : { "call_id" : "1234" }, "something" : "CCC" }
{ "_id" : ObjectId("4f30a2944d203c27d8f4c587"), "hash" : { "call_id" : "5555" }, "something" : "DDD" }
{ "_id" : ObjectId("4f30a2994d203c27d8f4c588"), "hash" : { "call_id" : "5555" }, "something" : "EEE" }
and running this map reduce
> db.disTest.mapReduce(_map,_reduce, {out: { inline : 1}})
{
"results" : [
{
"_id" : "1234",
"value" : {
"doc" : [
{
"_id" : ObjectId("4f30a27c4d203c27d8f4c584"),
"hash" : {
"call_id" : "1234"
},
"something" : "AAA"
}
]
}
},
{
"_id" : "5555",
"value" : {
"doc" : [
{
"_id" : ObjectId("4f30a2944d203c27d8f4c587"),
"hash" : {
"call_id" : "5555"
},
"something" : "DDD"
}
]
}
}
],
"timeMillis" : 2,
"counts" : {
"input" : 5,
"emit" : 5,
"reduce" : 2,
"output" : 2
},
"ok" : 1,
}
You get the first document of the distinct set. You can do the same in mongoid by first stringify the map/reduce functions and call mapreduce like this
MyObject.collection.mapreduce(_map,_reduce,{:out => {:inline => 1},:raw=>true })
Hope it helps

Resources