How to sort JSON Data in Array in swift 4 - swift

I have JSON array like this
var json = NSArray() // array with json objects
//print json >>
json = (
{
Name = "Alen";
Score = 500;
},
{
Name = "John";
Score = 0;
},
{
Name = "Mark";
Score = 2000;
},
{
Name = "Steve";
Score = 300;
},
{
Name = "Ricky";
Score = 900;
}
)
and i can access its objects as
(json[0] as! NSDictionary).object(forKey: "Name")
(json[0] as! NSDictionary).object(forKey: "Score")
I want to sort this JSON array according to scores.
I found the answers like
let sortedArray = json.sorted(by: { $0.0 < $1.0 })
which gives error
Value of type 'Any' has no member '0'
Then I tried this
let sortedArray = (json as! NSDictionary).sorted {(aDic, bDic) -> Bool in
return aDic.key < bDic.key
}
It gave error
Binary operator '<' cannot be applied to two 'Any' operands
Can you please guide me to sort the array according to score in swift 4?

That's a very good example why you are strongly discouraged from using NSArray and NSDictionary in Swift.
Both collection types don't provide type information so everything is treated as Any. Most of the shared generic API of the Swift Standard library cannot be used with Any so you are not able to take advantage of the powerful generic functions unless you add a lot of ugly type casts.
If all values are String declare your array as
var json = [[String:String]]()
Then you can sort the array with
let sortedArray = json.sorted { $0["Score"]! < $1["Score"]! }
The most recommended solution is to decode the JSON directly into a custom struct
struct Player : Decodable {
let name : String
let score : String
private enum CodingKeys : String, CodingKey { case name = "Name", score = "Score" }
}
Then you get rid of all type casting and you can sort by the property name
var players = [Player]()
let jsonString = """
[{"Name" : "Alen", "Score" : "500"},
{"Name" : "John", "Score" : "0"},
{"Name" : "Mark", "Score" : "2000"},
{"Name" : "Steve", "Score" : "300"},
{"Name" : "Ricky", "Score" : "900"}]
"""
let data = Data(jsonString.utf8)
do {
players = try JSONDecoder().decode([Player].self, from: data)
let sortedPlayers = players.sorted{ $0.score.compare($1.score, options: .numeric) == .orderedAscending }
print(sortedPlayers)
} catch { print(error) }
Edit:
To load the JSON use an asynchronous way (URLSession)
Never load data from a remote URL with synchronous Data(contentsOf.
var players = [Player]()
let jsonUrl = URL(string: "url.json")!
let task = URLSession.shared.dataTask(with : url) { [unowned self] (data, _, error) in
if let error = error { print(error); return }
do {
players = try JSONDecoder().decode([Player].self, from: data!).sorted{ $0.score < $1.score }
DispatchQueue.main.async { // reload the table view if necessary
self.tableView.reloadData()
}
} catch { print(error) }
}
task.resume()

After parsing your json, you can sort your score array like this
var scoreArray = ["500", "0", "2000", "300", "900"]
array.sort { $0.compare($1, options: .numeric) == .orderedAscending }

I did something like this before
First I created two arrays of dictionary
var jsonArray = [(name:String, score:String)]()
var sortedscoreArray:[(name: String, score: String)] = []
and in getting json data you can create for loop
for I in 0..< jsonData.count{
Let jsonInfo = jsonData[i]
jsonArray.append((name: jsonInfo[“Name”].string!, score: jsonInfo[“Score"].string!))
}
and after you fill the json array pass it to sortedArray
sortedscoreArray = jsonArray.sorted(by: { $0.score < $1.score })

If array contains dictionary then you can use this code for sorting:
let sortedArray = json.sort { $0["Score"] as! Int < $1["Score"] as! Int }
print(sortedArray)
and if you are using bean class then you can use dot(.) properties for sorting:
let sortedArray = json.sort { $0.Score < $1.Score }
print(sortedArray)

let sortedResults = self.json?.sorted(by: {$0.name ?? EMPTY_STRING < $1.name ?? EMPTY_STRING }) ?? []

Related

Recursively change some keys in a Dictionary in Swift

I have a dictionary
var dictionary: Any = ["det" : ["val" : "some","result" : ["key1" : "val1","key2" : "val2"],"key3" :["val1", "val2"]]]
and a mapping function below
func getMappedKey(_ key: String) -> String? {
var mapping: Dictionary = [
"key1" : "key3",
"key2" : "key4",
"det" : "data"
]
return mapping[key]
}
Now I want to change some keys in the same dictionary using the mapping function above. So after the change, the dictionary should look like
["data" : ["val" : "some","result" : ["key3" : "val1","key4" : "val2"],"key3" :["val1", "val2"]]]
So for that I wrote a function below
func translatePayload(_ payload: inout Any) {
if let _ = payload as? String {
return
} else if var payload = payload as? Dictionary<String, Any> {
for (key, value) in payload {
if let newKey = getMappedKey(key) {
if let _ = payload.removeValue(forKey: key) {
payload[newKey] = value
}
}
var nextPayload = value
translatePayload(&nextPayload)
}
} else if let payload = payload as? Array<Any> {
for value in payload {
var nextPayload = value
translatePayload(&nextPayload)
}
}
}
and when I call the function
translatePayload(&dictionary)
print(dictionary)
it does not change the keys in the same dictionary. Can someone please point out what is wrong with this code. Thanks in advance
Your code is perfectly fine, you just updated the local variable instead of the parametric one because you used the same name. Just change the local variable payload to dictionary and array or anything else you like.
Here is the final code:
func translatePayload(_ payload: inout Any) {
if let _ = payload as? String {
return
} else if var dictionary = payload as? Dictionary<String, Any> { // Here dictionary instead of payload
for (key, value) in dictionary {
var nextPayload = value
translatePayload(&nextPayload)
if let newKey = getMappedKey(key) {
if let _ = dictionary.removeValue(forKey: key) {
dictionary[newKey] = nextPayload
}
} else {
dictionary[key] = nextPayload
}
}
payload = dictionary
} else if let array = payload as? Array<Any> { // Here array instead of payload
var updatedArray = array
for (index, value) in array.enumerated() {
var nextPayload = value
translatePayload(&nextPayload)
updatedArray[index] = nextPayload
}
payload = updatedArray // Assign the new changes
}
}
translatePayload(&dictionary)
print(dictionary)
Not really a direct answer to the question "what's wrong", but I'd go with something like:
let dictionary = ["det" : ["val" : "some","result" : ["key1" : "val1", "key2" : "val2"],"key3" :["val1", "val2"]]]
func getMapped(key: String) -> String {
var mapping: Dictionary = [
"key1" : "key3",
"key2" : "key4",
"det" : "data"
]
return mapping[key] ?? key
}
func translate(payload:Any, map:(String)->String) -> Any {
switch payload {
case let value as String:
return value
case let value as [String:Any]:
return value.reduce(into:[String:Any]()) {
$0[map($1.0)] = translate(payload: $1.1, map:map)
}
case let value as [Any]:
return value.map { translate(payload: $0, map:map) }
default:
fatalError("Unknown data type")
}
}
let output = translate(payload: dictionary, map:getMapped(key:))
To really take advantage of the functional spirit of Swift.

Multiple Realm objects to JSON

I am trying to convert Realm Object into JSON. My version is working but not if you want to put multiple objects into JSON. So my question is, how should you add multiple Realm Objects into JSON?
Something like that:
{
"Users": [
{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}
],
"Posts": [
{"id": "1","title": "hey"},{"id": "2","title": "hey2"},{"id": "3","title": "hey3"}
]
}
This is what I am doing right now:
func getRealmJSON(name: String, realmObject: Object, realmType: Any) -> String {
do {
let realm = try Realm()
let table = realm.objects(realmType as! Object.Type)
if table.count == 0 {return "Empty Table"}
let mirrored_object = Mirror(reflecting: realmObject)
var properties = [String]()
for (_, attr) in mirrored_object.children.enumerated() {
if let property_name = attr.label as String! {
properties.append(property_name)
}
}
var jsonObject = "{\"\(name)\": ["
for i in 1...table.count {
var str = "{"
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 1])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)},"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))]}"
return jsonObject
}catch let error { print("\(error)") }
return "Problem reading Realm"
}
Above function does like that, which is good for only one object:
{"Users": [{"id": "1","name": "John"},{"id": "2","name": "John2"},{"id": "3","name": "John3"}]}
Like this I call it out:
let users = getRealmJSON(name: "Users", realmObject: Users(), realmType: Users.self)
let posts = getRealmJSON(name: "Posts", realmObject: Posts(), realmType: Posts.self)
And I tried to attach them.
Can anybody please lead me to the right track?
You can use data models to encode/decode your db data:
For example you have
class UserEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var createdAt: Date = Date()
#objc private dynamic var addressEntities = List<AddressEntity>()
var addresses: [Address] {
get {
return addressEntities.map { Address(entity: $0) }
}
set {
addressEntities.removeAll()
let newEntities = newValue.map { AddressEntity(address: $0) }
addressEntities.append(objectsIn: newEntities)
}
}
}
Here you hide addressEntities with private and declare addresses var with Address struct type to map entities into proper values;
And then use
struct User: Codable {
let id: String
let createdAt: Date
let addresses: [Address]
}
And then encode User struct any way you want

Swift 4: Find value in nested, dynamic Dictionary<String, Any> recursively

In a given Dictionary, I need to find a nested Dictionary ([String : Any]) for a given key.
The general structure of the Dictionary (e.g. nesting levels, value types) is unknown and given dynamically. [1]
Inside of this sub-Dictionary, there is a given value for the key "value" (don't ask) which needs to be fetched.
Here's an example:
let theDictionary: [String : Any] =
[ "rootKey" :
[ "child1Key" : "child1Value",
"child2Key" : "child2Value",
"child3Key" :
[ "child3SubChild1Key" : "child3SubChild1Value",
"child3SubChild2Key" :
[ "comment" : "child3SubChild2Comment",
"value" : "child3SubChild2Value" ]
],
"child4Key" :
[ "child4SubChild1Key" : "child4SubChild1Value",
"child4SubChild2Key" : "child4SubChild2Value",
"child4SubChild3Key" :
[ "child4SubChild3SubChild1Key" :
[ "value" : "child4SubChild3SubChild1Value",
"comment" : "child4SubChild3SubChild1Comment" ]
]
]
]
]
With brute force and pseudo memoization, I managed to hack a function together that iterates through the entire Dictionary and fetches the value for a given key:
func dictionaryFind(_ needle: String, searchDictionary: Dictionary<String, Any>) -> String? {
var theNeedleDictionary = Dictionary<String, Any>()
func recurseDictionary(_ needle: String, theDictionary: Dictionary<String, Any>) -> Dictionary<String, Any> {
var returnValue = Dictionary<String, Any>()
for (key, value) in theDictionary {
if value is Dictionary<String, Any> {
if key == needle {
returnValue = value as! Dictionary<String, Any>
theNeedleDictionary = returnValue
break
} else {
returnValue = recurseDictionary(needle, theDictionary: value as! Dictionary<String, Any>)
}
}
}
return returnValue
}
// Result not used
_ = recurseDictionary(needle, theDictionary: searchDictionary)
if let value = theNeedleDictionary["value"] as? String {
return value
}
return nil
}
This works so far. (For your playground testing pleasure:
let theResult1 = dictionaryFind("child3SubChild2Key", searchDictionary: theDictionary)
print("And the result for child3SubChild2Key is: \(String(describing: theResult1!))")
let theResult2 = dictionaryFind("child4SubChild3SubChild1Key", searchDictionary: theDictionary)
print("And the result for child4SubChild3SubChild1Key is: \(String(describing: theResult2!))")
let theResult3 = dictionaryFind("child4Key", searchDictionary: theDictionary)
print("And the result for child4Key is: \(String(describing: theResult3))")
).
My question here:
What would be a more clean, concise, "swifty", way to iterate through the Dictionary and - especially - break completely out of the routine as soon the needed key has been found?
Could a solution even be achieved using a Dictionary extension?
Thanks all!
[1] A KeyPath as described in Remove nested key from dictionary therefor isn't feasible.
A more compact recursive solution might be:
func search(key:String, in dict:[String:Any], completion:((Any) -> ())) {
if let foundValue = dict[key] {
completion(foundValue)
} else {
dict.values.enumerated().forEach {
if let innerDict = $0.element as? [String:Any] {
search(key: key, in: innerDict, completion: completion)
}
}
}
}
the usage is:
search(key: "child3SubChild2Key", in: theDictionary, completion: { print($0) })
which gives:
["comment": "child3SubChild2Comment", "value": "child3SubChild2Subchild1Value"]
alternatively, if you don't want to use closures, you might use the following:
extension Dictionary {
func search(key:String, in dict:[String:Any] = [:]) -> Any? {
guard var currDict = self as? [String : Any] else { return nil }
currDict = !dict.isEmpty ? dict : currDict
if let foundValue = currDict[key] {
return foundValue
} else {
for val in currDict.values {
if let innerDict = val as? [String:Any], let result = search(key: key, in: innerDict) {
return result
}
}
return nil
}
}
}
usage is:
let result = theDictionary.search(key: "child4SubChild3SubChild1Key")
print(result) // ["comment": "child4SubChild3SubChild1Comment", "value": "child4SubChild3SubChild1Value"]
The following extension can be used for finding values of a key in nested dictionaries, where different levels each can contain the same key associated with a different value.
extension Dictionary where Key==String {
func find<T>(_ key: String) -> [T] {
var keys: [T] = []
if let value = self[key] as? T {
keys.append(value)
}
self.values.compactMap({ $0 as? [String:Any] }).forEach({
keys.append(contentsOf: $0.find(key))
})
return keys
}
}

Swift: Filter a Dictionary with Array as Value

I'm new to Swift programming. For my particular project, I'm trying to filter a dictionary with some user input, and the dictionary's value consists of an array.
Here is some sample code, and what I'm trying to accomplish:
var dictionary = ["a": ["aberration", "abc"], "b" : ["babel", "bereft"]]
var filteredDictionary = [String: [String]]()
var searchText = "aberration"
//getting the first letter of string
var firstLetter = searchText[searchText.startIndex]
With this particular searchText, I'm trying to get:
filteredDictionary = ["a": ["aberration"]]
Edit: I want the dictionary to return with the first letter as its key, and the values with what searchText matches up with. Sorry if it I wasn't clear.
Here is some code I have tried, but obviously, I can't get it to work:
filteredDictionary = dictionary.filter{$0.key == firstLetter && for element in $0.value { element.hasPrefix(searchText) }}
Any help would be appreciated. Thanks.
Here's a solution that maps the values based on the search and then filters out the empty results.
var dictionary = ["a": ["aberration", "abc"], "b" : ["babel", "bereft"]]
var searchText = "aberration"
let filteredDictionary = dictionary.mapValues { $0.filter { $0.hasPrefix(searchText) } }.filter { !$0.value.isEmpty }
print(filteredDictionary)
Output:
["a": ["aberration"]]
Try this:
var dictionary = ["a": ["aberration", "abc"], "b" : ["babel", "bereft"]]
var searchText = "aberration"
var filteredDictionary = dictionary.filter { (key, value) -> Bool in
return (value as! [String]).contains(searchText)
}.mapValues { (values) -> [String] in
return [searchText]
}
print(filteredDictionary)
You can use a combination of filter and map to achieve the desired result.
Output:
["a": ["aberration"]]
let firstLetter = String(searchText[searchText.startIndex])
let filteredDictionary = dictionary
.reduce(into: [String: [String]]()) { (result, object) in
if object.key == firstLetter {
let array = object.value.filter({ $0.hasPrefix(searchText) })
if array.count > 0 {
result[object.key] = array
}
}
}
Output:
["a": ["aberration"]]

Swift 3 which is the best way to store of an Object with array etc

I started to develop with Swift 3 and i´m getting crazy. Following Situation:
class subObject
{
var name : String
var list : [Int]
init( _name : String, _list : [Int] ){
self.name = _name
self.list = _list
}
}
class mainObject
{
var subObjectList : [subObject]
init( _list : [subObject] ){
self.subObjectList = _list
}
}
var data : [mainObject]
Which way is state of the art to store var data : [mainObject] persistently. I've already unsuccessfully tried .plistand NSKeyedArchiver.
Sorry but my english is worse.
NSCoding cannot be used because the classes aren't subclasses of NSObject.
Since all properties in both classes are property list compliant you could add a computed property propertyListRepresentation and an appropriate initializer.
Class names are supposed to start with a capital letter and parameters starting with an underscore are unusual in Swift.
class SubObject
{
var name : String
var list : [Int]
init(name : String, list : [Int] ){
self.name = name
self.list = list
}
init?(dictionary : [String:Any]) {
guard let name = dictionary["name"] as? String,
let list = dictionary["list"] as? [Int] else { return nil }
self.name = name
self.list = list
}
var propertyListRepresentation : [String:Any] {
return ["name" : name, "list" : list]
}
}
class MainObject
{
var subObjectList : [SubObject]
init(list : [SubObject] ){
self.subObjectList = list
}
init(propertyList : [[String:Any]] ){
self.subObjectList = propertyList.flatMap{ SubObject(dictionary: $0) }
}
var propertyListRepresentation : [[String:Any]] {
return subObjectList.map{ $0.propertyListRepresentation }
}
}
To use it:
let subs = [SubObject(name: "Foo", list: [1, 2, 3]), SubObject(name: "Bar", list: [4, 5, 6])]
let main = MainObject(list: subs)
let list = main.propertyListRepresentation
let data = try! PropertyListSerialization.data(fromPropertyList: list, format: .xml, options: 0)
print(String(data:data, encoding: .utf8)!)
let restoredList = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [[String:Any]]
let restoredMain = MainObject(propertyList: restoredList)