Swift 3 - Compare key values from two different dictionaries and update one of them if key value matches - swift

I have two arrays of dictionaries:
Dict 1 =
[{"id":"100", "name":"Matt", "phone":"0404040404", "address":"TBC"}
,{"id":"110", "name":"Sean", "phone":"0404040404", "address":"TBC"}
, {"id":"120", "name":"Luke", "phone":"0404040404", "address":"TBC"}]
Dict 2 =
[{"id":"100", "address":"1 Main Street"}
,{"id":"110", "address":"2 Main Road"}
, {"id":"120", "address":"3 Main Street"}]
I want to compare the key:value pair, id , of each dictionary in Dict 2 against Dict 1, and if the id matches, update the corresponding address in Dict 1 from the value in Dict2.
So the desired output should be:
Dict 1 =
[{"id":"100", "name":"Matt", "phone":"0404040404", "address":"1 Main Street"}
,{"id":"110", "name":"Sean", "phone":"0404040404", "address":"2 Main Road"}
, {"id":"120", "name":"Luke", "phone":"0404040404", "address":"3 Main Street"}]
EDIT
As requested, here is more information regarding how I am parsing the data. I am getting Dict1 and Dict2 as response to HTTP URL call btw. And also, I use dictionaries of the type [Dictionary] while parsing.
let Task1 = URLSession.shared.dataTask(with: URL!) { (Data, response, error) in
if error != nil {
print(error)
} else {
if let DataContent = Data {
do {
let JSONresponse1 = try JSONSerialization.jsonObject(with: DataContent, options: JSONSerialization.ReadingOptions.mutableContainers)
print(JSONresponse1)
for item in JSONresponse1 as! [Dictionary<String, Any>] {
//Parse here
}
}
catch { }
DispatchQueue.main.async(execute: {
self.getAddressTask()
})
}
}
}
Task1.resume()
}
JSONResponse1 is Dict 1
Then inside the getAddressTask() func called above, I do the HTTP URL call to get Dict 2
let AddressTask = URLSession.shared.dataTask(with: URL2!) { (Data, response, error) in
if error != nil {
print(error)
} else {
if let DataContent = Data {
do {
let JSONresponse2 = try JSONSerialization.jsonObject(with: timeRestrictionsDataContent, options: JSONSerialization.ReadingOptions.mutableContainers)
print(JSONresponse2)
for item in JSONresponse2 as! [Dictionary<String, Any>] {
//Parse here
}
catch { }
self.compileDictionaries()
}
}
}
AddressTask.resume()
JSONResponse2 is Dict2
Inside compileDictionaries() i would like to get the desired output as shown above.

You should struct your data using Codable protocol and create a mutating method to update your contact. If you need an array of your contacts once you have them updated all you need is to encode your contacts using JSONEncoder:
struct Contact: Codable, CustomStringConvertible {
let id: String
var address: String?
var name: String?
var phone: String?
mutating func update(with contact: Contact) {
address = contact.address ?? address
name = contact.name ?? name
phone = contact.phone ?? phone
}
var description: String {
return "ID: \(id)\nName: \(name ?? "")\nPhone: \(phone ?? "")\nAddress: \(address ?? "")\n"
}
}
Playground testing:
let json1 = """
[{"id":"100", "name":"Matt", "phone":"0404040404", "address":"TBC"},
{"id":"110", "name":"Sean", "phone":"0404040404", "address":"TBC"},
{"id":"120", "name":"Luke", "phone":"0404040404", "address":"TBC"}]
"""
let json2 = """
[{"id":"100", "address":"1 Main Street"},
{"id":"110", "address":"2 Main Road"},
{"id":"120", "address":"3 Main Street"}]
"""
var contacts: [Contact] = []
var updates: [Contact] = []
do {
contacts = try JSONDecoder().decode([Contact].self, from: Data(json1.utf8))
updates = try JSONDecoder().decode([Contact].self, from: Data(json2.utf8))
for contact in updates {
if let index = contacts.index(where: {$0.id == contact.id}) {
contacts[index].update(with: contact)
} else {
contacts.append(contact)
}
}
let updatedJSON = try JSONEncoder().encode(contacts)
print(String(data: updatedJSON, encoding: .utf8) ?? "")
} catch {
print(error)
}
This will print:
[{"id":"100","phone":"0404040404","name":"Matt","address":"1 Main
Street"},{"id":"110","phone":"0404040404","name":"Sean","address":"2
Main
Road"},{"id":"120","phone":"0404040404","name":"Luke","address":"3
Main Street"}]

Related

Decode json object in Swift

I have a json file and reading it and decode it as follows.
my json file looks like as follows, A and B represents the struct object. I wonder are there a better and effective way to decoding this type of json?
[
[
{"id": "152478", "age": "20"},{"character": "king", "isDead":"no", "canMove" :"yes"}
],
[
{"id": "887541", "age": "22"},{"character": "lion", "isDead":"no", "canMove" :"yes"}
]
]
decoding is as follows:
let url = Bundle.main.url(forResource: "mypew", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
// B -> json[0][0], [1][0]
// A -> json[0][1], [0][1]
response = try JSONSerialization.jsonObject(with: jsonData) as! [[[String: Any]]]
for i in 0..<response.count
{
for j in 0..<response[i].count
{
if j == 0
{
let jsonDat = (try? JSONSerialization.data(withJSONObject:response[i][j]))!
let bElement = try JSONDecoder().decode(B.self, from: jsonDat)
self.bArray.append(bElement)
}
else if j == 1
{
let jsonDatt = (try? JSONSerialization.data(withJSONObject:response[i][j]))!
let aElement = try JSONDecoder().decode(A.self, from: jsonDatt)
self.aArray.append(aElement)
}
}
}
First, make the two objects (let's call them A and B) conform to Decodable:
struct A: Decodable {
var id, age: String
}
struct B: Decodable {
var character, isDead, canMove: String
}
Then, if your structure for the pair of A and B is always the same, i.e. it's always [A, B], then you can decode the pair into its own object. Let's call that object ABPair:
struct ABPair: Decodable {
let a: A
let b: B
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
guard let a = try? container.decode(A.self),
let b = try? container.decode(B.self)
else {
// throw since we didn't find A first, followed by B
throw DecodingError.dataCorruptedError(in: container, debugDescription: "error")
}
self.a = a
self.b = b
}
}
And you can decode the array of pairs as follows:
let decoder = JSONDecoder()
let pairs = try? decoder.decode([ABPair].self, from: jsonData)
print(pairs[1].b.character) // lion
Firstly, you need to create a Codable struct for the model. Then decode the model from the data and not the JSON object. Here the code,
Model:
struct Model: Codable {
let id, age, character, isDead, canMove: String?
}
Decoding:
let url = Bundle.main.url(forResource: "mypew", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
let models = try JSONDecoder().decode([[Model]].self, from: jsonData)
for model in models {
print(model[0].age, model[1].canMove)
}
} catch {
print(error)
}

swift json parsing, no titles for json objects

So I got a pretty hard problem to tackle. My JSON code has a pretty weird structure. It has the following structure:
{
"title": [
[
"Temperature",
"9 \u00b0C (283 \u00b0F)",
"Good"
],
[
"Visibility",
"10 KM (6.2 Mi)",
"Good"
]
]
}
With the following code I was able to print out some easy json code:
import UIKit
struct WeatherItem: Decodable {
let title: String?
let value: String?
let condition: String?
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let jsonUrlString = "http://somelinkhere"
guard let url = URL(string: jsonUrlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let weather = try JSONDecoder().decode(WeatherItem.self, from: data)
print(weather.title)
} catch let jsonErr{
print("error", jsonErr)
}
}.resume()
}
}
But the problem is that my output for all 3 variables, title, value and condition nil is.
I am sure I have to change the struct code, but I don't know in what way.
How do I get to JSON code with no title?
You will have to write the decoding initializer by yourself:
struct WeatherData: Decodable {
let title: [WeatherItem]
}
struct WeatherItem: Decodable {
let title: String?
let value: String?
let condition: String?
public init(from decoder: Decoder) throws {
// decode the value for WeatherItem as [String]
let container = try decoder.singleValueContainer()
let components = try container.decode([String].self)
title = components.count > 0 ? components[0] : nil
value = components.count > 1 ? components[1] : nil
condition = components.count > 2 ? components[2] : nil
}
}
let json = """
{
"title": [
["Temperature", "9", "Good"],
["Visibility", "10 KM (6.2 Mi)", "Good"]
]
}
"""
let jsonData: Data = json.data(using: .utf8)!
let decoder = JSONDecoder()
let decoded = try! decoder.decode(WeatherData.self, from: jsonData)
debugPrint(decoded)
Correct json
{
"title": [
[
"Temperature",
" ",
"Good"
],
[
"Visibility",
"10 KM (6.2 Mi)",
"Good"
]
]
}
var arr = [WeatherItem]()
do {
let res = try JSONDecoder().decode([String:[[String]]].self, from: data)
let content = res["title"]!
content.forEach {
if $0.count >= 3 {
arr.append(WeatherItem(title:$0[0],value:$0[1],condition:$0[2]))
}
}
print(arr)
} catch {
print(error)
}
Discussion : your root object is a dictionary that contains 1 key named title and it's value is an array of array of strings or from the model logic it's an array of model named WeatherItem but isn't structured properly for it , so using this
let weather = try JSONDecoder().decode(WeatherItem.self, from: data)
won't work as the current json does't contain keys value and condition
A proper strcuture would be
[
{
"title":"Temperature" ,
"value":"",
"condition":"Good"
},
{
"title":"Visibility",
"title":"10 KM (6.2 Mi)",
"condition":"Good"
}
]
and that will enable you to do
let weather = try JSONDecoder().decode([WeatherItem].self, from: data)

UTF-8 encoding issue of JSONSerialization

I was trying convert struct to Dictionary in Swift. This was my code:
extension Encodable {
var dictionary: [String: Any]? {
if let data = try? JSONEncoder().encode(self) {
if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
return dict
}
return nil
}
return nil
}
}
This works in most situation. But when I try to convert a nested structure which contains unicode characters such as Chinese, this happened:
struct PersonModel: Codable {
var job: String?
var contacts: [ContactSimpleModel]
var manager: ManagerSimpleModel?
}
struct ContactSimpleModel: Codable {
var relation: String
var name: String
}
struct ManagerSimpleModel: Codable {
var name: String
var age: Int
}
let contact1 = ContactSimpleModel(relation: "朋友", name: "宙斯")
let contact2 = ContactSimpleModel(relation: "同学", name: "奥丁")
let manager = ManagerSimpleModel(name: "拉斐尔", age: 31)
let job = "火枪手"
let person = PersonModel(job: job, contacts: [contact1, contact2], manager: manager)
if let dict = person.dictionary {
print(dict)
}
The result of this code is this:
["contacts": <__NSArrayI 0x600002471980>(
{
name = "\U5b99\U65af";
relation = "\U670b\U53cb";
},
{
name = "\U5965\U4e01";
relation = "\U540c\U5b66";
}
)
, "manager": {
age = 31;
name = "\U62c9\U6590\U5c14";
}, "job": 火枪手]
You can see the result. The Chinese characters in those nested structures were become a utf-8 encoding string. The top-level property "job": 火枪手 is right. But the values in those nested structures were not the original string.
Is this a bug of JSONSerialization? Or how to make it right?
More information. I used the result like this:
var sortedQuery = ""
if let dict = person.dictionary {
sortedQuery = dict.sorted(by: {$0.0 < $1.0})
.map({ "\($0)\($1)" })
.joined(separator: "")
}
It was used to check whether the query was legal. The result is not the same as Java or other platform.
The result is perfectly fine. That's the internal string representation – a pre-Unicode legacy – of an array or dictionary when you print it.
Assign the values to a label or text view and you will see the expected characters.

Getting value of children with same name - Firebase Swift3

I'm using the following query to get the snapshot below.
dbRef.child("users").queryOrdered(byChild: "username").queryStarting(atValue: text).queryEnding(atValue: text+"\u{f8ff}").observe(.value, with: { snapshot in })
But I'm having trouble figuring out how to iterate over the values of the children "username" (i.e. bill, billy, billykins) because they all have the same key ("username"). I'm trying to extract all the names and put them in an array of names.
Snap (users) {
fDrzmwRUt2guh3O8pc792ipyEqA3 = {
username = bill;
};
qyQkIOxSpXgQoUoh0QNvPlkQ1Mp1 = {
username = billy;
};
edSk54xSpXgQoYoh0QNvOlkQ1Mp3 = {
username = billykins;
};
}
This is my function which gets the snapshot and tries to iterate over it. However, the loop crashes:
func findUsers(text: String) {
print("search triggered")
dbRef.child("users").queryOrdered(byChild: "username").queryStarting(atValue: text).queryEnding(atValue: text+"\u{f8ff}").observe(.value, with: { snapshot in
print(snapshot)
for i in snapshot.children{
let value = snapshot.value as! Dictionary<String,String>
let username = value["username"]
print(username)
self.userList.append(username!)
print(self.userList)
}
}) { (error) in
print("searchUsers \(error.localizedDescription)")
}
}
Here's a fraction of the log. Too long to post:
0x1002cg360 <+272>: adr x9, #268929 ; "value type is not bridged to Objective-C"
0x1002cg364 <+276>: nop
0x1002cg368 <+280>: str x9, [x8, #8]
-> 0x1002cg36c <+284>: brk #0x1
0x1002cg370 <+288>: .long 0xffffffe8 ; unknown opcode
Here's the solution I was looking for. I needed to loop through the big snapshot to extract the child snapshots into an array, and then loop through the child snapshots by value to get the usernames:
func findUsers(text: String) {
print("search triggered")
dbRef.child("users").queryOrdered(byChild: "username").queryStarting(atValue: text).queryEnding(atValue: text+"\u{f8ff}").observe(.value, with: { snapshot in
print(snapshot)
if ( snapshot.value is NSNull ) {
print("not found")
} else {
var blaArray = [FIRDataSnapshot]()
for child in snapshot.children {
let snap = child as! FIRDataSnapshot //downcast
blaArray.append(snap)
}
for child in blaArray {
let dict = child.value as! NSDictionary
let username = dict["username"]!
print(username)
}
}) { (error) in
print("searchUsers \(error.localizedDescription)")
}
}
Adapted from this post: Get data out of array of Firebase snapshots

How to use swiftyjson to parse json data to foundation object and loop through data

So I'm trying to get his raw json data and use it to ultimately be viewed in a table(so one table cell would be --> Emirates - $1588.77)
Problem: Having trouble parsing the JSON data.. alamofire apparently does it automatically? but im completely confused with the data types. I keep getting weird errors like 'doesnt have a member named subscript" (I've also got swiftyjson installed but aa non-swiftyjson solution should work as well.
Code:
request(qpxRequest).responseJSON { (request, response, json, error) -> Void in
if response != nil {
//println(response!)
}
if json != nil {
// 1. parse the JSON data into a Foundation object
// 2. Grab the data from the foundation object (so its can be looped though in a table)
}
{
trips = {
data = {
carrier = (
{
name = "Cathay Pacific Airways Ltd.";
},
{
name = Emirates;
},
{
name = "Ethiopian Airlines Enterprise";
},
{
name = "Qantas Airways Ltd.";
},
{
name = "South African Airways";
}
);
};
tripOption = (
{
saleTotal = "AUD1537.22";
},
{
saleTotal = "AUD1588.77";
},
{
saleTotal = "AUD1857.42";
},
{
saleTotal = "AUD1857.42";
},
{
saleTotal = "AUD1922.42";
}
);
};
}
-------- Edit.
Using this model.
class FlightDataModel {
var carrier: String
var price: String
init(carrier: String?, price: String?) {
self.carrier = carrier!
self.price = price!
}
}
How woudl I use your solution to add it to an array of FlightDataModel class
This my my attempt..
var arrayOfFlights : [FlightDataModel] = [FlightDataModel]()
if let tripOptions = trips["tripOption"] as? [[String:AnyObject]] {
for (index, tripOption) in enumerate(tripOptions) {
//println("\(index): " + (tripOption["saleTotal"]! as String))
self.arrayOfFlights[index].carrier = tripOption["saleTotal"]! as String
println("\(self.arrayOfFlights[index].carrier)")
}
Alamofire can do it, but you have to dig into your JSON structure. :)
Like this, using Alamofire's responseJSON method:
Alamofire.request(.GET, YOUR_URL, parameters: nil, encoding: .URL).responseJSON(options: NSJSONReadingOptions.allZeros) { (request, response, json, error) -> Void in
if let myJSON = json as? [String:AnyObject] {
if let trips = myJSON["trips"] as? [String:AnyObject] {
if let data = trips["data"] as? [String:AnyObject] {
if let carriers = data["carrier"] as? [[String:String]] {
for (index, carrierName) in enumerate(carriers) {
println("\(index): " + carrierName["name"]!)
}
}
}
if let tripOptions = trips["tripOption"] as? [[String:AnyObject]] {
for (index, tripOption) in enumerate(tripOptions) {
println("\(index): " + (tripOption["saleTotal"]! as! String))
}
}
}
}
}
Output:
0: Cathay Pacific Airways Ltd.
1: Emirates
...
0: AUD1537.22
1: AUD1588.77
...
It's a bit easier with SwiftyJSON indeed. And for diversity's sake, we'll use Alamofire's responseString method this time:
Alamofire.request(.GET, YOUR_URL, parameters: nil, encoding: .URL).responseString(encoding: NSUTF8StringEncoding, completionHandler: {(request: NSURLRequest, response: NSHTTPURLResponse?, responseBody: String?, error: NSError?) -> Void in
if let dataFromString = responseBody!.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
let carriers = json["trips"]["data"]["carrier"].array
for (index, carrier) in enumerate(carriers!) {
println("\(index):" + carrier["name"].string!)
}
let tripOption = json["trips"]["tripOption"].array
for (index, option) in enumerate(tripOption!) {
println("\(index):" + option["saleTotal"].string!)
}
}
})
Output:
0: Cathay Pacific Airways Ltd.
1: Emirates
...
0: AUD1537.22
1: AUD1588.77
...
Note: I've used enumerate as an example for how getting the index of the content at the same time you get the content.