My project includes dynamic data, that changes when controllers change etc, so at one point my data may be:
[
{
"callDescription":"TEST 16/12",
"callDuration":"5-8 Minutes",
"callID":0,
"callMade":false,
"callMade_dateTime":"false_1608151560.0",
"dateTime":1608044666,
"type":"Breakfast Call"
},
{
"callDescription":"TEST 16/12",
"callDuration":"5-8 Minutes",
"callID":0,
"callMade":false,
"callMade_dateTime":"false_1608151560.0",
"dateTime":1608044666,
"type":"Breakfast Call"
},
]
Then a piece of code is ran and my data is now
[
{
"callDescription":"TEST 16/12",
"callDuration":"5-8 Minutes",
"callID":0,
"callMade":false,
"callMade_dateTime":"false_1608151560.0",
"dateTime":1608044666,
"type":"Breakfast Call"
},
null
]
Which is causing the valueNotFound error when the data is requested again.
What is the best way to skip/handle any indexes that are null?
Here is my API code:
class Service {
static let shared = Service()
let BASE_URL = "https://url.com"
func fetchClient(completion: #escaping ([Calls]) -> ()) {
guard let url = URL(string: BASE_URL) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
print("Failed to fetch data with error: ", error)
return
}
guard let data = data else {return}
do {
let myDecoder = JSONDecoder()
let calls = try myDecoder.decode([Calls].self, from: data)
completion(calls)
} catch let error {
print("Failed to create JSON with error: ", error)
}
}.resume()
}
Calls model:
struct Calls: Decodable {
let callDescription, callDuration, callMade_dateTime: String
let callID: Int
let dateTime: Date
let callMade: Bool
let type: String
}
Quick solution:
let calls = try myDecoder.decode([Calls].self, from: data)
completion(calls)
=>
let calls = try myDecoder.decode([Calls?].self, from: data)
completion(calls.compactMap{ $0 })
Let's simplify the example (I started writing the answer before you wrote a real JSON working) :
struct Custom: Codable {
let data: String
}
let jsonString = """
[{"data": "Hello"}, {"data": "world"}]
"""
let jsonString2 = """
[{"data": "Hello"}, null, {"data": "world"}]
"""
So, some values might be null inside your JSON. That's where we can use Optional.
func test(json: String) {
do {
print("Testing with [Custom].self: \(json)")
let customs = try JSONDecoder().decode([Custom].self, from: json.data(using: .utf8)!)
print("Result: \(customs)")
} catch {
print("Error: \(error)")
}
}
func test2(json: String) {
do {
print("Testing with [Custom?].self: \(json)")
let customs = try JSONDecoder().decode([Custom?].self, from: json.data(using: .utf8)!)
print("Result with optionals: \(customs)")
let unwrapped = customs.compactMap { $0 }
print("Result unwrapped: \(unwrapped)")
} catch {
print("Error: \(error)")
}
}
test(json: jsonString)
test(json: jsonString2)
test2(json: jsonString)
test2(json: jsonString2)
Output:
$>Testing with [Custom].self: [{"data": "Hello"}, {"data": "world"}]
$>Result: [Custom(data: "Hello"), .Custom(data: "world")]
$>Testing with [Custom].self: [{"data": "Hello"}, null, {"data": "world"}]
$>Error: valueNotFound(Swift.KeyedDecodingContainer<.Custom.CodingKeys>, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 1", intValue: 1)], debugDescription: "Cannot get keyed decoding container -- found null value instead.", underlyingError: nil))
$>Testing with [Custom?].self: [{"data": "Hello"}, {"data": "world"}]
$>Result with optionals: [Optional(.Custom(data: "Hello")), Optional(.Custom(data: "world"))]
$>Result unwrapped: [.Custom(data: "Hello"), .Custom(data: "world")]
$>Testing with [Custom?].self: [{"data": "Hello"}, null, {"data": "world"}]
$>Result with optionals: [Optional(.Custom(data: "Hello")), nil, Optional(.Custom(data: "world"))]
$>Result unwrapped: [.Custom(data: "Hello"), .Custom(data: "world")]
Related
I have prepared a simple test Playground at Github to demo my problem:
My Swift code:
struct TopResponse: Codable {
let results: [Top]
}
struct Top: Codable {
let uid: Int
let elo: Int
let given: String
let photo: String?
let motto: String?
let avg_score: String?
let avg_time: String?
}
let url = URL(string: "https://slova.de/ws/top")!
let task = URLSession.shared.dataTask(with: url) {
data, response, error in
let decoder = JSONDecoder()
guard let data2 = data,
let tops = try? decoder.decode(TopResponse.self, from:
data2) else { return }
print(tops.results[4].given)
}
task.resume()
fails to parse the fetched JSON string and does not print anything.
What could be the problem here please?
What's wrong with your code?
try?
That's the main culprit.
Why? You are ignoring the error thrown by the decode(_:from:). You are ignoring the error that could give you the exact reason or at least a hint on why it failed. Instead, write a proper do { try ... } catch { ... }.
So:
guard let data2 = data,
let tops = try? decoder.decode(TopResponse.self, from:
data2) else { return }
print(tops.results[4].given)
=>
guard let data2 = data else { return }
do {
let tops = try decoder.decode(TopResponse.self, from: data2)
print(tops.results[4].given)
} catch {
print("Got error while parsing: \(error)")
print("With response: \(String(data: data2, encoding: .utf8))") //Just in case because I've seen plenty of code where expected JSON wasn't the one received: it was an error, doc changed, etc...
}
Output for the first print:
$>Got error while parsing: keyNotFound(CodingKeys(stringValue: "results", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"results\", intValue: nil) (\"results\").", underlyingError: nil))
Fix:
struct TopResponse: Codable {
let results: [Top]
enum CodingKeys: String, CodingKey {
case results = "data"
}
}
Or rename results with data.
Then, next error:
$>Got error while parsing: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "avg_score", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
Extract from JSON:
"avg_score": 20.4
It's not a String (the value it's not between double quotes), that's a Double.
Fix:
let avg_score: String?
=>
let avg_score: Double?
I have the following json data and trying to parse it, but I get number of objects, but object itself in the array all are nil.
I do not want to decode the origin in the following json object.
By the way, the following string is first converted to data and then passed it to parse function below.
Data is follows:
[
[
{"id": "152478", "age": 20},
{"character": "king","isDead":"no", "canMove" :"yes", "origin" :"south africa"}
],
[
{"id": "887541", "age": 22},
{"character": "lion", "isDead":"no", "canMove" :"yes", "origin" :"south america"}
]
]
Models
struct A: Codable {
let id: String?
let age: Int?
enum CodingKeys: String, CodingKey {
case id
case age
}
}
struct B: Codable {
let character, isDead, canMove: String?
enum CodingKeys: String, CodingKey {
case character
case isDead
case canMove
}
}
struct AB :Codable {
let a: A
let b: B
init(from decoder: Decoder) throws {
guard var container = try? decoder.unkeyedContainer() else {
//no error here!!!
fatalError()
}
print(container)
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.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: nil)
)
}
self.a = a
self.b = b
}
}
ViewModel
private func parse(jsonData : Data){
do {
let decoder = JSONDecoder()
let response = try decoder.decode([AB].self, from: jsonData)
print(response)
}
catch (let error as NSError) {
print(error)
}
}
UPDATE:
By the way, the following code works. I wonder why above code does not handle?
private func parseData(jsonData : Data)
{
do {
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 b = try JSONDecoder().decode(A.self, from: jsonDat)
}
else if j == 1
{
let jsonDatt = (try? JSONSerialization.data(withJSONObject:response[i][j]))!
let a = try JSONDecoder().decode(B.self, from: jsonDatt)
}
}
}
print(response)
}
catch let error as NSError {
print(error)
}
}
UPDATE II:
If I make the following changes [AB] --> [[AB]], and then I call it as follows, it decodes data, but in the array I end up, A object has values, but B nil, or vice versa.
let response = try decoder.decode([[AB]].self, from: jsonData)
guard var container = try? decoder.singleValueContainer() else
{
fatalError()
}
I just tried this stripped-down version of your code in a playground
let json = """
[
[
{"id": "152478", "age": 20},
{"character": "king","isDead":"no", "canMove" :"yes", "origin" :"south africa"}
],
[
{"id": "887541", "age": 22},
{"character": "lion", "isDead":"no", "canMove" :"yes", "origin" :"south america"}
]
]
""".data(using: .utf8)!
struct A: Codable {
let id: String
let age: Int
}
struct B: Codable {
let character, isDead, canMove: String
}
struct AB: Codable {
let a: A
let b: B
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
self.a = try container.decode(A.self)
self.b = try container.decode(B.self)
}
}
do {
let ab = try JSONDecoder().decode([AB].self, from: json)
print(ab.count)
print(ab[0].a.id)
print(ab[0].b.character)
print(ab[1].a.id)
print(ab[1].b.character)
} catch {
print(error)
}
and it works just fine. Maybe this helps figuring out what's going on.
Goal:
When I select a message from the list of chat messages in the MessageListController with didSelectRowAt, I want the opened chat session in the next ChatDetailController to match the same conversation that was selected. Obviously.
I'm working with WatchKit but it's the same thing in this iOS image. The message with Sophia is selected and the chat with Sophia opens.
I want to pass the json "message_id" i.e. the chatMessageId property from my model. I'm already passing the chatMessageId from the MessageModelto the ChatDetailController with this line presentController(withName: "ChatDetailController", context: messageContext)
Here is a print out of the passed data using messageContext.
Passed context: Optional(HTWatch_Extension.MessageModel(partner: "9859", nickname: "Marco", message: "Have you seen is dog?", city: "Madrid", countryBadgeImageURL: https://i.imgur.com/PJcyle7.jpg, messageListImageURL: https://i.imgur.com/PJcyle7.jpg, chatMessageId: "Tva9d2OJyWHRC1AqEfKjclRwXnlRDQ", status: "offline"))
What's the next step? How do I tell the ChatDetailController to populate its table with the conversation that matches the row that was selected?
MessageListController
// ...code...
let messageObject = [MessageModel]()
//var chatObject = [ChatModel]()
// ...code...
override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
var messageContext = messageObject[rowIndex]
var chatContext = chatObject[rowIndex]
do {
guard let fileUrl = Bundle.main.url(forResource: "Chats", withExtension: "json") else {
print("File could not be located")
return
}
let data = try Data(contentsOf: fileUrl)
let decoder = JSONDecoder()
let msg = try decoder.decode([ChatModel].self, from: data)
self.chatObject = msg
} catch let error {
print(error)
}
// I got part of this line from a previous question, but these property types do not match.
// No matter what chatObject I create I cannot access it/assign it.
// Also I do not want to mutate messageContext which Xcode is telling me to do.
messageContext.chatMessageId = (chatObject as AnyObject).filter { (dictionaryTemp:[String:String]) -> Bool in
return dictionaryTemp["message_id"] == chatContext.chatMessageId
}
// WatchKit's model presentation method.
presentController(withName: "ChatDetailController", context: messageContext)
}
ChatDetailController
var chats: [ChatModel] = []
var messageModel: MessageModel? {
didSet {
guard let model = messageModel else { return }
partnerLabel.setText(model.nickname)
}
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if let message = context as? MessageModel {
self.messageModel = message
print("Passed context: \(String(describing: messageModel))")
} else {
print("Passed context error: \(String(describing: context))")
}
do {
guard let fileUrl = Bundle.main.url(forResource: "Chats", withExtension: "json") else {
print("File could not be located")
return
}
let data = try Data(contentsOf: fileUrl)
let decoder = JSONDecoder()
let msg = try decoder.decode([ChatModel].self, from: data)
self.chats = msg
} catch let error {
print(error)
}
DispatchQueue.main.async(execute: {
self.setupTable(chatMessageArray: self.chats as [AnyObject])
})
}
ChatModel
public struct ChatModel: Codable {
// ... other properties
public var chatMessageId: String
enum CodingKeys: String, CodingKey {
// ... other cases
case chatMessageId = "message_id"
}
init (message:String , fromId:String, toID : String, imgUrl : URL?, chatMessageId : String) {
// ... other properties
self.chatMessageId = chatMessageId
}
}
// .... decoders etc
}
MessageModel
public struct MessageModel: Codable {
// ...
public var chatMessageId: String
enum CodingKeys: String, CodingKey {
// ... other cases
case chatMessageId = "message_id"
}
// ... init/decoders etc
}
Messages.json
[
{
"userid": "4444",
"nickname": "Marco",
"online_status": "offline",
"message": "Have you seen his dog?",
"city": "Madrid",
"flag_url": "https://i.imgur.com/PJcyle7.jpg",
"creationDate": "2016-02-22 15:18:40",
"avatar_url": "https://i.imgur.com/PJcyle7.jpg",
"message_id": "Tva9d2OJyWHRC1AqEfKjclRwXnlRDQ" // different id
},
{
"userid": "12121",
"nickname": "Tom",
"online_status": "online",
"message": "Where is the pizza shop?",
"city": "Kyoto",
"flag_url": "https://i.imgur.com/PJcyle7.jpg",
"creationDate": "2016-02-22 15:18:40",
"avatar_url": "https://i.imgur.com/PJcyle7.jpg",
"message_id": "EnotMkk8REEd0DHGvUgnd45wBap80E" // different id
}
]
Chats.json
2 conversations containing 2 messages each. Each conversation has a unique fromId (partner), toID (myself), and unique message_id.
[
{
"fromId": "zz1234skjksmsjdfwe2zz",
"toId": "qq43922sdkfjsfmmxdfqq",
"messageText": "Have you seen is dog?",
"imageUrl": "https://i.imgur.com/PJcyle7.jpg",
"message_id": "Tva9d2OJyWHRC1AqEfKjclRwXnlRDQ", // conversation 1 - same message id as the 1st message in Messages.josn with nickname Marco.
"read": "true"
},
{
"fromId": "zz1234skjksmsjdfwe2zz",
"toId": "qq43922sdkfjsfmmxdfqq",
"messageText": "Yes I have. It's cute.",
"imageUrl": "https://i.imgur.com/PJcyle7.jpg",
"message_id": "Tva9d2OJyWHRC1AqEfKjclRwXnlRDQ", // conversation 1
"read": "true"
},
{
"fromId": "bb888skjaaasjdfwe2333",
"toId": "qq43922sdkfjsfmmxdfqq",
"messageText": "What kind of pizza do you want?",
"imageUrl": "https://i.imgur.com/PJcyle7.jpg",
"message_id": "EnotMkk8REEd0DHGvUgnd45wBap80E", // conversation 2 - same message id as the 2nd message in Messages.josn with nickname Tom.
"read": "true"
},
{
"fromId": "bb888skjaaasjdfwe2333",
"toId": "qq43922sdkfjsfmmxdfqq",
"messageText": "I like ham & pineapple pizza.",
"imageUrl": "https://i.imgur.com/PJcyle7.jpg",
"message_id": "EnotMkk8REEd0DHGvUgnd45wBap80E", // conversation 2
"read": "true"
}
]
let msg = try decoder.decode([ChatModel].self, from: data)
self.chats = msg
You are storing all chat data contained in Chats.json into chats property.
To filter chats, try replacing second line like below
self.chats = msg.filter { chat in
return chat.chatMessageId == self.messageModel.chatMessageId
}
or, while I don't know why all chats are contained in single json file, it will be better to separate json file into multiple files for each chat like Chat-Tva9d2OJyWHRC1AqEfKjclRwXnlRDQ.json.
I hope this will help.
Your MessageListController didSelectRowAt method should be as simple as this.
//MessageListController
// ...code...
let messageObject = [MessageModel]()
// var chatObject = [ChatModel]()
// ...code...
override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
//creating a context with the message object at selected row.
var messageContext = messageObject[rowIndex]
// WatchKit's model presentation method. passing the context along.
presentController(withName: "ChatDetailController", context: messageContext)
// Rest of the codes not required in your MessageListController
}
// ...code...
Your MessageListController will be like this.
// ChatDetailController
var chats: [ChatModel] = []
var messageModel: MessageModel? {
didSet {
guard let model = messageModel else { return }
partnerLabel.setText(model.nickname)
}
}
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if let message = context as? MessageModel {
self.messageModel = message
print("Passed context: \(String(describing: messageModel))")
} else {
print("Passed context error: \(String(describing: context))")
}
do {
guard let fileUrl = Bundle.main.url(forResource: "Chats", withExtension: "json") else {
print("File could not be located")
return
}
let data = try Data(contentsOf: fileUrl)
let decoder = JSONDecoder()
//msg holds all chat messages in the file
let msg = try decoder.decode([ChatModel].self, from: data)
// We should filter the chats in the msg array with the selected message content
self.chats = msg.filter({
$0.chatMessageId == messageModel?.chatMessageId
})
// self.chats only holds the chats with chatMessageId
} catch let error {
print(error)
}
DispatchQueue.main.async(execute: {
self.setupTable(chatMessageArray: self.chats as [AnyObject])
})
Hope this will resolve your issue. Happy Coding :)
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)
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"}]