Parse Using Swifty JSON - ios

My JSON is like:
{
"status": 1,
"msg": "Category Product List",
"product_data": [{
"product_id": "49",
"image": "http://192.168.1.78/Linkon/site/pub/static/frontend/Linkon/default/en_US/Magento_Catalog/images/product/placeholder/image.jpg",
"shopName": "putin",
"review": "",
"rating": "2",
"productName": "ccd",
"customrFirstName": "devi",
"customrLastName": "ss",
"address": "6th Ln, S.T.Colony, Mahalaxminagar, Rajarampuri, Kolhapur, Maharashtra 416008, India",
"contactNumber": null,
"description": "<p>ccd</p>"
},
{
"product_id": "50",
"image": "http://192.168.1.78/Linkon/site/pub/static/frontend/Linkon/default/en_US/Magento_Catalog/images/product/placeholder/image.jpg",
"shopName": "putin",
"review": "",
"rating": "2",
"productName": "car garage",
"customrFirstName": "devi",
"customrLastName": "ss",
"address": "6th Ln, S.T.Colony, Mahalaxminagar, Rajarampuri, Kolhapur, Maharashtra 416008, India",
"contactNumber": null,
"description": "<p>car garage</p>"
}
]
}
So my question is: How to create JSON model class and parse using swifty JSON?

I would recommend ditching SwiftyJSON in favor of the built-in Codable and JSONDecoder support in Swift 4.
For this, you simply define a struct that matches your JSON format, and decode it:
struct Data: Codable {
let status: Int
let msg: String
let products: [Product]
enum CodingKeys: String, CodingKey {
case status, msg
case products = "product_data"
}
}
struct Product: Codable {
let product_id, image, shopName, review: String
let rating, productName, customrFirstName, customrLastName: String
let address: String
let contactNumber: String?
let description: String
}
do {
let data = try JSONDecoder().decode(Data.self, from: json)
print("\(data.msg)") // e.g.
} catch {
print("\(error)")
}

You can create your data model class like below:
import UIKit
import SwiftyJSON
class ProductModels: NSObject {
var productModel:[ProductModel]?
}
public init(json:JSON) {
self.productModel = json["product_data"].dictionary
}
class ProductModel: NSObject {
var productID:String?
var image:String?
var shopName:String?
var review:String?
var rating:String?
var productName:String?
var customrFirstName:String?
var customrLastName:String?
var address:String?
var contactNumber:String?
var description:String?
public init(json:JSON) {
self.productID = json["product_id"].string
self. image = json["image"].string
self. shopName = json["shopName"].string
self. review = json["review"].string
self. rating = json["rating"].string
self. productName = json["productName"].string
self. customrFirstName = json["customrFirstName"].string
self. customrLastName = json["customrLastName"].string
self. address = json["address"].string
self. contactNumber = json["contactNumber"].string
self. description = json["description"].string
}
}
and can use this class by passing response from model who is calling api and getting this response example below: (the class where you are using this below code, you have to import SwiftyJSON)
Alamofire.request(//(your method or api call).responseJSON(completionHandler: { (response) in
switch response.result {
case .success(let value):
let productJson = JSON(value)
let productsData = ProductModels(json: productJson)
break;
}
})

You can create class using SwiftyJSONAccelerator
Get SwiftyJSONAccelerator from Here: https://github.com/insanoid/SwiftyJSONAccelerator
or you can create it online using https://app.quicktype.io/

Related

Elegant solutions to decoding a JSON nested array in swift using decodable

Currently working with an API, and while I have successfully gotten it to decode the full result, I am only interested in the Entities/identifier portion. While I have gotten it working and get what I want/need I feel like this could be done better and more elegant and maybe in a single step. Any insight/suggestions appreciated.
JSON returned from API:
{
"count": 4596,
"entities": [
{
"facet_ids": [
"contact",
"siftery",
"investor",
"ipqwery",
"aberdeen",
"apptopia",
"semrush",
"company",
"rank",
"builtwith",
"bombora",
"key_event"
],
"identifier": {
"uuid": "468bef9f-2f50-590e-6e78-62e3adb05aa1",
"value": "Citi",
"image_id": "v1417152861/pdgwqt8ddecult5ktvdf.jpg",
"permalink": "citigroup",
"entity_def_id": "organization"
},
"short_description": "Citigroup is a diversified financial services holding company that provides various financial products and services."
},
{
"facet_ids": [
"contact",
"siftery",
"investor",
"apptopia",
"semrush",
"company",
"rank",
"builtwith",
"key_event"
],
"identifier": {
"uuid": "031a344b-c2b9-e60b-d950-1ae062026fde",
"value": "Citi",
"image_id": "yzlzhjqpparamrswaqa1",
"permalink": "citi-2",
"entity_def_id": "organization"
},
"short_description": "CITi is an NPO supporting the ICT sector in Western Cape."
},
{
"facet_ids": [
"contact",
"siftery",
"semrush",
"company",
"rank",
"builtwith",
"bombora"
],
"identifier": {
"uuid": "7ce45379-957c-49c5-bca2-c9ffd521f7da",
"value": "CITI",
"image_id": "qbkqndm7d0wgbogxjcrs",
"permalink": "citi-f7da",
"entity_def_id": "organization"
},
"short_description": "CITI trusted gateway to project-based change expertise that major organisations need to thrive, change and innovate."
}
]
}
Structs:
struct Entity: Decodable, Identifiable
{
var id: String
var companyName: String
var permalink: String
var imageID: String
init(from entity: Entities.Entity) throws
{
self.id = entity.identifier?.uuid ?? ""
self.companyName = entity.identifier?.value ?? ""
self.permalink = entity.identifier?.permalink ?? ""
self.imageID = entity.identifier?.image_id ?? ""
}
}
struct Entities: Decodable
{
var count:Int?
var entities: [Entity]?
struct Entity: Decodable
{
var facet_ids:[String]?
var identifier:Identifier?
var short_description:String?
}
struct Identifier:Decodable
{
var permalink:String?
var value:String?
var image_id:String?
var entity_def_id:String?
var uuid:String?
}
}
Call to decode:
if let data = data{
do {
let businessEntities = try decoder.decode(Entities.self, from: data)
let entities:[Entity] = try businessEntities.entities!.compactMap{
entity in
do
{
return try Entity(from: entity)
}
}
Thinking you are just interested in the Entities/identifier, you can simplify your model. Here's an example:
typealias Entities = [Entitie]
struct Entitie: Codable {
let facetIDS: [String]
let identifier: Identifier
let shortDescription: String
enum CodingKeys: String, CodingKey {
case facetIDS = "facet_ids"
case identifier
case shortDescription = "short_description"
}
}
struct Identifier: Codable {
let uuid, value, imageID, permalink: String
let entityDefID: String
enum CodingKeys: String, CodingKey {
case uuid, value
case imageID = "image_id"
case permalink
case entityDefID = "entity_def_id"
}
}
You can access entities object and decode it like this:
guard let data = data,
let jsonDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let entitiesObj = jsonDict["entities"] else { return }
do {
let entitiesData = try JSONSerialization.data(withJSONObject: entitiesObj)
let result = try JSONDecoder().decode(Entities.self, from: entitiesData)
} catch {
print(error.localizedDescription)
}
Obs: I created the CodingKeys because I use camelCase in my projects, you can left it in snake_case just remember to replace variables.

How would I read JSON data in an array using SwiftyJSON?

I have just been introduced to using JSON data in my iOS app. I used a service to parse JSON data from a website, and I would like to decode that data to display to my user in a UITableView. My JSON data looks as follows:
{
"success": true,
"outputScenario": "Default",
"data": {
"collection": [
{
"teamName": "Gulf Coast Monarchs, FL",
"teamW": "10",
"teamL": "0",
"teamT": "0",
"teamPct": "1.000",
"teamGB": "-",
"teamGP": "10",
"teamRA": "10",
"teamDivision": "10-0-0"
},
{
"teamName": "Ohio Nationals, OH",
"teamW": "9",
"teamL": "1",
"teamT": "0",
"teamPct": ".900",
"teamGB": "1.0",
"teamGP": "10",
"teamRA": "20",
"teamDivision": "9-1-0"
}, {
"teamName": "Mount Kisco Chiefs, NY",
"teamW": "0",
"teamL": "8",
"teamT": "0",
"teamPct": ".000",
"teamGB": "8.0",
"teamGP": "8",
"teamRA": "108",
"teamDivision": "0-8-0"
}
{
"teamName": "Mount Kisco Chiefs, NY",
"teamW": "0",
"teamL": "8",
"teamT": "0",
"teamPct": ".000",
"teamGB": "8.0",
"teamGP": "8",
"teamRA": "108",
"teamDivision": "0-8-0"
}
]
},
Just keep in mind that I have cut out a significant amount of the data provided in the JSON so it is easily viewable.
I would like to decode this data using SwiftyJSON if possible so I can display it to my user in a UITableView. For now, the UITableView will display the team name in the UITableView.textLabel.text and the teamW and teamL in the UITableView.detailTextLabel.text. How would I decode this data using SwiftyJSON? I am struggling to figure out how this type of structure would be decoded. I would like to use the model that I have created:
struct Standing: Decodable {
var teamName: String
var teamW: Int
var teamL: Int
var teamT: Int
var teamPct: Int
teamGB: Int
teamGP: Int
teamRA: Int
teamDivision: String
}
Why do you want to use SwiftyJSON as you already adopted Decodable?
The types in your struct are widely wrong because all values are String. And you need two other structs for the parent objects.
struct Root: Decodable {
let success : Bool
let outputScenario : String
let data : TeamData
}
struct TeamData: Decodable {
let collection : [Standing]
}
struct Standing: Decodable {
let teamName, teamW, teamL, teamT: String
let teamPct, teamGB, teamGP, teamRA: String
let teamDivision: String
}
Decode the data, the Standing array is in the variable standings.
do {
let result = try JSONDecoder().decode(Root.self, from: data)
let standings = result.data.collection
} catch {
print(error)
}
If you're using Decodable, you don't need to use SwiftyJSON at all, everything's built into Swift itself.
Use this as your model struct:
struct Standing: Codable {
let success: Bool
let outputScenario: String
let data: DataClass
}
struct DataClass: Codable {
let collection: [Collection]
}
struct Collection: Codable {
let teamName, teamW, teamL, teamT: String
let teamPct, teamGB, teamGP, teamRA: String
let teamDivision: String
}
and parse it like so:
do {
let standing = try JSONDecoder().decode(Standing.self, from: data)
} catch {
print(error)
}

Generic UITableViewCell with Different Kind Of JSON data

I like to use a single tableViewCell instance (instead creating separate cells) for different kind of data; used a simple generic approach by creating a protocol for this but how can I populate data from JSON (by avoiding switch-case) in clear way?
protocol CellData {
var title: String { get set }
var subTitle: String { get set }
var image: String { get set }
}
for the singleCell
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var subTitleLabel: UILabel!
#IBOutlet weak var imageView: UIImageView!
{
"data": [
{
"type": "company",
"data": {
"name": "Google",
"sector": "IT",
"logo": "https://www.google.com/logos/doodles/2015/googles-new-logo-5078286822539264.3-hp2x.gif"
}
},
{
"type": "person",
"data": {
"name": "Bill Gates",
"occupation": "Microsoft CEO",
"picture": "https://img.etimg.com/thumb/msid-66398917,width-640,resizemode-4,imgsize-702055/words-of-wisdom.jpg"
}
},
{
"type": "song",
"data": {
"name": "Beat It",
"singer": "M.Jackson",
"thumbnail": "https://cdn.smehost.net/michaeljacksoncom-uslegacyprod/wp-content/uploads/2019/08/Sept2019Mobile.jpg"
}
},
{
"type": "vehicle",
"data": {
"name": "Silver Silver",
"brand": "Silver",
"photo": "https://images.pexels.com/photos/112460/pexels-photo-112460.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500"
}
}
],
"error": null
}
If you have guarantees about the keys that you can expect, then you could try to parse the data for the first key that is found:
struct CellDataEnvelope: Decodable {
let data: CellData
let type: CellDataType
enum CellDataType: String, Decodable {
case company
case person
case song
case vehicle
}
}
struct CellData: Decodable {
let title: String
let subTitle: String
let image: String
enum CodingKeys: CodingKey {
// title
case name
// subTitle
case sector
case occupation
case singer
case brand
// image
case thumbnail
case picture
case logo
case photo
}
}
extension CellData {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.title = try container.decode(String.self, forKey: .name)
self.subTitle = try tryDecodeString(in: container, keys: [.sector, .occupation, .singer, .brand])
self.image = try tryDecodeString(in: container, keys: [.thumbnail, .picture, .logo, .photo])
}
}
// Attempts to decode a String value from an array of keys, returns the first one to successfully decode.
private func tryDecodeString(
in container: KeyedDecodingContainer<CellData.CodingKeys>,
keys: [CellData.CodingKeys]
) throws -> String {
for key in keys {
if let value = try? container.decode(String.self, forKey: key) {
return value
}
}
throw DecodingError.dataCorrupted(
.init(
codingPath: [],
debugDescription: "Invalid data"
)
)
}
let models = try JSONDecoder().decode([CellDataEnvelope].self, from: Data(json.utf8))
This could become unwieldy if the list of keys will grow substantially or if you don't have guarantees about them.
You can have a model with optional attributes, which means they won't always need to have all of the properties setted, but as mentioned by Vadian, at some point you will need to map what you are receiving.
You can also define all of your keys that are not image or title, as the subtitle (for example, sector or occupation or singer or brand).

How to decode .keys from dictionary using Coadable

I have a below JSON response. And I want to fetch the allkeys of “data” which is [“rules”, “preference”, “goals”] using .keys method. But I couldn’t get the array of allkeys using .keys feature. I have attached my code snippet also. if you had faced this one, please suggest me to rid of this concern.
Although, I can get these allKeys using ObjectMapper and native Dictionary objects. I just need to know why I couldn't achieve this using Codable.
My json response
{
"statusCode": 200,
"status": "success",
"message": null,
"data": {
"rules": {
"goals": {
"min": "1",
"max": "3"
}
},
"preference": [
1,
2,
3
],
"goals": {
"total": 4,
"data": []
}
}
}
My code Snippet:
struct MeetingsDataModal: Codable {
let statusCode: Int?
let status: String?
let message: String?
let data: Results?
enum CodingKeys: String, CodingKey {
case statusCode = "statusCode"
case status = "status"
case message = "message"
case data = "data"
}
func allkeys() {
}
}
struct Results : Codable {
let rules: Rules?
let preference: [Preference]?
let goals: Goals?
enum CodingKeys: String, CodingKey {
case rules = "rules"
case preference = "preference"
case goals = "goals"
}
}
struct Rules : Codable {
}
struct Preference : Codable {
}
struct Goals : Codable {
}
My expectation
let content = try JSONDecoder().decode(MeetingsDataModal.self, from: (response as? Data)!)
print(content.data.keys)
But I am getting,
Value of type 'Results?' has no member 'keys'
Perhaps I am not understanding the question well but your "keys" are defined by your Codable protocol - so they are known. If you are using Swift 4.2+ you can take advantage of the CaseIterable protocol
struct Results: Codable {
let testOne: Int
let testTwo: String
enum CodingKeys: String, CodingKey, CaseIterable {
case testOne
case testTwo
}
}
Results.CodingKeys.allCases.map { $0.rawValue }
If you do need .keys. could add two-line code :
struct Results : Codable {
let rules: Rules?
let preference: [Preference]?
let goals: Goals?
enum CodingKeys: String, CodingKey {
case rules = "rules"
case preference = "preference"
case goals = "goals"
}
var keys : [String]{
return["rules", "preference","goals"]
}
}
Actually, if you don't like encoding/decoding way , we have another traditional JSON object can help you to handle unstructured JSON.
let obj = try JSONSerialization.jsonObject(with: response!, options: JSONSerialization.ReadingOptions.allowFragments)
print((((obj as! [String: Any?])["data"]) as! [String: Any?]).keys)
Here is one way to decode you data structure.
let d = """
{
"statusCode": 200,
"status": "success",
"message": null,
"data": {
"rules": {
"goals": {
"min": "1",
"max": "3"
}
},
"preference": [
1,
2,
3
],
"goals": {
"total": 4,
"data": []
}
}
}
""".data(using: .utf8)!
struct MeetingsDataModal: Decodable {
let statusCode: Int
let status: String
let message: String?
let data: Results
}
struct Results : Decodable {
let rules: Rules
let preference: [Int]
let goals: Goals
}
struct Rules : Decodable {
let goals : DirectData
}
struct DirectData : Decodable{
let min : String
let max : String
}
struct Goals : Decodable {
let total : Int
let data : [String]
}
let data0 = try JSONDecoder().decode(MeetingsDataModal.self, from: d)
print(data0)

iOS swift JSON to class object

How can I parse JSON to a class when JSON has multiples arrays?
I'm trying to convert data from REST API to my classes in Swift 2.0, but I can't figure out how to do it properly, anyone can help?
/* JSON from REST API
{
"Name": "New name",
"Email": "test#test.com",
"FacebookId": "new facebook Id",
"Address": "New Address",
"Photo": "https://graph.facebook.com/novo facebook Id/picture?width=500&height=500",
"CreditCards": [
{
"Id": 5,
"LastNumber": "3123",
"Brand": "visa"
},
{
"Id": 6,
"LastNumber": "3124",
"Brand": "visa"
}
]
}
Class representation in swift?
class Client {
var fullName: String?
var email: String?
var facebookId: String?
var photo: String?
var creditCards: [CreditCard]?
required init(json: JSON) {
self.fullName = json["FullName"].string
self.email = json["Email"].string
self.facebookId = json["FacebookId"].string
self.photo = json["Photo"].string
self.creditCards = json["CreditCards"]["Id"]
}
required init() {}
}
class CreditCard {
var Id: Int?
var lastNumber: String?
var brand: String?
}
You should take a look at the NSJSONSerialization class and its methods. They do exactly what you need: converting JSON data into arrays, dictionaries, strings and numbers.

Resources