I'm dealing with this. This is mi JSON response
{
"code": 200,
"message": "ok",
"data": {
"section1": [
{
"clave": "xxxx",
"orden": 0,
"nombre": "xxxxx",
"video": "xxxxx",
"imagen": "xxxxx",
"series": 0,
"repeticiones": 0,
"descanso":0,
"completado": false
},
{
"clave": "xxxx",
"orden": 0,
"nombre": "xxxxx",
"video": "xxxxx",
"imagen": "xxxxx",
"series": 0,
"repeticiones": 0,
"descanso":0,
"completado": false
}
}
],
"section2": [
{
"clave": "xxx",
"equipo": "xx",
"imagen": "x",
"tiempo": 0,
"intensidad": 0,
"completado": false
}
],
"section3": [
{
"clave": "xxx",
"nombre": "xxxx",
"imagen": "",
"completado": false
},
{
"clave": "xxx",
"nombre": "xxxx",
"imagen": "",
"completado": false
}
],
"section4": [
{
"clave": "xx",
"nombre": "xxxx",
"imagen": "x",
"completado": false
},
{
"clave": "xx",
"nombre": "xxxx",
"imagen": "x",
"completado": false
}
]
}
}
What I want to do is display the info in sections, the sections should be "section1", "section2", "section3", "section4" ,obviously and display all the info that "section1" contains, and if the section is "section2" display all the info in cardios an so on... But I want to display it in the same tableView just divided in sections Could you help me?. thanks in Advance
Since NSDictionary is not an ordered data container, you would have to use a different data structure or you would have to update the API and return an ordered array inside the "data".
First of all in the JSON above Section2 is a extraneous closing brace.
This is a starting point.
Decode the value for data as [String:[Item]] and map each dictionary to a helper struct Section containing the name (dictionary key) and the array of Item (dictionary value). The sections array is sorted by name
struct Root : Decodable {
let code : Int
let message : String
let sections : [Section]
enum CodingKeys: String, CodingKey { case code, message, sections = "data"}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
code = try container.decode(Int.self, forKey: .code)
message = try container.decode(String.self, forKey: .message)
let data = try container.decode([String : [Item]].self, forKey: .sections)
sections = data.map({Section(name: $0.0, items: $0.1)}).sorted(by: {$0.name < $1.name})
}
}
struct Section {
let name : String
let items : [Item]
}
struct Item : Decodable {
let clave : String
let completado : Bool
let repeticiones : Int?
// ... other properties
}
Decode the Root struct (data is the JSON data)
let result = try JSONDecoder().decode(Root.self, from: data)
let sections = result.sections
In the table view sections are the sections and items are the rows
Another solution should create a custom parser which will convert your data into an array of a model which would represent any section.
Model
class SomethingObj {
var clave: String?
var orden: Int?
var nombre: String?
var video: String?
var imagen: String?
var series: Int?
var repeticiones: Int?
var descanso: Int?
var completado: Bool?
init() {
}
}
Parser
private func parseData(for structure: NSDictionary) -> [[SomethingObj]] {
var sectionsArray = [[SomethingObj]]()
guard let sectionsLoop = structure["data"] as? NSDictionary else { return sectionsArray }
var sectionIndex = 1
while let sectionObjsData = sectionsLoop["section\(sectionIndex)"] as? [NSDictionary] {
var sectionArray = [SomethingObj]()
for sectionObjData in sectionObjsData {
let obj = SomethingObj()
obj.clave = sectionObjData["clave"] as? String
obj.orden = sectionObjData["orden"] as? Int
obj.nombre = sectionObjData["nombre"] as? String
obj.video = sectionObjData["video"] as? String
obj.imagen = sectionObjData["imagen"] as? String
obj.series = sectionObjData["series"] as? Int
obj.repeticiones = sectionObjData["repeticiones"] as? Int
obj.descanso = sectionObjData["descanso"] as? Int
obj.completado = sectionObjData["completado"] as? Bool
sectionArray.append(obj)
}
sectionsArray.append(sectionArray)
sectionIndex = sectionIndex + 1
}
return sectionsArray
}
Display parsed data
Use whatever you want in order to display something with your array of parsed data.
Related
I want to parse this response. Everything else is parsed - except Images. I receive it in a string but then I cannot convert it into a dictionary. This is my model.
struct PropertyList: Decodable {
let result: Property?
let success: Bool = false
struct Property: Decodable {
let name: String?
let description: String?
let propertyType: PropertyType
let latitude, longitude: String
let images: String?
let areaSize:Int?
let threeSixtyView: String?
let threeDModel: String?
enum CodingKeys: String, CodingKey {
case name
case propertyDescription = "description"
case propertyType, latitude, longitude
case threeDModel = "threeDModel"
case images = "images"
}
}
}
struct PropertyType: Codable {
let id: Int
let name, propertyTypeDescription: String
enum CodingKeys: String, CodingKey {
case id, name
case propertyTypeDescription = "description"
}
}
API Response :
"name": "Al Deyar",
"description": "Al Deyar Villa",
"propertyType": {
"id": 709415277471,
"name": "villa",
"description": "villa"
},
"latitude": "1",
"longitude": "2",
"viewOfWater": null,
"threeDModel": "https://viewer.archilogic.com/?sceneId=80d4b3bb-98de-4279-a867-633bf67c6e72&s=m2fss0p3slst",
"images": "[{\"id\": 1, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/1BEEA0B6-A2B1-4D5E-837B-9C2B00F46EE4_2048x2048.jpg\"},\n{\"id\": 2, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/984D2D29-2448-4B68-827F-EC912AB9AF14_2048x2048.jpg\"},\n{\"id\": 3, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-_0002_Layer_11_2048x2048.jpg\"},\n{\"id\": 4, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-_0002_Layer_10_ad2d92e2-3740-4d1d-8e9c-ed41cf89c3b2_2048x2048.jpg\"},\n{\"id\": 5, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0001_Layer_21_2048x2048.jpg\"},\n{\"id\": 6, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0044_Layer_5_2048x2048.jpg\"},\n{\"id\": 7, \"image\":\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0042_Layer_3_2048x2048.jpg\"}]"
> Blockquote
images is a nested JSON string which has to be decoded separately.
A solution is to declare the value containing the nested JSON string as struct (ImageJSON) with a singleValueContainer and decode the string on second level.
I left out the properties which are not in the JSON
let json = """
{
"success" : true,
"result" : {
"name": "Al Deyar",
"description": "Al Deyar Villa",
"propertyType": {
"id": 709415277471,
"name": "villa",
"description": "villa"
},
"latitude": "1",
"longitude": "2",
"viewOfWater": null,
"threeDModel": "https://viewer.archilogic.com/?sceneId=80d4b3bb-98de-4279-a867-633bf67c6e72&s=m2fss0p3slst",
"images":"[{\\"id\\": 1, \\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/1BEEA0B6-A2B1-4D5E-837B-9C2B00F46EE4_2048x2048.jpg\\"},{\\"id\\": 2,\\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/984D2D29-2448-4B68-827F-EC912AB9AF14_2048x2048.jpg\\"},{\\"id\\": 3,\\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-_0002_Layer_11_2048x2048.jpg\\"},{\\"id\\": 4,\\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-_0002_Layer_10_ad2d92e2-3740-4d1d-8e9c-ed41cf89c3b2_2048x2048.jpg\\"},{\\"id\\": 5,\\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0001_Layer_21_2048x2048.jpg\\"},{\\"id\\": 6,\\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0044_Layer_5_2048x2048.jpg\\"},{\\"id\\": 7,\\"image\\":\\"https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0042_Layer_3_2048x2048.jpg\\"}]"
}
}
"""
struct Response : Decodable {
let result: Property
let success: Bool
}
struct Property: Decodable {
let name: String
let description: String
let propertyType: PropertyType
let latitude, longitude: String
let images: ImageJSON
let threeDModel: URL
}
struct PropertyType: Codable {
let id: Int
let name, description: String
}
struct Image : Decodable {
let id : Int
let image : URL
}
struct ImageJSON : Decodable {
let images : [Image]
init(from decoder : Decoder) throws {
let container = try decoder.singleValueContainer()
let imageJSONString = try container.decode(String.self)
let imageJSONData = Data(imageJSONString.utf8)
images = try JSONDecoder().decode([Image].self, from: imageJSONData)
}
}
let data = Data(json.utf8)
do {
let decoder = JSONDecoder()
let response = try decoder.decode(Response.self, from: data)
let images = response.result.images.images
print(images)
} catch {
print(error)
}
Try the below code :
Update your json :
let json = """
{
"name": "Al Deyar",
"description": "Al Deyar Villa",
"propertyType": {
"id": 709415277471,
"name": "villa",
"description": "villa"
},
"latitude": "1",
"longitude": "2",
"viewOfWater": null,
"threeDModel":"https://viewer.archilogic.com/?sceneId=80d4b3bb-98de-4279-a867-633bf67c6e72&s=m2fss0p3slst",
"images": [
{"id": 1, "image": "https://neigborhood-images.s3.amazonaws.com/property/1BEEA0B6-A2B1-4D5E-837B-9C2B00F46EE4_2048x2048.jpg"},
{"id": 2, "image": "https://neigborhood-images.s3.amazonaws.com/property/984D2D29-2448-4B68-827F-EC912AB9AF14_2048x2048.jpg"},
{"id": 3, "image": "https://neigborhood-images.s3.amazonaws.com/property/EBARZA-_0002_Layer_11_2048x2048.jpg"},
{"id": 4, "image": "https://neigborhood-images.s3.amazonaws.com/property/EBARZA-_0002_Layer_10_ad2d92e2-3740-4d1d-8e9c-ed41cf89c3b2_2048x2048.jpg"},
{"id": 5, "image": "https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0001_Layer_21_2048x2048.jpg"},
{"id": 6, "image": "https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0044_Layer_5_2048x2048.jpg"},
{"id": 7, "image": "https://neigborhood-images.s3.amazonaws.com/property/EBARZA-furniture_0042_Layer_3_2048x2048.jpg"}
]
}
"""
Update your codable class :
struct Property : Codable {
let descriptionField : String?
let images : [Images]?
let latitude : String?
let longitude : String?
let name : String?
let propertyType : PropertyType?
let threeDModel : String?
let viewOfWater : String?
enum CodingKeys: String, CodingKey {
case descriptionField = "description"
case images = "images"
case latitude = "latitude"
case longitude = "longitude"
case name = "name"
case propertyType = "propertyType"
case threeDModel = "threeDModel"
case viewOfWater = "viewOfWater"
}
}
struct PropertyType : Codable {
let descriptionField : String?
let id : Int?
let name : String?
enum CodingKeys: String, CodingKey {
case descriptionField = "description"
case id = "id"
case name = "name"
}
}
struct Images : Codable {
let id : Int?
let image : String?
}
if let jsonData = json.data(using: .utf8) {
let decoder = JSONDecoder()
do {
let jsonModel = try decoder.decode(Property.self, from: jsonData)
print(jsonModel)
} catch {
print("Error = \(error)")
}
}
Create an image type as below,
struct PropertyImage: Decodable {
var id: Int
var image: String?
}
Now, get data from the images string and decode an array of property images as below,
if let data = property.images.data(using: .utf8) {
do {
let images = try JSONDecoder().decode([PropertyImage].self, from: data)
images.forEach { image in
print(image.id)
print(image.image)
}
} catch {
print(error)
}
}
I can't find online how to store an array of objects so that the key "line_items" presents numbers for each menuItem with values for each menuItem corresponding to its own number. In other words, I need the numbers to come after line_items rather than the nested key so that each individual MenuItem object can be quickly referenced. I found online how to make it so each key has an array of values, but I need line_items to have an array of MenuItem objects. The following code crashes:
public func uploadTransactionData(_ menuItems: [MenuItem], balanceId: String, subTotal: Int, completion: #escaping (() -> ())) {
guard let userId = Auth.auth().currentUser?.uid else { completion(); return }
let utilitiesManager = UtilitiesManager()
let timestamp = utilitiesManager.timestamp()
let params: [String: Any] = ["date": "\(timestamp)",
"balance_id": "\(balanceId)",
"subtotal": "\(subTotal)",
"user_id": "\(userId)",
"line_items": menuItems
]
Firestore.firestore().document("transaction_history/\(timestamp)").setData(params)
{ err in
if let e = err {
print("$-- error creating user \(e)")
completion()
} else {
completion()
}
}
}
Here's the MenuItem model:
struct MenuItem {
let itemId: String
let name: String
var modifiers: [String]?
var photoName: String?
var photoUrl: String?
var quantity: Int
var price: Int
var sizeAddOnPrice: Int
var toppingsAddOnPrice: Int
let description: String
var size: String
let category: String
init(itemId: String, name: String, modifiers: [String]?, photoName: String?, photoUrl: String?, quantity: Int, price: Int, sizeAddOnPrice: Int, toppingsAddOnPrice: Int, description: String, size: String, category: String) {
self.itemId = itemId
self.name = name
self.modifiers = modifiers
self.photoName = photoName
self.photoUrl = photoUrl
self.quantity = quantity
self.price = price
self.sizeAddOnPrice = sizeAddOnPrice
self.toppingsAddOnPrice = toppingsAddOnPrice
self.description = description
self.size = size
self.category = category
}
Problem:
Your app is crashing because you are trying to save user defined object MenuItem to Firestore. Firestore doesn't allow it. Firestore only supports this datatypes.
Solution:
You can convert your custom object MenuItem to Firestore supported datatypes.
You can do this by making following changes to your code.
Make MenuItem confirm to Codable protocol.
struct MenuItem: Codable {
// Your code as it is.
}
Make following changes to your uploadTransactionData() function:
public func uploadTransactionData(_ menuItems: [MenuItem], balanceId: String, subTotal: Int, completion: #escaping (() -> ())) {
let userId = Auth.auth().currentUser?.uid else { completion(); return }
let utilitiesManager = UtilitiesManager()
let timestamp = utilitiesManager.timestamp()
var list_menuItem = [Any]()
for item in menuItems {
do {
let jsonData = try JSONEncoder().encode(item)
let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: [])
list_menuItem.append(jsonObject)
}
catch {
// handle error
}
}
let params: [String: Any] = ["date": "\(timestamp)",
"balance_id": "\(balanceId)",
"subtotal": "\(subTotal)",
"user_id": "\(userId)",
"line_items": list_menuItem
]
Firestore.firestore().document("transaction_history/\(timestamp)").setData(params)
{ err in
if let e = err {
print("$-- error creating user \(e)")
completion()
} else {
completion()
}
}
}
This is because Firestore doesn't know how to save the value: menuItems
you can map it like this: "objectExample": [
"a": 5,
"b": [
"nested": "foo"
]
]
or:
.setData([
"name": "Frank",
"favorites": [ "food": "Pizza", "color": "Blue", "subject": "recess" ],
"age": 12
])
I have the json list of :
[
{
"title": "Languages",
"name_list": [
{
"title": "Hindi",
"locked": true
},
{
"title": "Hindi1",
"locked": true
},
{
"title": "Hindi2",
"locked": false
},
{
"title": "Hindi3",
"locked": true
},
{
"title": "Hindi4",
"locked": false
}],
},
{
"title": "Subject",
"name_list": [
{
"title": "Hindi4",
"locked": false
},
{
"title": "Hindi4",
"locked": true
},
{
"title": "Hindi4",
"locked": true
}]
}
]
I am using table view and inside table view i have collection view.
Now in my table view i will show the title .And in my collection view i will show the name_list title on each collection view cell. Now it's working fine.
What i needs is, as you can see in the json, that have many locked = true and false.
So, In my table view- which object have less locked == false. that title i needs to show as first. For example.
In my above json, title = subject have only one locked = false, but title = language have 2 locked = false. So in my table view first subject have to display and language.
Same in each collection view, which object have locked = false that needs to display first in my collection view cell.
Any help please.
Here is my code of table view :
cellTitleLabel.text = jsonData?["title"] as? String ?? ""
in my collection view :
if let gamesList = jsonData?["name_list"] as? [[String: Any]] {
let Info = nameList[indexPath.item]
collectionCell.cellTitleLabel.text = Info["title"] as? String ?? ""
}
var Info: [String: Any]?{
didSet {
cellTitleLabel.text = jsonData?["title"] as? String ?? ""
collectionView.reloadData();
guard let gamesList = jsonData?["name_list"] as? [[String: Any]] else {
return
}
}
}
My solution here is to create a function in your tableview cell and call it at cellForRowAt indexPath with the corresponding array. And you can call the function simply by cell. fillCollectionView(array).
class ExampleTableViewCell: UITableViewCell {
#IBOutlet weak var exampleCollectionView: UICollectionView!
var array = [[String:Any]]()
func fillCollectionView(with array: [[String:Any]]) {
self.array = array
exampleCollectionView()
}
In this case, If you want to display locked == false as first then you need to rearrange your dictionary of array(sort by locked). and then load data into UItableview and UIcollectionview
Just use this function and it will work for you without creating any models.
func sortArr(array : [[String:Any]]) -> [[String:Any]]{
var arr = array
for i in 0...arr.count - 1 {
var dict = arr[i]
let name_list = dict["name_list"] as! [[String:Any]]
let lockedItems = name_list.filter { (item) -> Bool in
let locked = item["locked"] as! Bool
if locked == false {
return true
}
return false
}
print(lockedItems.count)
dict["lockedItems"] = lockedItems.count
arr[i] = dict
}
arr = arr.sorted(by: { (item1, item2) -> Bool in
let lockedcount1 = item1["lockedItems"] as! Int
let lockedcount2 = item2["lockedItems"] as! Int
return lockedcount1 < lockedcount2
})
return arr
}
EDITED
import UIKit
var arr = [
[
"title": "Languages",
"name_list": [
[
"title": "Hindi",
"locked": true
],
[
"title": "Hindi1",
"locked": true
],
[
"title": "Hindi2",
"locked": false
],
[
"title": "Hindi3",
"locked": true
],
[
"title": "Hindi4",
"locked": false
]],
],
[
"title": "Subject",
"name_list": [
[
"title": "Hindi4",
"locked": false
],
[
"title": "Hindi4",
"locked": true
],
[
"title": "Hindi4",
"locked": true
]]
]
]
func sortArr(array : [[String:Any]]) -> [[String:Any]]{
var arr = array
for i in 0...arr.count - 1 {
var dict = arr[i]
let name_list = dict["name_list"] as! [[String:Any]]
let lockedItems = name_list.filter { (item) -> Bool in
let locked = item["locked"] as! Bool
if locked == false {
return true
}
return false
}
print(lockedItems.count)
dict["lockedItems"] = lockedItems.count
arr[i] = dict
}
arr = arr.sorted(by: { (item1, item2) -> Bool in
let lockedcount1 = item1["lockedItems"] as! Int
let lockedcount2 = item2["lockedItems"] as! Int
return lockedcount1 < lockedcount2
})
return arr
}
var array = sortArr(array: arr)
print(array)
I used this below json for dummy
[
{
"title": "Languages",
"name_list": [
{
"title": "Sub 1",
"locked": true
},
{
"title": "Sub 2",
"locked": true
},
{
"title": "Sub 3",
"locked": false
},
{
"title": "Sub 4",
"locked": true
},
{
"title": "Sub 5",
"locked": false
}
]
},
{
"title": "Subject",
"name_list": [
{
"title": "Sub 6",
"locked": false
},
{
"title": "Sub 7",
"locked": false
},
{
"title": "Sub 8",
"locked": false
}
]
}
]
Model Struct for this json :
public struct TestModel {
public var nameList : [NameList]
public var title : String
}
public struct NameList {
public var locked : Bool
public var title : String
}
My json is in local project, so I use this below func to
func getLocalData(){
let url = Bundle.main.url(forResource: "testjson", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: jsonData) as! [Any]
self.arrData = self.createDataModel(json)
}
catch {
print(error)
}
}
Func to bind the json into model struct
func createDataModel (_ json : [Any]) -> [TestModel] {
var arr = [TestModel]()
for adata in json {
let aDictItems = adata as! [String : Any]
let aNameListData = aDictItems["name_list"] as! [Any]
var arrNameList = [NameList]()
for aName in aNameListData {
let adictname = aName as! [String : Any]
arrNameList.append(NameList.init(locked: adictname["locked"] as! Bool, title: adictname["title"] as! String ))
}
arr.append(TestModel.init(nameList: arrNameList, title: aDictItems["title"] as! String))
}
return arr
}
And in last, func that sort your list into the number of locked count (false) and return back filtered data.
func filterData (_ searchText : String) -> [TestModel] {
if searchText == "" { return arrData }
let s = arrData.sorted { (res1, res2) -> Bool in
var count1 = 0, count2 = 0
for r in res1.nameList {
if !r.locked {
count1 += 1
}
}
for r in res2.nameList {
if !r.locked {
count2 += 1
}
}
return count1 > count2
}
let f = s.filter {
$0.nameList.contains(where: { (aNameRes) -> Bool in
aNameRes.title.contains(searchText)
})
}
return f
}
Edit : To Show data in cell
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return filtered.Count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
:
// filtered[indexPath.row].title // your tableview cell title
// filtered[indexPath.item].nameList // your collectView Data, Use this array to show data in collectionview
}
Note: Your API json Data and filtered data is always be same. When there is no string in the search bar then you need to toggle filtered data with api original data.
If something change in your requirement then let me know.
struct Root {
let nameList: [NameList]
let title : String
init(dictionary: [String: Any]) {
self.title = dictionary["title"] as? String ?? ""
self.nameList = (dictionary["name_list"] as? [[String:Any]] ?? []).map(NameList.init)
}
}
struct NameList {
let locked: Bool
let title: String
init(dictionary: [String: Any]) {
self.locked = dictionary["locked"] as? Bool == true
self.title = dictionary["title"] as? String ?? ""
}
}
let jsonData = Data("""
[
{
"title" : "Languages",
"name_list" : [
{
"title" : "Hindi",
"locked" : true
},
{
"title" : "Hindi1",
"locked" : true
},
{
"title" : "Hindi2",
"locked" : false
},
{
"title" : "Hindi3",
"locked" : true
},
{
"title" : "Hindi4",
"locked" : false
}
]
},
{
"title" : "Subject",
"name_list" : [
{
"title" : "Hindi4",
"locked" : false
},
{
"title" : "Hindi4",
"locked" : true
},
{
"title" : "Hindi4",
"locked" : true
}
]
}
]
""".utf8)
let dictionaries = (try? JSONSerialization.jsonObject(with: jsonData)) as? [[String:Any]] ?? []
print("\nAll Models", terminator: "\n============\n")
let models = dictionaries.map(Root.init)
for model in models {
print(model.title)
for name in model.nameList {
print(name.title)
}
}
print("\nunlockedModels", terminator: "\n============\n")
let unlockedDictionary: [String: [NameList]] = models.reduce(into: [:]) {
$0[$1.title] = $1.nameList.filter{ $0.locked}
}
for (title, nameList) in unlockedDictionary {
print(title)
for name in nameList {
print(name.title)
}
}
I'm trying to take my JSON from a HTTP POST and put it in a multidimensional array to use for sections / table cells in Swift.
I would like each table section to use these dynamic keys (submitid) and insert the cell data for each:
15302992338145
15301374235890
15302930963080
My JSON:
let swiftyJsonVar = JSON(data!)
{
"data" : {
"15302992338145" : [
{
"date" : "2018-06-27",
"username" : "user1",
"submitid" : 15302992338145,
"notes" : "Testing"
},
{
"date" : "2018-06-28",
"username" : "user1",
"submitid" : 15302992338145,
"notes" : "Testing"
}
],
"15301374235890" : [
{
"date" : "2018-06-21",
"username" : "user2",
"submitid" : 15301374235890,
"notes" : "Comments one two three"
},
{
"date" : "2018-06-22",
"username" : "user2",
"submitid" : 15301374235890,
"notes" : "N/A"
}
],
"15302930963080" : [
{
"date" : "2018-07-03",
"username" : "user3",
"submitid" : 15302930963080,
"notes" : "Hello"
}
]
}
}
I've tried but with no luck:
if let resData = swiftyJsonVar["data"][].arrayObject {
self.arrRes = resData as! [String: [[String:AnyObject]]]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return arrRes.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("tableCell", forIndexPath: indexPath)
// Configure the cell...
var dict = arrRes[indexPath.section][indexPath.row]
cell.dateLabel?.text = dict["date"]
return cell
}
You should stop using SwiftyJSON and move up to Swift 4 and Decodable:
struct User : Decodable {
let date : String
let username : String
let submitid : Int
let notes : String
}
struct Result : Decodable {
let data : [[User]]
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: AnyCodingKey.self)
let intermediate = try! con.decode([String:[User]].self,
forKey: AnyCodingKey(stringValue:"data"))
var data = [[User]]()
for d in intermediate {
data.append(d.value)
}
self.data = data
}
}
// jsondata is your original JSON data, as you downloaded it
let result = try! JSONDecoder().decode(Result.self, from: jsondata)
After that, result.data is an array of array of User.
[[User(date: "2018-07-03", username: "user3",
submitid: 15302930963080, notes: "Hello")],
[User(date: "2018-06-27", username: "user1",
submitid: 15302992338145, notes: "Testing"),
User(date: "2018-06-28", username: "user1",
submitid: 15302992338145, notes: "Testing")],
[User(date: "2018-06-21", username: "user2",
submitid: 15301374235890, notes: "Comments one two three"),
User(date: "2018-06-22", username: "user2",
submitid: 15301374235890, notes: "N/A")]]
My dilemma is I'm receiving two different object types from one table in a JSON response. Here is an example of the response of both types in a return.
"supplementaryItems": [
{
"header": "Doodle",
"subHeader": "It's a drawing.",
"slideID": 4,
"imageName": null,
"textItems": null,
"sortOrder": 0
},
{
"header": "Cell Phones",
"subHeader": "No phones please",
"slideID": 8,
"imageName": "welcome_icon_cellphones",
"textItems": ["first","second","third"],
"sortOrder": 1
}
]
What we're hoping to do is create two different types of objects here. A textOnlyItem, and a imageWithTextItem.
Is there a way to create one as a subclass or extension that can be keyed off of a Bool defined by whether imageName is null or not?
Thanks for any help all.
You don't need two different objects. Just declare imageName and textItems as optional, this handles the null case.
You can simply check whether imageName is nil
let jsonString = """
{"supplementaryItems": [
{
"header": "Doodle",
"subHeader": "It's a drawing.",
"slideID": 4,
"imageName": null,
"textItems": null,
"sortOrder": 0
},
{
"header": "Cell Phones",
"subHeader": "No phones please",
"slideID": 8,
"imageName": "welcome_icon_cellphones",
"textItems": ["first","second","third"],
"sortOrder": 1
}
]
}
"""
struct Root : Decodable {
let supplementaryItems : [SupplementaryItem]
}
struct SupplementaryItem : Decodable {
let header : String
let subHeader : String
let slideID : Int
let imageName : String?
let textItems : [String]?
let sortOrder : Int
}
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
for item in result.supplementaryItems {
if let imageName = item.imageName {
print(imageName + " has text items")
} else {
print(item.header + " has no text items")
}
}
} catch { print(error) }
I actually like vadian's approach, of one type. But I gather that would require significant refactoring in your situation.
The other approach is to just use JSONSerialization and build your heterogeneous array manually. JSONSerialization isn't deprecated, it just doesn't do it automatically like JSONDecoder.
Another approach is to use JSONDecoder, writing custom initializer that tries to decode it as ImageItem, and if that fails, try decoding it as TextItem:
protocol SupplementaryItem {
var header: String { get }
var subHeader: String { get }
var slideID: Int { get }
var sortOrder: Int { get }
var textItems: [String]? { get }
}
struct TextItem: SupplementaryItem, Codable {
let header: String
let subHeader: String
let slideID: Int
let sortOrder: Int
let textItems: [String]?
}
struct ImageItem: SupplementaryItem, Codable {
let header: String
let subHeader: String
let slideID: Int
let sortOrder: Int
let textItems: [String]?
let imageName: String
}
struct ResponseObject: Decodable {
let supplementaryItems: [SupplementaryItem]
enum CodingKeys: String, CodingKey {
case supplementaryItems
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
var container = try values.nestedUnkeyedContainer(forKey: .supplementaryItems)
if container.count == nil {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "Expected array for supplementaryItems")
}
var items = [SupplementaryItem]()
while !container.isAtEnd {
if let item = try? container.decodeIfPresent(ImageItem.self), let imageItem = item {
items.append(imageItem)
} else {
let textItem = try container.decode(TextItem.self)
items.append(textItem)
}
}
supplementaryItems = items
}
}
Then:
let string = """
{
"supplementaryItems": [
{
"header": "Doodle",
"subHeader": "It's a drawing.",
"slideID": 4,
"imageName": "foo",
"textItems": null,
"sortOrder": 0
},
{
"header": "Cell Phones",
"subHeader": "No phones please",
"slideID": 8,
"imageName": "welcome_icon_cellphones",
"textItems": ["first","second","third"],
"sortOrder": 1
}
]
}
"""
let data = string.data(using: .utf8)!
let json = try! JSONDecoder().decode(ResponseObject.self, from: data)
print(json)
I'm not convinced this is any better or worse than just using JSONSerialization, but it's another approach.