Json4swift model class - swift

I am new to swift , using json4swift tool to make model class. I would like to know how to get data from model class , i managed to map the items to model class using below code.
let responseModel = Json4Swift_Base(dictionary: searchResultsData)
My json responce folllows:
{
"success": true,
"categorys": [
{
"categoryId": 1,
"categoryName": "Electricians "
},
{
"categoryId": 2,
"categoryName": " Drivers "
},
{
"categoryId": 3,
"categoryName": " Plumbers "
},
{
"categoryId": 4,
"categoryName": "Carpenters "
},
{
"categoryId": 5,
"categoryName": "Automobile works "
}
]
}
Json4swift tool made two classes namely Json4Swift_Base and Categorys class. I need to get from model class.

If you want to learn Swift, I would suggest you to forget json4swift.
First, you have to build your owns models: Category and Response
Category:
struct Category {
let id: Int
let name: String
}
Response:
struct Response {
let success: Bool
let categories: [Category]
}
Second, you want to initialize your models with a JSON. We are going to create a protocol for that:
typealias JSONDictionary = [String : Any]
protocol JSONDecodable {
init?(dictionary: JSONDictionary)
}
Your models must implement this protocol, so we add extensions:
Category extension:
extension Category: JSONDecodable {
init?(dictionary: JSONDictionary) {
guard let id = dictionary["categoryId"] as? Int,
let name = dictionary["categoryName"] as? String else {
return nil
}
self.id = id
self.name = name
}
}
Response extension:
extension Response: JSONDecodable {
init?(dictionary: JSONDictionary) {
guard let success = dictionary["success"] as? Bool,
let jsonCategoriesArray = dictionary["categorys"] as? [JSONDictionary] else {
return nil
}
self.success = success
self.categories =
jsonCategoriesArray.flatMap{ jsonCategoryDictionary in
Category(dictionary: jsonCategoryDictionary)
}
}
}
Now, you can write:
let response = Response(dictionary: jsonResponse)
if let response = response {
let success = response.success
let categories = response.categories
let firstCategory = categories[0]
// ...
}

Related

How to use generics and inheritance to parse an output?

I'm programming a network call for a backend in NestJs which has pagination enabled. The output of the service has the following structure:
{
"items": [{
"id": "26c26ecb-0763-4813-b489-3c6ad94084a9",
"body": "Test body",
"subTitle": "SubTitle Test",
"author": "Test Author",
"createAt": "2023-01-12T07:04:58.480Z",
"title": "Temporary Title",
"updateAt": "2023-01-12T07:04:58.480Z"
}],
"meta": {
"itemsPerPage": 10,
"totalPages": 2,
"currentPage": 1,
"totalItems": 19,
"itemCount": 10
},
"links": {
"previous": "",
"first": "\/post\/public?limit=10",
"next": "\/post\/public?page=2&limit=10",
"last": "\/post\/public?page=2&limit=10"
}
}
The Meta and Links atribute are fairly straight through so they have no problems. The issue that i'm having is the items list. The objects data structures that are inside can be different, there can be News, Posts, Events.
All of this objects have some properties in common so a BaseEntity Model was created. Therefore all models inherit from BaseEntity.
I dont want to create a class for each of this outputs. I want to declare a generic class that receives a generic an parses it accordingly.
My BaseCrudEntity model and protocol are designed like this:
public protocol BaseCrudEntityParsingProtocol: AnyObject
{
var id: String { get set }
var createAt: Date { get set }
var updateAt: Date { get set }
var deletedAt: Date? { get set }
var isActive: Bool { get set }
init()
init?(info: Any?)
static func parseList(info: Any?) -> [BaseCrudEntityParsingProtocol]?
}
public extension BaseCrudEntityParsingProtocol
{
static func parseList(info: Any?) -> [BaseCrudEntityParsingProtocol]? {
return nil
}
}
public class BaseCrudEntity: BaseCrudEntityParsingProtocol
{
public var createAt: Date = Date()
public var updateAt: Date = Date()
public var deletedAt: Date?
public var isActive: Bool = true
public var id: String = ""
enum CodingKeysBase: String {
case createAt = "createAt"
case updateAt = "updateAt"
case deletedAt = "deletedAt"
case isActive = "isActive"
case id = "id"
}
required public init() { }
required public init?(info: Any?) {
guard let anyInfo = info,
let info = anyInfo as? NSDictionary else { return nil }
self.createAt = info.parseString(forKeyPath: CodingKeysBase.createAt.rawValue).toDate()
self.updateAt = info.parseString(forKeyPath: CodingKeysBase.updateAt.rawValue).toDate()
self.deletedAt = info.parseStringOptional(forKeyPath: CodingKeysBase.deletedAt.rawValue)?.toDateOptional()
self.isActive = info.parseBool(forKeyPath: CodingKeysBase.isActive.rawValue)
self.id = info.parseString(forKeyPath: CodingKeysBase.id.rawValue)
}
}
And an example of a specific model which will have pagination looks like this:
// MARK: - Post
public class Post: BaseCrudEntity
{
public var title: String?
public var subTitle: String?
public var author: String?
public var body: String?
enum CodingKeys: String, CodingKey {
case title = "title"
case subTitle = "subTitle"
case author = "author"
case body = "body"
}
required public init() {
super.init()
}
required public init?(info: Any?) {
super.init(info: info)
guard let anyInfo = info,
let info = anyInfo as? NSDictionary else { return nil }
self.title = info.parseStringOptional(forKeyPath: CodingKeys.title.rawValue)
self.subTitle = info.parseStringOptional(forKeyPath: CodingKeys.subTitle.rawValue)
self.author = info.parseStringOptional(forKeyPath: CodingKeys.author.rawValue)
self.body = info.parseStringOptional(forKeyPath: CodingKeys.body.rawValue)
}
public static func parseList(info: Any?) -> [Post]? {
guard let anyInfo = info,
let rawList = anyInfo as? [NSDictionary] else { return nil }
return rawList.compactMap(Post.init)
}
}
I've created the following class so that I can pass a generic value and parse it accordingly, the problem that i'm having is that the items list returns nil, as if the parsing cannot be done successfully or is having problems. This task could be done using the Codable protocol but due to the codebase design it cannot be done at this time.
public class PaginatedList<Body: BaseCrudEntity> where Body: BaseCrudEntity
{
public var items: [Body]?
public var meta: PaginatedListMeta?
public var links: PaginatedListLinks?
enum CodingKeys: String, CodingKey {
case meta = "meta"
case links = "links"
case items = "items"
}
public init() {
}
public init(items: [Body]?, meta: PaginatedListMeta?, links: PaginatedListLinks?) {
self.meta = meta
self.links = links
}
public init?(info: Any?) {
guard let anyInfo = info,
let info = anyInfo as? NSDictionary else { return nil }
self.meta = PaginatedListMeta(info: info[CodingKeys.meta.rawValue])
self.links = PaginatedListLinks(info: info[CodingKeys.links.rawValue])
self.items = Body.parseList(info: info[CodingKeys.items.rawValue]) as? [Body]
}
}
I've tried a couple of solutions but none seem to work.

Casting JSON array to a struct model swift

I am parsing a json array of object into a model which works. In this object, there is an array to a value and I created another model to handle that array but when ever I pass this object, the internal array returns nil after casting as the model class. Any help is appreciated
JSON Sample
[
{
"code": "AF",
"code3": "AFG",
"dial_code": "+93",
"name": "Afghanistan",
"capital": "Kabul",
"region": "Asia",
"subregion": "Southern Asia",
"states": [
{
"code": "BDS",
"name": "Badakhshān",
"subdivision": null
},
{
"code": "BGL",
"name": "Baghlān",
"subdivision": null
}
]
}
}
]
MODEL
public struct LocaleInfo {
public var locale: Locale?
public var id: String? {
return locale?.identifier
}
public var country: String
public var code: String
// public var phoneCode: String
public var states: [LocalStateInfo]
public var flag: UIImage? {
return UIImage(named: "Countries.bundle/Images/\(code.uppercased())", in: Bundle.main, compatibleWith: nil)
}
public var currencyCode: String? {
return locale?.currencyCode
}
public var currencySymbol: String? {
return locale?.currencySymbol
}
public var currencyName: String? {
guard let currencyCode = currencyCode else { return nil }
return locale?.localizedString(forCurrencyCode: currencyCode)
}
init(country: String, code: String/*, phoneCode: String*/, states: [LocalStateInfo]) {
self.country = country
self.code = code
self.states = states
self.locale = Locale.availableIdentifiers.map { Locale(identifier: $0) }.first(where: { $0.regionCode == code })
}
}
public struct LocalStateInfo {
public var code: String
public var name: String
public var subdivision: String
}
Passing the JSON Body
func getInfo(completionHandler: #escaping (FetchResults) -> ()) {
let bundle = Bundle(for: LocalePickerViewController.self)
let path = "Countries.bundle/Data/CountryCodes"
guard let jsonPath = bundle.path(forResource: path, ofType: "json"),
let jsonData = try? Data(contentsOf: URL(fileURLWithPath: jsonPath)) else {
let error: (title: String?, message: String?) = (title: "ContryCodes Error", message: "No ContryCodes Bundle Access")
return completionHandler(FetchResults.error(error: error))
}
if let jsonObjects = (try? JSONSerialization.jsonObject(with: jsonData, options: JSONSerialization.ReadingOptions.allowFragments)) as? Array<Any> {
var result: [LocaleInfo] = []
for jsonObject in jsonObjects {
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
guard let country = countryObj["name"] as? String,
let code = countryObj["code"] as? String/*,
let phoneCode = countryObj["dial_code"] as? String*/ else {
fatalError("Broken here")
continue
}
log("countryObj state \(countryObj["states"] as? [LocalStateInfo])", .fuck)
log("countryObj \(countryObj)", .fuck)
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
let new = LocaleInfo(country: country, code: code/*, phoneCode: phoneCode*/, states: states)
result.append(new)
}
return completionHandler(FetchResults.success(response: result))
}
let error: (title: String?, message: String?) = (title: "JSON Error", message: "Couldn't parse json to Info")
return completionHandler(FetchResults.error(error: error))
}
let states = countryObj["states"] as? [LocalStateInfo] ?? [LocalStateInfo]()
is presumably the line that isn't working for you. But countryObj is just a dictionary straight from JSON:
guard let countryObj = jsonObject as? Dictionary<String, Any> else { continue }
Why would casting it to an array of LocalStateInfo work at at all? It's an array of dictionaries, and you need to parse each one out individually.
You've said using Codable would alter the "entire scope" of the library, I don't understand how this is the case. You can implement codable (or even just Decodable) without affecting any other file.

How to map the nested data in a document from Firestore by Swift?

I have a document data structure on Firestore like this:
pic1
pic2
So there are 2 map-objects inside the document and a collection and a another document inside this document
Then I create 3 model swift files for this document
task:
struct task {
var Name: String
var Address: String
var Car: CarModel
car Price: PriceModel
var dictionary: [String:Any] {
return [
"Name" : Name,
"Address" : Address,
"Car" : CarModel,
"Price" : PriceModel
]
}
init?(data: [String:Any]) {
guard let Name = dictionary["Name"] as? String,
let Address = dictionary["Address"] as? String,
let Car = ditionary["car"] as? CarModel,
let Price = dictionary["price"] as? PriceModel else{
return nil
}
self.Name = Name
self.Address = Address
self.Car = Car
self.Price = Price
}
}
CarModel:
struct CarModel {
var brand: String
var model: String
var year: String
var dictionary: [String:Any] {
return [
"brand" : brand,
"model" : model,
"year" : year,
]
}
init?(data: [String:Any]) {
guard let brand = dictionary["brand"] as? String,
let model = dictionary["model"] as? String,
let year = ditionary["year"] as? String else{
return nil
}
self.brand = brand
self.model = model
self.year = year
}
}
PriceModel:
struct PriceModel {
var basic: Int
var extra: Int
var dictionary: [String:Any] {
return [
"basic" : basic,
"extra" : extra,
]
}
init?(data: [String:Any]) {
guard let basic = dictionary["basic"] as? Int,
let extra = ditionary["extra"] as? Int else{
return nil
}
self.basic = basic
self.extra = extra
}
}
Then download the data with this following code:
func loadDataFromFirestore(completion: #escaping (Bool) -> ()) {
var success: Bool = false
DispatchQueue.global(qos: .userInteractive).async {
let downloadGroup = DispatchGroup()
let colRef = db.collection("tasks")
downloadGroup.enter()
colRef.getDocuments() { (querySnapshot, error) in
if let error = error {
print("Error: \(error)")
return
}
for document in querySnapshot!.documents{
let result = document.data()
print (result)
if let data = task(data: result){
print(data)
}
}
success = true
downloadGroup.leave()
}
downloadGroup.wait()
DispatchQueue.main.async {
completion(success)
}
}
}
I can get the data with comment the CarModel and PriceModel, but if I uncomment these two, it will let my app crash!
So how could I get the map-object to adapt to my code?
And the second question is: How can I get the document inside a document's collection with this kind of code?

Swift - How to init an array in struct?

I'm not sure how to init the array in Struct. I'm not able to fetch data from array, meanwhile I was manage to get the result from object (platform.status).
Am I init it wrongly ?
Any ideas ?
Here is Network Request :
func fetchServicePlatform(token: String, _key: String) {
let selectedUrl = URL(string: "\(mainUrl)/get_service")
let parameters: [String: String] = ["_key": _key]
var serviceList = [ServiceList]()
URLSession.shared.dataTask(with: setupURLRequest(selectedURL: selectedUrl!, parameters: parameters, token: token, email: "test#gmail.com")) { (data, response, error) in
if let err = error {
print("Failed to fetch API: \(err.localizedDescription)")
}
guard let data = data else { return }
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] else { return }
let platform = Platform(json: json)
if platform.status == "success" {
self.serviceList = platform.service_list
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
} catch let jsonErr {
print("Failed to fetch service platform: ", jsonErr.localizedDescription)
}
}.resume()
}
Here is JSON :
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
Here is Struct :
struct Platform {
let service_list: [ServiceList]
let status: String
init(json: [String: Any]) {
service_list = [ServiceList(json: json["service_list"] as? [String: Any] ?? [:])]
status = json["status"] as? String ?? ""
}
}
struct ServiceList {
let title: String
let description: String
let image: String
init(json: [String: Any]) {
title = json["title"] as? String ?? ""
description = json["description"] as? String ?? ""
image = json["image"] as? String ?? ""
}
}
In your data json["service_list"] is an array of dictionaries,
You Can try out.
struct Platform {
var service_list: [ServiceList] = []
var status: String
init(json: [String: Any]) {
if let jsonArray = json["service_list"] as? [[String: Any]] {
for service in jsonArray {
service_list.append(ServiceList(json: service))
}
}
else{
service_list = []
}
status = json["status"] as? String ?? ""
}
}
init an array in the struct
struct MyData {
var dataArray:[Any] = []
var latitude: Float
var longitude: Float
}
You have to unwrap the dictionary as an array of dictionaries and then loop through it with a map or flatmap, where you use the value $0 as the value
guard let serviceList = (json["service_list"] as? [[String: Any]])?.flatmap({ServiceList(withDictionary: $0)})
Go with the below approach, this way it is simpler
Create your structure with Codable
struct Platform: Codable {
let service_list: [ServiceList]
let status: String
enum CodingKeys: String, CodingKey {
case service_list, status
}
}
struct ServiceList: Codable {
let title: String
let description: String
let image: String
enum CodingKeys: String, CodingKey {
case title, description, image
}
}
Your json data object
let json = """
{
"status": "pass",
"service_list": [
{
"title": "Plumber",
"description": "Plumber",
"image": "https://s3-ap-southeast-1.heroku.com.png"
},
{
"title": "Cleaner",
"description": "Cleaner",
"image": "https://s3-ap-southeast-1.heroku.com.png"
}
]
}
""".data(using: .utf8)!
Use the JSONDecoder to map the json object to structure
let decoder = JSONDecoder()
let platform = try! decoder.decode(Platform.self, from: json)
if platform.status == "pass"{
for service in platform.service_list{
print(service.title)
}
}

Decode an array inside JSON data only using Swift 4 Codable

I'm using Swift 4 Codable and I'm receiving this JSON from my web service:
{
"status": "success",
"data": {
"time": "00:02:00",
"employees": [
{
"id": 001,
"name": "foo"
},
{
"id": 002,
"name": "bar"
}
]
}
}
I want to decode only employees array into employee objects (the time property will only be saved once), but nothing works.
I read a lot of materials about Swift 4 Codable but don't get how can I decode this array.
EDIT: My employee class:
import Foundation
struct Employee: Codable {
var id: Int
var time: Date
enum CodingKeys: String, CodingKey {
case id = "id"
case time = "time"
}
}
The request:
Alamofire.SessionManager.default.request(Router.syncUsers)
.validate(contentType: ["application/json"])
.responseJSON { response in
if response.response?.statusCode == 200 {
guard let jsonDict = response as? Dictionary<String, Any>,
let feedPage = Employee(from: jsonDict as! Decoder) else {
return
}
guard let response = response.result.value as? [String: Any] else {
return
}
guard let data = response["data"] as? [String: Any] else {
return
}
guard let users = data["employees"] else {
return
}
guard let usersData = try? JSONSerialization.data(withJSONObject: users, options: .prettyPrinted) else {
return
}
guard let decoded = try? JSONSerialization.jsonObject(with: usersData, options: []) else {
return
}
let decoder = JSONDecoder()
guard let employee = try? decoder.decode(Employee.self, from: usersData) else {
print("errorMessage")
return
}
} else {
print("errorMessage")
}
}
When using Codable you cannot decode inner data without decoding the outer.
But it's pretty simple, for example you can omit all CodingKeys.
struct Root : Decodable {
let status : String
let data : EmployeeData
}
struct EmployeeData : Decodable {
let time : String
let employees : [Employee]
}
struct Employee: Decodable {
let id: Int
let name: String
}
let jsonString = """
{
"status": "success",
"data": {
"time": "00:02:00",
"employees": [
{"id": 1, "name": "foo"},
{"id": 2, "name": "bar"}
]
}
}
"""
do {
let data = Data(jsonString.utf8)
let result = try JSONDecoder().decode(Root.self, from: data)
for employee in result.data.employees {
print(employee.name, employee.id)
}
} catch { print(error) }
You can solve this kind of JSON parsing problem very easily with quicktype. Just paste in your JSON on the left and you'll get types and serialization/deserialization code on the right, for a variety of languages. Here are the types it produces for your JSON:
struct Employees: Codable {
let status: String
let data: EmployeesData
}
struct EmployeesData: Codable {
let time: String
let employees: [Employee]
}
struct Employee: Codable {
let id: Int
let name: String
}