I have a problem with sending a JSON response from UITableView into another UITableView.
I want to send the Products array like in the code below into another UITableViewController based on the selected row on the TableView, but when I log the value in didSelectRowAt it returns nil.
So here's the JSON return.
{
"error_description": [],
"results": [
{
"id": 2,
"name": "Ernser, Kilback and Kreiger LLC",
"address": "48788 Adaline Ville 7634 Bertram Shoal",
"contact_person": "Coralie Schaden",
"mobile_number": "(238) 076-0562",
"image": {
"url": null,
"thumb": {
"url": null
},
"medium": {
"url": null
},
"small": {
"url": null
},
"icon": {
"url": null
}
},
"registered_at": "2017-01-10T04:16:52.621Z",
"products": [
{
"id": 21,
"name": "Fantastic Rubber Car",
"image": {
"url": null,
"thumb": {
"url": null
},
"medium": {
"url": null
},
"small": {
"url": null
},
"icon": {
"url": null
}
},
"points": 0,
"merchant_id": 2,
"created_at": "2017-01-10T04:16:52.630Z",
"updated_at": "2017-01-10T04:16:52.636Z"
},
{
"id": 22,
"name": "Mediocre Plastic Shirt",
"image": {
"url": null,
"thumb": {
"url": null
},
"medium": {
"url": null
},
"small": {
"url": null
},
"icon": {
"url": null
}
},
"points": 15,
"merchant_id": 2,
"created_at": "2017-01-10T04:16:52.819Z",
"updated_at": "2017-01-10T04:16:52.827Z"
},
]
}
This is a part of my code for getting the response.
I used the Alamofire
var merchantModel: Merchant!
var merchantArr = [Merchant]()
var productArr = [Product]()
var productModel : Product?
.....
case .success:
let json = JSON(data: response.data!)
for result in json["results"].arrayValue{
guard let merchantID = result["id"].number, let merchantName = result["name"].string, let merchantAddress = result["address"].string, let contactPerson = result["contact_person"].string, let mobilenumber = result["mobile_number"].string else{
return
}
self.merchantModel = Merchant(merchantID: merchantID, merchantName: merchantName, merchantAddress: merchantAddress, contactPerson: contactPerson, mobileNumber: mobilenumber, merchantImage: contactPerson)
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(self.merchantModel!, toFile: Merchant.ArchiveURL.path)
if !isSuccessfulSave {
print("Failed to save sender...")
}
for prodArr in result["products"].arrayValue{
print("prodArr:\(prodArr)")
guard let prodID = prodArr["id"].number, let prodName = prodArr["name"].string,
let points = prodArr["points"].number else{
return
}
self.productModel = Product(productID: prodID, productName: prodName, productPoints: points)
}
self.merchantArr.append(self.merchantModel!)
self.productArr.append(self.productModel!)
self.merchantModel.loadMerchant()
self.tableView.reloadData()
}
Swift is not javascript, and JSON is not the simplest way to handle the data in this language.
You'd probably prefer to transform your JSON data into Foundation objects, manipulate them, create UITableView with them... And when you'll need to have the JSON format again, you transform those Foundation objects back to JSON.
There are a lot of ways to achieve that, and some amazing third-party libraries to do it very easily. However for a start I encourage you to have a look at Apple's resources :
NSJSONSerialization
Working with JSON in Swift
Related
I can successfully decode JSON from this URL, mostly. The query item -nbd is the number of "dates" to request.
https://ssp.imcce.fr/webservices/miriade/api/ephemcc.php?-step=1m&-tcoor=3&-teph=1&-observer=43.078148,-79.075699,180.0&-mime=json&-nbd=1440&-output=--iso&-ep=2021-1-17T00:00&-name=p:Sun
When I request -nbd=769 or fewer dates, ephem is correctly decoded every time.
However, when I request -nbd=770 or more dates, my dataTask prints out the entire response with 1440 elements, but ephem is nil.
What could be going wrong with this?
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
let decoder = JSONDecoder()
if let data = data {
let str = String(data: data, encoding: .utf8)!
print(str)
let ephem = try? decoder.decode(EphemCC.self, from: data)
completion(ephem)
} else {
print("Either no data was returned, or data was not properly decoded.")
completion(nil)
}
}
task.resume()
Sample JSON response:
{
"sso": {
"num": "11",
"name": "Sun",
"type": "planet",
"parameters": {
"diameter": 0.1392E+7
}
},
"coosys": {
"epoch": "J2000",
"equinox": "J2000",
"system": "eq_ICRF"
},
"ephemeris": {
"time_scale": "UTC",
"planetary_theory": "INPOP19A",
"coordinates": "local",
"reference_frame": {
"type": "astrometric J2000",
"plane": "equator",
"center": "topocenter",
"topocenter": {
"iau_code": "--1",
"name": "userLocation",
"longitude": -0.79075698999999986E+2,
"latitude": 0.43078147999999999E+2,
"altitude": 0.18E+3
}
}
},
"data": [
{
"Date": "2021-01-17T00:00:00.000",
"LAST": "02:30:14.28",
"Az": 0.26002683458818331E+3,
"H": -0.20245586021541037E+2,
"Dobs": 0.98378058040177629E+0,
"VMag": -0.26775508772817108E+2,
"R": -0.999E+3,
"RV": 0.42476667062773499E+0
},
//
// ... etc, 1440 "data" elements received
//
{
"Date": "2021-01-17T23:59:00.000",
"LAST": "02:33:10.66",
"Az": 0.25997789921032143E+3,
"H": -0.19879751688585404E+2,
"Dobs": 0.98384649280603065E+0,
"VMag": -0.26775363291013889E+2,
"R": -0.999E+3,
"RV": 0.43266153050417039E+0
}
],
"datacol": {
"Date": "Date",
"LAST": "Local sidereal time",
"Az": "Azimuth",
"H": "Elevation",
"Dobs": "Range of target center to observer",
"VMag": "Apparent magnitude",
"R": "Atmospheric refraction",
"RV": "Radial velocity of target wrt observer"
},
"unit": {
"diameter": "km",
"topocenter": {
"longitude": "deg",
"latitude": "deg",
"altitude": "m"
},
"LAST": "hms",
"Az": "deg",
"H": "deg",
"Dobs": "au",
"VMag": "mag",
"R": "arcsec",
"RV": "km/s"
}
}
I would like to ask if how to group the object by another object inside its common array based on the id in Swift.
Here's the JSON response, I need to group the list of the item by program id.
{
"id": "",
"ordered_by": 64,
"order_details": [
{
"resource": "Product",
"required_prescription": false,
"item": {
"id": 6,
"name": "Synergistic Copper Gloves",
"code": "51537661-C",
"enabled": true,
"generic_name": "Mediocre Steel Wallet",
"price_cents": 200000
},
"program": {
"id": 12, <----PROGRAM ID
"name": "Synergistic Wooden Shoes",
"provider": "Synergistic Rubber Coat",
"discount_type": "fixed"
}
},
{
"resource": "Product",
"required_prescription": true,
"item": {
"id": 7,
"name": "Rustic Leather Table",
"code": "74283131-P",
"enabled": true,
"generic_name": "Incredible Bronze Clock",
"price_cents": 8994
},
"program": {
"id": 12, <----PROGRAM ID
"name": "Synergistic Wooden Shoes",
"provider": "Synergistic Rubber Coat",
"discount_type": "fixed"
}
},
{
"resource": "Product",
"required_prescription": false,
"item": {
"id": 116,
"name": "Ergonomic Marble Hat",
"code": "98845056-A",
"enabled": true,
"generic_name": "Incredible Granite Lamp",
"price_cents": 8267
},
"program": {
"id": 10, <----PROGRAM ID
"name": "Durable Rubber Bag",
"provider": "Aerodynamic Steel Chair",
"discount_type": "fixed"
}
}
]}
For example, the item with program id 12 should be inserted under its common program.
This should be the expected object after grouping. The item was grouped by program id 12 & 10.
[
{
"id": 12, <----- PROGRAM ID
"name": "Synergistic Wooden Shoes",
"provider": "Synergistic Rubber Coat",
"discount_type": "fixed",
"item": [
{
"id": 6,
"name": "Synergistic Copper Gloves",
"code": "51537661-C",
"enabled": true,
"generic_name": "Mediocre Steel Wallet",
"price_cents": 200000
},
{
"id": 7,
"name": "Rustic Leather Table",
"code": "74283131-P",
"enabled": true,
"generic_name": "Incredible Bronze Clock",
"price_cents": 8994
}
]
},
{
"id": 10, <----PROGRAM ID
"name": "Durable Rubber Bag",
"provider": "Aerodynamic Steel Chair",
"discount_type": "fixed",
"item": [
{
"id": 116,
"name": "Ergonomic Marble Hat",
"code": "98845056-A",
"enabled": true,
"generic_name": "Incredible Granite Lamp",
"price_cents": 8267
}
]
}
]
I've successfully made it in Java using the sample code below:
private String parseJson(String source) {
JSONArray result = new JSONArray();
List<Integer> ids = new ArrayList<>();
HashMap<Integer,JSONObject> programs = new HashMap<>();
try {
JSONObject jSource = new JSONObject(source);
JSONArray orderDetails = jSource.getJSONArray("order_details");
if (orderDetails.length() > 0) {
for (int i = 0; i < orderDetails.length(); i++) {
JSONObject jsonObject = orderDetails.getJSONObject(i);
JSONObject item = jsonObject.getJSONObject("item");
JSONObject program = jsonObject.getJSONObject("program");
int programId = jsonObject.getJSONObject("program").getInt("id");
if (!ids.contains(programId)) {
ids.add(programId);
program.put("item",new JSONArray().put(item));
programs.put(programId,program);
}else{
program.put("item",programs.get(programId).getJSONArray("item").put(item));
}
}
for(int k :programs.keySet()){
result.put(programs.get(k));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
I'm trying to replicate it on swift but I'm looking for a different approach, not the same approach as I did in Java. I tried to used reduce and filter method but it doesn't work.
Any answer would be hight appreciated.
Thanks
Few comments on an answer from #omerfarukozturk,
Flatmap is deprecated so you can use map or compactMap.
In omerfarukozturk's answer he returns only program id while grouping which is not as per requirement, we need whole program details instead of just Id
So I have applied some other logic.
Bind data with model
let arrDetails = //Bind data with model, response.orderDetails
Get Unique program ID
let arrProgramID = arrDetails.compactMap{ $0.program }.removingDuplicates(byKey: \.id)
// removingDuplicates is logic for remove duplicate programs and have unique program array with all information
Bind array in [[Program: [Item]]] form
let finalArray = arrProgram.map { (program) -> [Program: [Item]] in
let arr = arrDetails.filter{ $0.program.id == program.id }.compactMap{ $0.item }
return [program: arr]
}
Adding extension for removing duplicates
extension Array {
func removingDuplicates<T: Equatable>(byKey key: KeyPath<Element, T>) -> [Element] {
var result = [Element]()
var seen = [T]()
for value in self {
let key = value[keyPath: key]
if !seen.contains(key) {
seen.append(key)
result.append(value)
}
}
return result
}
}
You can use Dictionary(grouping:) to group a list. Not completely answer, but for your case you can create a logic like below;
Assume you have a decoded response model as response for your json.
let flatttenOrderDetails = response.order_details.flatMap( { $0 })
let grouped = Dictionary(grouping: flatttenOrderDetails, by: { (element: Detail) in
return element.program.id
})
I was able to group the JSON based on its common program id by creating another model ProgramWith items.
struct ProgramWithItems: Codable {
let id: Int?
let name: String?
let provider: String?
let discountType: String?
let resource: String?
let discount: String?
let item: [Item]
}
Then I used the reduce method and populate the ProgramWithItems model using the code below:
func groupItems(_ response: CartEntity.Response) -> [ProgramWithItems] {
response.orderDetails!.reduce(into: []) { (result: inout [ProgramWithItems], detail: OrderDetail) in
guard let index = result.firstIndex(where: { $0.id == detail.program?.id }) else {
var item = detail.item
item?.quantity = detail.quantity
item?.costCents = detail.cost_cents
let newProgram = ProgramWithItems(
id: detail.program?.id,
name: detail.program?.name ?? "",
provider: detail.program?.provider ?? "",
discountType: detail.program?.discountType ?? "",
resource: detail.resource ?? "",
discount: detail.program?.discount,
item: [item!])
result.append(newProgram)
return
}
let existingProgram = result[index]
var item = detail.item
item?.quantity = detail.quantity
item?.costCents = detail.cost_cents
let extendedProgram = ProgramWithItems(
id: existingProgram.id,
name: existingProgram.name,
provider: existingProgram.provider,
discountType: existingProgram.discountType,
resource: detail.resource ?? "",
discount: detail.program?.discount,
item: existingProgram.item + [item!])
result[index] = extendedProgram
}
}
Im working with decoded JSON, and specifically i need to work within an array to pull out a property of a specific object based on its key.
JSON looks like the following, and Im needing to pull out the filenames for "fanart" and "clearlogo"
{
"code": 200,
"status": "Success",
"data": {
"count": 1,
"base_url": {
"original": "https://cdn.thegamesdb.net/images/original/",
"small": "https://cdn.thegamesdb.net/images/small/",
"thumb": "https://cdn.thegamesdb.net/images/thumb/",
"cropped_center_thumb": "https://cdn.thegamesdb.net/images/cropped_center_thumb/",
"medium": "https://cdn.thegamesdb.net/images/medium/",
"large": "https://cdn.thegamesdb.net/images/large/"
},
"images": {
"2578": [
{
"id": 5512,
"type": "boxart",
"side": "front",
"filename": "boxart/front/2578-1.jpg",
"resolution": "1533x2155"
},
{
"id": 5513,
"type": "boxart",
"side": "back",
"filename": "boxart/back/2578-1.jpg",
"resolution": "1522x2155"
},
{
"id": 87092,
"type": "fanart",
"side": null,
"filename": "fanart/2578-1.jpg",
"resolution": "1920x1080"
},
{
"id": 87093,
"type": "fanart",
"side": null,
"filename": "fanart/2578-2.jpg",
"resolution": "1920x1080"
},
{
"id": 87094,
"type": "fanart",
"side": null,
"filename": "fanart/2578-3.jpg",
"resolution": "1920x1080"
},
{
"id": 87095,
"type": "clearlogo",
"side": null,
"filename": "clearlogo/2578.png",
"resolution": "400x300"
},
{
"id": 87096,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-1.jpg",
"resolution": null
},
{
"id": 87097,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-2.jpg",
"resolution": null
},
{
"id": 87098,
"type": "fanart",
"side": null,
"filename": "fanart/2578-4.jpg",
"resolution": "1920x1080"
},
{
"id": 87099,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-3.jpg",
"resolution": null
},
{
"id": 87100,
"type": "screenshot",
"side": null,
"filename": "screenshots/2578-4.jpg",
"resolution": null
},
{
"id": 87101,
"type": "fanart",
"side": null,
"filename": "fanart/2578-5.jpg",
"resolution": "1920x1080"
}
]
}
},
The key "2578"is dynamic and changes by the game. I have this succesfuly decoding with the following:
struct GameDBData : Decodable {
let data : GameDataImages
}
struct GameDataImages : Decodable {
let images : Images
}
struct Images : Decodable {
var innerArray: [String: [Inner]]
struct Inner: Decodable {
let id : Int
let fileName : String
let type : String
enum CodingKeys : String, CodingKey {
case id
case fileName = "filename"
case type
}
}
private struct CustomCodingKeys: CodingKey {
var stringValue : String
init?(stringValue : String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
self.innerArray = [String: [Inner]]()
for key in container.allKeys {
let value = try container.decode([Inner].self, forKey: CustomCodingKeys(stringValue: key.stringValue)!)
self.innerArray[key.stringValue] = value
}
}
}
Ive tried firstIndex(where:) but Im not getting anywhere with it. How can i iterate the 2578 array to get the filename for type: "clearlogo" and type: "fanart" im needing?
You have array of Inner.Something like this,
var array:[Inner] = [_yourData]
If we assume your array should have only one object with type "fanart" & "clearlogo" for each.we can get each filename using ,
let fanartFileName = array.filter({$0.type == "fanart"})[0].fileName
let clearlogoFileName = array.filter({$0.type == "clearlogo"})[0].fileName
some times your array havn't any record for these types.or have more than one,
let fanartArray = ar.filter({$0.type == "fanart"})
if fanartArray.count > 0 {
fanartArray.forEach { (record) in
print(record.fileName)
}
}
I have one SwiftyJson object.
I can not filter that array. I have tried this solution https://stackoverflow.com/a/37497170/4831567. But my json format is different that's why not working.
[
{
"name": "19860",
"header": {
"start_time": "1519270200",
"end_time": "1519299000",
"state": "lunch"
},
"alerts": "1",
"venue": {
"location": "Delhi, India",
"timezone": "+05:30"
},
"srs_category": [
0,
1
]
},
{
"name": "19861",
"header": {
"start_time": "1519270200",
"end_time": "1519299000",
"state": "Dinner"
},
"alerts": "1",
"venue": {
"location": "Mumbai, India",
"timezone": "+05:30"
},
"srs_category": [
1,
3
]
},
{
"name": "19862",
"header": {
"start_time": "1519270200",
"end_time": "1519299000",
"state": "lunch"
},
"alerts": "1",
"venue": {
"location": "Surat, India",
"timezone": "+05:30"
},
"srs_category": [
0,
2
]
}
]
i want to find that object that srs_category contain 1. I know it is possible by looping and condition. But i want via NSPredicate. If it is possible then please help me.
Thank You.
Here is easy way to use SwiftyJSON:
let filtered = JSON(yourArray).arrayValue.filter({
$0["srs_category"].arrayValue.map({ $0.intValue }).contains(1)
})
Use a Swift native function rather than NSPredicate
data represents the Data object received from somewhere
do {
if let json = try JSONSerialization.jsonObject(with:data) as? [[String:Any]] {
let srsCategory1 = json.first(where: { dict -> Bool in
guard let array = dict["srs_category"] as? [Int] else { return false }
return array.contains(1)
})
print(srsCategory1 ?? "not found")
}
} catch {
print(error)
}
If there are multiple items which can match the condition replace first with filter. Then the result is a non-optional array.
I have an iOS application which parses a JSON feed provided by Google Plus. This JSON feed contains links, titles, etc for posts on a Google Plus profile.
The JSON file in certain parts has an array called "attachments" and that array has the image URL which I am parsing.
The problem is that that array is not always present, so sometime its available with the image URL and sometime its not even there.
As a result of this, when I parse for the image URL my iOS application crashes because the array it is looking for is not always present.
So how can I test to see if the array is present before deciding to parse for the image URL. I tried to make a simple if-statement, but it doesn't seem to work. Here it is:
if ([[[episodes objectAtIndex:index] valueForKey:#"object"] valueForKey:#"attachments"] == [NSNull null]) {
NSLog("No image to parse.");
}
else {
// parse image link
}
Here is the JSON file:
{
"kind": "plus#activity",
"etag": "\"9Q4kEgczRt3gWehMocqSxXqUhYo/uQcxIDsySGhlI5hMFHcxjuBhM9k\"",
"title": "",
"published": "2013-02-24T22:30:25.159Z",
"updated": "2013-02-24T22:30:25.159Z",
"id": "z13jgdihsvnytdttc23hxdz5ypfsjnzsb",
"url": "https://plus.google.com/102757352146004417544/posts/7psQLdwS2F7",
"actor": {
"id": "102757352146004417544",
"displayName": "Daniel Sadjadian",
"url": "https://plus.google.com/102757352146004417544",
"image": {
"url": "https://lh4.googleusercontent.com/-EF0LibpIsEY/AAAAAAAAAAI/AAAAAAAAALQ/2nt32bqYBtM/photo.jpg?sz=50"
}
},
"verb": "post",
"object": {
"objectType": "note",
"content": "",
"url": "https://plus.google.com/102757352146004417544/posts/7psQLdwS2F7",
"replies": {
"totalItems": 0,
"selfLink": "https://www.googleapis.com/plus/v1/activities/z13jgdihsvnytdttc23hxdz5ypfsjnzsb/comments"
},
"plusoners": {
"totalItems": 0,
"selfLink": "https://www.googleapis.com/plus/v1/activities/z13jgdihsvnytdttc23hxdz5ypfsjnzsb/people/plusoners"
},
"resharers": {
"totalItems": 0,
"selfLink": "https://www.googleapis.com/plus/v1/activities/z13jgdihsvnytdttc23hxdz5ypfsjnzsb/people/resharers"
},
"attachments": [
{
"objectType": "video",
"displayName": "SGU - The Game Has Changed!",
"content": "Send this video to your friends to spread the word about the upcoming SyFy marathon. This very well may be our last chance to save the show. If you're in the...",
"url": "http://www.youtube.com/watch?v=WtHusm7Yzd4",
"image": {
"url": "https://lh3.googleusercontent.com/proxy/z9shhK2d39jM2P-AOLMkqP2KMG2DjipCWlcPeVPaRvHkfj-nmxkxqZfntAVMoV88ZOuroRHIvG4qs-K0SaMjQw=w506-h284-n",
"type": "image/jpeg",
"height": 284,
"width": 506
},
"embed": {
"url": "http://www.youtube.com/v/WtHusm7Yzd4?version=3&autohide=1",
"type": "application/x-shockwave-flash"
}
}
]
},
"provider": {
"title": "Google+"
},
"access": {
"kind": "plus#acl",
"description": "Public",
"items": [
{
"type": "public"
}
]
}
},
Thanks for your time, Dan.
UPDATE:
Just assign that to array and check the array is nil.
NSMutableArray *attachments = [[[episodes objectAtIndex:index] valueForKey:#"object"] valueForKey:#"attachments"];
if (!attachments) {
NSLog("No image to parse.");
}
This will work.
I don't see the problem (please post your error details) but when dealing with JSON, I like to use a category IfNullThenNil. Your code would look like:
if ( [ [ episodes[index] valueForKeyPath:#"object.attachments" ] ifNullThenNil ]
{
// parse image link
}
else
{
NSLog(#"No image to parse.\n");
}
Here's the category on NSObject/NSNull
#implementation NSObject (IfNullThenNil)
-(id)ifNullThenNil
{
return self ;
}
#end
#implementation NSNull (IfNullThenNil)
-(id)ifNullThenNil
{
return nil ;
}
#end