I've created NSObject class for "CurrentUser",
here, sign-up API will call & response data will insert into the "CurrentUser" model.
This is one type of global model, like in any screen user_logic_details will fetch, modify & save.
Example :
let loginuser : CurrentUser = CurrentUser.getLoginData()!
loginuser.email = "abc#gmail.com"
CurrentUser.saveLoginData(loginData: loginuser)
// working
let email = CurrentUser.getLoginData()?.email ?? "" // i'll get "abc#gmail.com"
in the above example if I write the below code then it shows nil data
let loginuser : CurrentUser = CurrentUser.getLoginData()!
loginuser.email = "abc#gmail.com"
CurrentUser.saveLoginData(loginData: loginuser)
// working
let email = CurrentUser.getLoginData()?.email ?? "" // i'll get "abc#gmail.com"
// not working
let roleName = CurrentUser.getLoginData()?.roles?.name ?? "" // showing get ""
I'm not able to find exact issues here,
Check below the code of how I use the model class, saving data into UserDefaults & retrieve data from the model.
class CurrentUser: NSObject, NSCoding, NSKeyedUnarchiverDelegate {
var email : String!
var roles : UserRole!
private var _isLoggedIn = false
required convenience init(coder aDecoder: NSCoder) {
self.init()
roles = aDecoder.decodeObject(forKey: "roles") as? UserRole
email = aDecoder.decodeObject(forKey: "email") as? String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(email, forKey: "email")
aCoder.encode(roles, forKey: "roles")
}
class func getLoginData() -> CurrentUser? {
let userDefaults = UserDefaults.standard
if let UserData = userDefaults.object(forKey: "loginUser") {
guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(UserData as! Data)
else {
return nil
}
return unarchivedFavorites as? CurrentUser
} else {
return CurrentUser()
}
}
class func saveLoginData(loginData: CurrentUser?) {
do {
let encodedData = try NSKeyedArchiver.archivedData(withRootObject: loginData as Any, requiringSecureCoding: false)
let userDefaults: UserDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: "loginUser")
userDefaults.synchronize()
} catch {
print("Couldn't write file")
}
}
func unarchiver(_ unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
print("classNames", classNames)
return nil
}
}
class UserRole: NSObject, NSCoding {
var name : String!
required convenience init(coder aDecoder: NSCoder) {
self.init()
name = aDecoder.decodeObject(forKey: "name") as? String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
}
}
The above model is for reference purposes.
I need to convert the below Response to the whole model class
{
"addressList": [
{
"addressLabel": "string",
"city": "string",
"country": "string",
"createdAt": "2022-03-16T12:10:41.148Z",
"homePhone": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDefault": true,
"isDeleted": true,
"state": "string",
"streetAddress": "string",
"updatedAt": "2022-03-16T12:10:41.148Z",
"version": 0,
"zip": "string"
}
],
"ageRange": "string",
"badges": [
{
"badge": {
"createdAt": "2022-03-16T12:10:41.148Z",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"image": "string",
"isDeleted": true,
"label": "string",
"name": "string",
"status": "ACTIVE",
"updatedAt": "2022-03-16T12:10:41.148Z",
"users": [
null
],
"version": 0
},
"userBadgeId": {
"badgeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
],
"classesTaken": 0,
"createdAt": "2022-03-16T12:10:41.148Z",
"deviceList": [
{
"createdAt": "2022-03-16T12:10:41.148Z",
"deviceId": "string",
"deviceName": "string",
"deviceType": "ANDROID",
"display": "string",
"fcmToken": "string",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDeleted": true,
"osVersion": "string",
"updatedAt": "2022-03-16T12:10:41.148Z",
"version": 0
}
],
"displayName": "string",
"displayPic": "string",
"dob": "2022-03-16T12:10:41.148Z",
"email": "string",
"experience": 0,
"firebaseToken": "string",
"firebaseUserId": "string",
"followerCount0l": 0,
"fullName": "string",
"gender": "FEMALE",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDeleted": true,
"lastLoginTime": "2022-03-16T12:10:41.148Z",
"location": "string",
"phoneNumber": "string",
"roles": [
{
"createdAt": "2022-03-16T12:10:41.148Z",
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"isDeleted": true,
"name": "ROLE_ADMIN",
"updatedAt": "2022-03-16T12:10:41.148Z",
"version": 0
}
],
"rubyBalance": 0,
"signupDateTime": "2022-03-16T12:10:41.148Z",
"signupSource": {
"buildNumber": "string",
"deviceId": "string",
"deviceType": "ANDROID",
"osVersion": "string"
},
"status": "ACTIVE",
"studentsTaught": 0,
"updatedAt": "2022-03-16T12:10:41.148Z",
"userInterest": [
"string"
],
"userName": "string",
"version": 0
}
If anyone has a better way to do it or a better solution, please answer it.
Your code reads like Objective-C from a few years ago. Something more modern and Swift-like might be:
#propertyWrapper
struct UserDefaultsCodableWrapper<T: Codable> {
let key: String
let defaultValue: T?
init(_ key: String, defaultValue: T?) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T? {
get {
guard let data = UserDefaults.standard.data(forKey: key),
let decoded = try? JSONDecoder().decode(T.self, from: data)
else { return defaultValue }
return decoded
}
set {
let encoded = try? JSONEncoder().encode(newValue)
UserDefaults.standard.set(encoded, forKey: key)
}
}
}
struct User: Codable {
var email: String // TODO make a codable "Email" struct type that validates its input, no invalid strings allowed
var roles: [UserRole]
}
struct UserRole: Codable {
var name: String
}
class AppState {
#UserDefaultsCodableWrapper("CurrentUser", defaultValue: nil)
var currentUser: User?
}
class ViewController: UIViewController {
let state = AppState()
override func viewDidLoad() {
super.viewDidLoad()
print(String(describing: state.currentUser)) // nil
state.currentUser = User(email: "foo#bar.baz", roles: [.init(name: "Foo"), .init(name: "Bar")])
print(String(describing: state.currentUser)) // Optional(User...
Also worth pointing you to app.quicktype.io which can generate you codable models from your json.
Related
I would like to pass the parent area Id to it's children areas while parsing the nested JSON structure as per the attached response, Here I would like to insert 'parentId' for each children which will link to it's immediate parent area,
{
"areas": [
{
"id": "271341877549072423",
"name": "Breeze Office Tower",
"children": [
{
"id": "271341877549072424",
"name": "100 flinders street",
"position": 0,
"children": []
},
{
"id": "271341877549130929",
"name": "100 flinders street",
"position": 1,
"children": []
},
{
"id": "271341877549072425",
"name": "100 Flinder Stree",
"position": 2,
"children": [
{
"id": "271341877549072426",
"name": "Büro",
"position": 0,
"children": [
{
"id": "271341877549072427",
"name": "Dachgeschoß",
"position": 0,
"children": []
}
]
}
]
},
{
"id": "271341877549130931",
"name": "100 Flinder Stree",
"position": 3,
"children": [
{
"id": "271341877549130933",
"name": "Büro",
"position": 0,
"children": [
{
"id": "271341877549130935",
"name": "Dachgeschoß",
"position": 0,
"children": []
}
]
}
]
}
]
}
]
}
My JSON Codable model struct looks like,
struct AreaModel: Decodable {
var areas: [NestedAreaModel]?
}
struct NestedAreaModel: Codable {
let areaId: String
let areaName: String
let children: [NestedAreaModel]
let hasChildren: Bool
var areaPosition: Int16?
var parentId: String?
var projectId: String?
enum CodingKeys: String, CodingKey {
case areaId = "id"
case areaName = "name"
case areaPosition = "position"
case children
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.areaId = try values.decode(String.self, forKey: .areaId)
self.children = try values.decode([NestedAreaModel].self, forKey: .children)
self.areaName = try values.decode(String.self, forKey: .areaName)
self.projectId = ORAUserDefaults.selectedProjectId()
self.areaPosition = try values.decodeIfPresent(Int16.self, forKey: .areaPosition)
if !self.children.isEmpty {
self.hasChildren = true
self.parentId = self.areaId
} else {
self.hasChildren = false
}
}
}
Here I am not able to set the parent Id, its pointing its own id always.
As I allready pointed out in the comments you would need to iterate over the decoded children and set their respective parentId to the current areaId.
One possible Solution would be:
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.areaId = try values.decode(String.self, forKey: .areaId)
// decode the children to a local var
var children = try values.decode([NestedAreaModel].self, forKey: .children)
self.areaName = try values.decode(String.self, forKey: .areaName)
self.projectId = ORAUserDefaults.selectedProjectId()
self.areaPosition = try values.decodeIfPresent(Int16.self, forKey: .areaPosition)
// if there are children loop over them and assign your id
if !children.isEmpty {
self.hasChildren = true
for (index, _ ) in children.enumerated(){
children[index].parentId = areaId
}
} else {
self.hasChildren = false
}
// assign to self
self.children = children
}
Tested with the given JSON:
let decoded = try JSONDecoder().decode(AreaModel.self, from: data)
print(decoded.areas?[0].areaId)
print(decoded.areas?[0].children[0].parentId)
Result:
Optional("271341877549072423")
Optional("271341877549072423")
Remarks:
Regarding your comment on complexity:
children[index].parentId = areaId runs exactly once per child no matter of the level deapth. So this function is (O)n.
I am currently building a food app using Yelp Fusion API. There are two models called Business and BusinessDatail in my project. To list up restaurants in a tableView, I fetch a restaurant list from backend and custom decode it as an array of Business, [Business]. And then, when a tableViewCell of Business is clicked, I fetch detailed business info and custom decode it as a BusinessDetail.
struct Business {
let id: String
let name: String
let rating: Float?
let price: String?
let displayPhone: String?
let imageUrl: URL
let category: String
let reviewCount: Int
let coordinates: Coordinates
let address: Address
}
struct BusinessDetail {
let id: String
let name: String
let rating: Float?
let price: String?
let displayPhone: String?
let imageUrl: URL
let category: String
let reviewCount: Int
let coordinates: Coordinates
let address: Address
let isClaimed: Bool
let isClosed: Bool
let url: URL
let phone: String?
let photoURLs: [URL]
var hours: [Hours]?
}
Both Business and BusinessDetail are custom decoded models and they are currently initialised this way.
extension Business: Decodable {
enum CodingKeys: String, CodingKey {
case id
case name
case rating
case price
case displayPhone = "display_phone"
case imageUrl = "image_url"
case category = "categories"
case reviewCount = "review_count"
case coordinates
case address = "location"
enum CategoryCodingKeys: String, CodingKey {
case title
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
price = try container.decodeIfPresent(String.self, forKey: .price)
displayPhone = try container.decodeIfPresent(String.self, forKey: .displayPhone)
imageUrl = try container.decode(URL.self, forKey: .imageUrl)
var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
var tempCategory = ""
while !categoriesContainer.isAtEnd {
let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
let title = try categoryContainer.decode(String.self, forKey: .title)
tempCategory += tempCategory == "" ? title: ", \(title)"
}
category = tempCategory
reviewCount = try container.decode(Int.self, forKey: .reviewCount)
coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
address = try container.decode(Address.self, forKey: .address)
}
}
extension PlaceDetail: Decodable {
enum CodingKeys: String, CodingKey {
case id
case name
case rating
case price
case displayPhone = "display_phone"
case imageUrl = "image_url"
case category = "categories"
case reviewCount = "review_count"
case coordinates
case address = "location"
enum CategoryCodingKeys: String, CodingKey {
case title
}
case isClaimed = "is_claimed"
case isClosed = "is_closed"
case url
case phone
case photoURLs = "photos"
case hours
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
rating = try container.decode(Float.self, forKey: .rating)
price = try container.decodeIfPresent(String.self, forKey: .price)
displayPhone = try container.decode(String.self, forKey: .displayPhone)
imageUrl = try container.decode(URL.self, forKey: .imageUrl)
var categoriesContainer = try container.nestedUnkeyedContainer(forKey: .category)
var tempCategory = ""
while !categoriesContainer.isAtEnd {
let categoryContainer = try categoriesContainer.nestedContainer(keyedBy: CodingKeys.CategoryCodingKeys.self)
let title = try categoryContainer.decode(String.self, forKey: .title)
tempCategory += tempCategory == "" ? title: ", \(title)"
}
category = tempCategory
reviewCount = try container.decode(Int.self, forKey: .reviewCount)
coordinates = try container.decode(Coordinates.self, forKey: .coordinates)
address = try container.decode(Address.self, forKey: .address)
isClaimed = try container.decode(Bool.self, forKey: .isClaimed)
isClosed = try container.decode(Bool.self, forKey: .isClosed)
url = try container.decode(URL.self, forKey: .url)
phone = try container.decode(String.self, forKey: .phone)
photoURLs = try container.decode([URL].self, forKey: .photoURLs)
hours = try container.decodeIfPresent([Hours].self, forKey: .hours)
}
}
Here is the json for a business list.
{
"businesses": [
{
"id": "I1D8NHvMWf8oMYceTyLlHg",
"name": "John Mills Himself",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"review_count": 55,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
},
{
"alias": "bakeries",
"title": "Bakeries"
},
{
"alias": "wine_bars",
"title": "Wine Bars"
}
],
"rating": 4.5,
"coordinates": {
"latitude": -27.47151,
"longitude": 153.025654
},
"transactions": [],
"price": "$",
"location": {
"address1": "40 Charlotte St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"40 Charlotte St",
"Brisbane Queensland 4000",
"Australia"
]
},
"phone": "",
"display_phone": "",
"distance": 383.2254392716822
},
{
"id": "KaIoCOg-IZJtLdN39Cw__Q",
"alias": "strauss-brisbane",
"name": "Strauss",
"image_url": "https://s3-media2.fl.yelpcdn.com/bphoto/eYKx68uOaEY5k9Jt4TrQPw/o.jpg",
"is_closed": false,
"url": "https://www.yelp.com/biz/strauss-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_search&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"review_count": 33,
"categories": [
{
"alias": "cafes",
"title": "Cafes"
}
],
"rating": 4.5,
"coordinates": {
"latitude": -27.469804,
"longitude": 153.027384
},
"transactions": [],
"price": "$",
"location": {
"address1": "189 Elizabeth St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"189 Elizabeth St",
"Brisbane Queensland 4000",
"Australia"
]
},
"phone": "+61732365232",
"display_phone": "+61 7 3236 5232",
"distance": 247.3760157828213
}
}
And this is json response for individual BusinessDetail.
{
"id": "I1D8NHvMWf8oMYceTyLlHg",
"name": "John Mills Himself",
"image_url": "https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"is_claimed": false,
"is_closed": false,
"url": "https://www.yelp.com/biz/john-mills-himself-brisbane?adjust_creative=0mCaOEYvfM9_oOaXgMuW6A&utm_campaign=yelp_api_v3&utm_medium=api_v3_business_lookup&utm_source=0mCaOEYvfM9_oOaXgMuW6A",
"phone": "",
"display_phone": "",
"review_count": 55,
"categories": [
{
"alias": "coffee",
"title": "Coffee & Tea"
},
{
"alias": "bakeries",
"title": "Bakeries"
},
{
"alias": "wine_bars",
"title": "Wine Bars"
}
],
"rating": 4.5,
"location": {
"address1": "40 Charlotte St",
"address2": "",
"address3": "",
"city": "Brisbane",
"zip_code": "4000",
"country": "AU",
"state": "QLD",
"display_address": [
"40 Charlotte St",
"Brisbane Queensland 4000",
"Australia"
],
"cross_streets": ""
},
"coordinates": {
"latitude": -27.47151,
"longitude": 153.025654
},
"photos": [
"https://s3-media3.fl.yelpcdn.com/bphoto/OH84e6eP8zpzBECF0WvTXA/o.jpg",
"https://s3-media1.fl.yelpcdn.com/bphoto/L8dJYv1GCW1m5IvD0M3qXw/o.jpg",
"https://s3-media4.fl.yelpcdn.com/bphoto/oUH4cJmPRuAs_jv_xRtXQg/o.jpg"
],
"price": "$",
"hours": [
{
"open": [
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 0
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 1
},
{
"is_overnight": false,
"start": "1600",
"end": "2100",
"day": 1
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 2
},
{
"is_overnight": false,
"start": "1600",
"end": "2100",
"day": 2
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 3
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 3
},
{
"is_overnight": false,
"start": "0630",
"end": "1530",
"day": 4
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 4
},
{
"is_overnight": false,
"start": "1600",
"end": "2330",
"day": 5
}
],
"hours_type": "REGULAR",
"is_open_now": true
}
]
}
As you can see, BusinessDetail has exactly same info as Business at the top and I'd like to change BusinessDetail like below.
struct BusinessDetail {
let business: Business
let isClaimed: Bool
let isClosed: Bool
let url: URL
let phone: String?
let photoURLs: [URL]
var hours: [Hours]?
}
However, I am not sure if it is technically possible.
Here's a conceptual example, but you can apply the same principle to your case, with the following JSON:
{ "a": 1, "b": 2, "c": "three" }
and these models:
struct Smaller: Decodable {
var a, b: Int
}
struct Larger: Decodable {
var smaller: Smaller
var c: String
}
Decoding Smaller doesn't need any extra work (i.e. no need to manually implement the init(from: Decoder) initializer):
let decoder = JSONDecoder()
let small = try! decoder.decode(Smaller.self, from: jsonData)
But with Larger, you need to manually do this:
struct Larger: Decodable {
var smaller: Smaller
var c: String
enum CodingKeys: CodingKey {
case c
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.smaller = try Smaller.init(from: decoder)
self.c = try container.decode(String.self, forKey: .c)
}
}
I'm new in iOS programming language and I'm trying to fetch data from a WordPress JSON into a table view. I'm encountering the error:
value of type 'Any' has no subscripts
when I try to instantiate the object of the array.
Here is the JSON:
[
{
"id": 1352,
"date": "2019-10-16T09:30:39",
"date_gmt": "2019-10-16T09:30:39",
"guid": {
"rendered": "https://wepress.comm-it.it/ddjhgtr/"
},
"modified": "2019-10-16T13:23:41",
"modified_gmt": "2019-10-16T13:23:41",
"slug": "ddjhgtr",
"status": "publish",
"type": "post",
"link": "https://wepress.comm-it.it/ddjhgtr/",
"title": "ddjhgtr",
"content": "eryyreytyvggjggvhghhh",
"excerpt": "eryyreyty",
"author": 2,
"featured_media": {
"id": 1418,
"url": "https://wepress.comm-it.it/wp-content/uploads/2019/10/10-62.jpeg"
},
"comment_status": "open",
"ping_status": "open",
"sticky": false,
"template": "",
"format": "standard",
"meta": [],
"categories": [
{
"id": 1,
"name": "Uncategorized",
"description": ""
}
],
"tags": [],
"_links": {
"self": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts/1352"
}
],
"collection": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts"
}
],
"about": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/types/post"
}
],
"author": [
{
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/users/2"
}
],
"replies": [
{
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/comments?post=1352"
}
],
"version-history": [
{
"count": 3,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts/1352/revisions"
}
],
"predecessor-version": [
{
"id": 1419,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts/1352/revisions/1419"
}
],
"wp:featuredmedia": [
{
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/media/1418"
}
],
"wp:attachment": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/media?parent=1352"
}
],
"wp:term": [
{
"taxonomy": "category",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/categories?post=1352"
},
{
"taxonomy": "post_tag",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/tags?post=1352"
},
{
"taxonomy": "difficulty-level-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/difficulty-level-course?post=1352"
},
{
"taxonomy": "category-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/category-course?post=1352"
},
{
"taxonomy": "location-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/location-course?post=1352"
},
{
"taxonomy": "duration-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/duration-course?post=1352"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
},
....(many other News objects)
{
"id": 774,
"date": "2019-10-07T07:30:51",
"date_gmt": "2019-10-07T07:30:51",
"guid": {
"rendered": "https://wepress.comm-it.it/name-here/"
},
"modified": "2019-10-07T07:30:51",
"modified_gmt": "2019-10-07T07:30:51",
"slug": "name-here",
"status": "publish",
"type": "post",
"link": "https://wepress.comm-it.it/name-here/",
"title": "name here",
"content": "desc here",
"excerpt": "desc here",
"author": 2,
"featured_media": null,
"comment_status": "open",
"ping_status": "open",
"sticky": false,
"template": "",
"format": "standard",
"meta": [],
"categories": [
{
"id": 1,
"name": "Uncategorized",
"description": ""
}
],
"tags": [],
"_links": {
"self": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts/774"
}
],
"collection": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts"
}
],
"about": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/types/post"
}
],
"author": [
{
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/users/2"
}
],
"replies": [
{
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/comments?post=774"
}
],
"version-history": [
{
"count": 0,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/posts/774/revisions"
}
],
"wp:attachment": [
{
"href": "https://wepress.comm-it.it/wp-json/wp/v2/media?parent=774"
}
],
"wp:term": [
{
"taxonomy": "category",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/categories?post=774"
},
{
"taxonomy": "post_tag",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/tags?post=774"
},
{
"taxonomy": "difficulty-level-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/difficulty-level-course?post=774"
},
{
"taxonomy": "category-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/category-course?post=774"
},
{
"taxonomy": "location-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/location-course?post=774"
},
{
"taxonomy": "duration-course",
"embeddable": true,
"href": "https://wepress.comm-it.it/wp-json/wp/v2/duration-course?post=774"
}
],
"curies": [
{
"name": "wp",
"href": "https://api.w.org/{rel}",
"templated": true
}
]
}
}
]
For the moment I'm interested to get just the image (featured_media), title and content of these "objects" and put them into the tableView. Indeed here are the structs that I created for them:
The news represents the struct contained into the JSON array
struct News {
public var id: Int
public var title: String
public var content: String
public var image: FeaturedMedia
}
struct FeaturedMedia {
public var id: Int
public var url: String
}
Here is the UITableViewCell class:
import UIKit
class NewsTableViewCell: UITableViewCell {
#IBOutlet weak var newsImage: UIImageView!
#IBOutlet weak var newsTitle: UILabel!
#IBOutlet weak var newsContent: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
And here the UITableViewController class:
import UIKit
import Alamofire
import Alamofire_SwiftyJSON
import SwiftyJSON
class NewsTableViewController: UITableViewController {
var newsList: [News] = [News]()
func parseJsonNews() {
DispatchQueue.main.async {
Alamofire.request("link request", method: .get).responseJSON { (response) in
switch response.result {
case .success(let value):
let news = [value]
print(news) // here in console it prints correctly the json, starting with [<__NSArrayI 0x6000001a9e60 ....
for new in news {
let title = new["title"]
print(title)
}
print(newsss)
self.tableView.reloadData()
case.failure(let error):
print(error.localizedDescription)
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
parseJsonNews()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.newsList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "newsCell", for: indexPath) as? NewsTableViewCell
// Configure the cell...
let imageUrl = URL.init(string: newsList[indexPath.row].featuredMedia.url)
cell?.newsTitle.text = self.newsList[indexPath.row].title
cell?.newsContent.text = self.newsList[indexPath.row].content
cell.newsImage.load(url: imageUrl!)
return cell!
}
}
extension UIImageView {
func load(url: URL) {
DispatchQueue.global().async { [weak self] in
if let data = try? Data(contentsOf: url) {
if let image = UIImage(data: data) {
DispatchQueue.main.async {
self?.image = image
}
}
}
}
}
}
Looking for a solution, I just found many ways to parse dictionary JSON, but in this case, it is an array so I modified the code as you read at parseJsonNews method but it doesn't work properly.
Would be grateful for any help.
You can decode with Codable like this:
let data = try? JSONDecoder().decode([DummyData].self, from: jsonData)
But first, all of your models must comform Codable protocol. For example:
struct DummyData: Codable {
let id: Int
let date, dateGmt: String
let modified, modifiedGmt, slug, status: String
let type: String
let link: String
let title, content, excerpt: String
let author: Int
let commentStatus, pingStatus: String
let sticky: Bool
let template, format: String
enum CodingKeys: String, CodingKey {
case id, date
case dateGmt = "date_gmt"
case modified
case modifiedGmt = "modified_gmt"
case slug, status, type, link, title, content, excerpt, author
case commentStatus = "comment_status"
case pingStatus = "ping_status"
case sticky, template, format
}
}
A couple of things:
The error value of type 'Any' has no subscripts, refers to your line let title = new["title"].
the response results from Alamofire in the .success enum returns a type of Any,
this is true to the whole array of data that you fetched.
The Any type in swift doesn't have any subscripts implementations (i.e: you cannot access vars inside it using the following syntax obj['MY_VAR_NAME'].
In order to access the title from your news object like so let title = new["title"], you have to first cast the objects to a dictionary, this could be done like this:
// ...
let news = [value]
print(news)
for new in news {
if let obj = new as? [String: Any] {
let title = obj["title"]
print(title)
}
}
// ...
In order to parse/use your custom structs, they must first comply to swift's Codable protocol.
/// Example show only News, but the same which be used for FeaturedMedia
struct News {
public var id: Int
public var title: String
public var content: String
public var image: FeaturedMedia?
init?(jsonString: String) {
guard let data = jsonString.data(using: .utf8) else {
return nil
}
guard let object = News(data: data) else {
return nil
}
self = object
}
init?(data: Data) {
guard let object = try? JSONDecoder().decode(News.self, from:
data) else {
return nil
}
self = object
}
// Optional, for custom key names (i.e: "image" instead of "featured_media"
private enum CodingKeys: String, CodingKey {
case id = "id"
case image = "featured_media"
// etc..
}
}
After you've done number 4, you can then init your objects like so:
// ...
let news = [value]
print(news)
for new in news {
if let obj = News(new) {
/// obj is now a News object
let title = obj.title
print(title)
}
}
// ...
if you are in pursuit for even more info (for example to init the whole array)
check out this
I hope I've cleared things up:)
func ComplainData() {
let semaphore = DispatchSemaphore(value: 0)
var request = URLRequest(url: URL(string: Constant.localBaseurl2 + "compID") !, timeoutInterval: Double.infinity)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) {
data,
response,
error in
if let response = response {
let nsHTTPResponse = response as!HTTPURLResponse
print(nsHTTPResponse)
}
if let error = error {
print("\(error)")
return
}
if let data = data {
DispatchQueue.main.async {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase //or any other Decoder\
do {
let jsonDecoder = JSONDecoder()
let memberRecord =
try jsonDecoder.decode(COMPLAINTSVC.GetComplaints.self, from: data)
print(memberRecord.message)
for detailData in memberRecord.message {
print(detailData)
}
} catch {
print(error.localizedDescription)
}
}
}
semaphore.signal()
}
task.resume()
semaphore.wait()
}
I want to filter all details(school, city, name, active) whose active value is true. I have stored value of key "details"
let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any>
{
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "1student", "Active": false },
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true },
{ "name": "4student", "Active": false },
{ "name": "5student", "Active": false}
]
}
}
Expected Result
[
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true }
]
}
]
You can use filter
if let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any> ,
let detailDic = details["details"] as? [String:Any],
let students = detailDic["students"] as? [[String:Any]] {
let activeStudents = students.filter { (item) -> Bool in
guard let active = item["Active"] as? Bool else {return false}
return active
}
print(activeStudents)
}
or you can use shourthand
if let details = jsonRes[RequestResponses.Keys.details.rawValue] as? Dictionary< String, Any> ,
let detailDic = details["details"] as? [String:Any],
let students = detailDic["students"] as? [[String:Any]] {
let activeStudents = (details["students"] as?
[[String:Any]])?.filter{ $0["Active"] as? Bool == true}
print(activeStudents)
}
You start realising the true elegance of Swift once you start turning this into regular objects using Codable. This will let you do things as in the Playground:
import Cocoa
let jsonData = """
{
"details": {
"code": 235,
"school": "sp school",
"students": [
{ "name": "1student", "Active": false },
{ "name": "2student", "Active": true },
{ "name": "3student", "Active": true },
{ "name": "4student", "Active": false },
{ "name": "5student", "Active": false}
]
}
}
""".data(using: .utf8)!
struct Student : Codable {
let name: String
let active: Bool
enum CodingKeys: String, CodingKey {
case name
case active = "Active"
}
}
struct School : Codable {
let code: Int
let school: String
let students: [Student]
}
struct Details: Codable {
let details: School
}
do {
let det = try JSONDecoder().decode(Details.self, from: jsonData)
print(det)
let activeStudents = det.details.students.filter({(student)->Bool in student.active})
print(activeStudents)
} catch {
print(error)
}
This is obviously much easier to understand and Xcode can also support you much better during the process. The effort spent on the parser is minimal and easily recovered by the sheer elegance and clarity of the final filtering line.
This is my json to parse (example):
[
{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
},
{
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}
]
paste it this json viewer to view it easily.
as you can see, it is an array (of teams).
I need to get each team and do something with it.
After many attempts, I tried using SwiftyJSON, because I thought it will be easier. But, it did not worked for me.
This is what I tried:
let array = JSON(response)
// print each subJSON in array
for team in array.arrayValue {
print(team)
}
But the loop does not work. It does not go in to the loop at all.
Maybe it does not understand that my json is an array.
I can see the array object in the debugger. It looks like this:
How can I get these sub-JSONs?
Thanks.
I think you should use
let array = JSON(parseJSON: response)
instead of
let array = JSON(response)
SwiftyJSON contains methods to parse JSON string into a JSON object, check documentation
/**
Parses the JSON string into a JSON object
- parameter json: the JSON string
- returns: the created JSON object
*/
public init(parseJSON jsonString: String) {
if let data = jsonString.data(using: .utf8) {
self.init(data)
} else {
self.init(NSNull())
}
}
/**
Creates a JSON from JSON string
- parameter string: Normal json string like '{"a":"b"}'
- returns: The created JSON
*/
#available(*, deprecated: 3.2, message: "Use instead `init(parseJSON: )`")
public static func parse(json: String) -> JSON {
return json.data(using: String.Encoding.utf8)
.flatMap{ JSON(data: $0) } ?? JSON(NSNull())
}
or alternatively you can convert son string into son object like
Swift 3:
let dataFromString = response.data(using: .utf8)
let jsonArray = JSON(data: dataFromString!)
In the following example, I save team names in an array. I've tested it.
var names = [String]()
if let array = json.array {
for i in 0..<array.count {
let name = array[i]["name"]
names.append(name.stringValue)
}
}
print(names) // ["Team name", "Team name"]
Here is the answer for Swift 5. In My case data response is something like below :
[
{
"Name": "Some Type",
"Data": [
{
"ParentId": 111,
"Code": "Personal",
"SortOrder": 1,
"Name": "Personal",
"Id": 323
},
{
"ParentId": null,
"Code": "Work",
"SortOrder": 2,
"Name": "Work",
"Id": 324
}
],
"KeyType": "Integer"
},
{
"Name": "Phone Type",
"Data": [
{
"ParentId": null,
"Code": "Phone",
"SortOrder": 1,
"Name": "Phone",
"Id": 785
},
{
"ParentId": null,
"Code": "Cell",
"SortOrder": 2,
"Name": "Cell",
"Id": 786
},
{
"ParentId": null,
"Code": "Fax",
"SortOrder": 3,
"Name": "Fax",
"Id": 787
},
{
"ParentId": null,
"Code": "Home",
"SortOrder": 4,
"Name": "Home",
"Id": 788
},
{
"ParentId": null,
"Code": "Office",
"SortOrder": 5,
"Name": "Office",
"Id": 789
}
],
"KeyType": "Integer"
}
]
I was handled it with following code.
struct responseObjectClass:BaseModel {
var responsearray: [arrayData]? = nil
init(json: JSON) {
responsearray = json.arrayValue.map { arrayData(json: $0) }
}
}
struct arrayData:BaseModel {
let Name: String?
var DataValue: [DataLookup]? = nil
let KeyType: String?
init(json: JSON) {
Name = json["Name"].stringValue
DataValue = json["Data"].arrayValue.map { DataLookup(json: $0) }
KeyType = json["KeyType"].stringValue
}
}
struct DataLookup:BaseModel {
let ParentId: Any?
let Code: String?
let SortOrder: Int?
let Name: String?
let Id: Int?
init(json: JSON) {
ParentId = json["ParentId"]
Code = json["Code"].stringValue
SortOrder = json["SortOrder"].intValue
Name = json["Name"].stringValue
Id = json["Id"].intValue
}
}
BaseModel is Optional it's just used for init Json.
protocol BaseModel {
init(json: JSON)
}
Without SwiftyJSON
Below is the valid JSON
data.json File
[{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
}, {
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}]
Below is the code to read your json.
if let path = Bundle.main.path(forResource: "data", ofType: "json") {
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
if let jsonArray = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSArray {
for (_, item) in jsonArray.enumerated() {
let itemDict = item as! NSDictionary
let id = itemDict["id"] as! Int
let name = itemDict["name"] as! String
let shower = itemDict["shower"] as! NSDictionary
let showerId = shower["id"] as! Int
let showerStatus = shower["status"] as! Int
let startLocation = shower["startLocation"] as! NSDictionary
let startLocationId = startLocation["id"] as! Int
let startLocationName = startLocation["name"] as! String
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}
this is what worked for me:
// Convert JSON to Array
func JSONToArray(_ json: String) -> Array<Any>? {
if let data = json.data(using: String.Encoding.utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? Array
} catch let error as NSError {
print(error)
}
}
return nil
}
After using this function i could loop trough the sub JSONs.
Thanks.