Mapping json response to custom object - ios

I'm receiving following response from server.
[
{
"resourceEmail" : "",
"kind" : "admin#directory#resources#calendars#CalendarResource",
"resourceId" : "",
"resourceName" : "Inubation-Newton-4p",
"resourceType" : "Confroom",
"etags" : "\"eqO3c9wtJJ4wVWz2xe0E9HiU_D0\/rfmk2kZmhGi4HcHadds\""
},
{
"resourceEmail" : "...",
"kind" : "admin#directory#resources#calendars#CalendarResource",
"resourceId" : "...",
"resourceName" : "MVD11-R2-Napolitana",
"resourceType" : "1 Tel Room",
"etags" : "\"eqO3c9wtJJ4wVWz2xe0E9HiU_D0\/zcPwONBLID-O_3gvi0ly\""
},
]
How can I transform above json array to array of custom object. I'm using SwiftyJSON for creating custom object. Below is my custom object class.
class CalendarResource: NSObject {
var resourceID: String
var resourceEmail: String
var resourceName: String
var etags: String
init(json: JSON) {
self.resourceID = json[Constants.Model.CalendarResource.resourceID].stringValue
self.resourceEmail = json[Constants.Model.CalendarResource.resourceEmail].stringValue
self.resourceName = json[Constants.Model.CalendarResource.resourceName].stringValue
self.etags = json[Constants.Model.CalendarResource.etags].stringValue
}
}
Thanks

struct CalendarResource {
var resourceID: String
var resourceEmail: String
var resourceName: String
var etags: String
init(json: JSON) {
self.resourceID = json["resourceID"].stringValue
self.resourceEmail = json["resourceEmail"].stringValue
self.resourceName = json["resourceName"].stringValue
self.etags = json["etags"].stringValue
}
}
And when you are getting response for example like so:
//create array somewhere outside of the method, var calendarResourceArray: [CalendarResource]
let jsonArray = response
self.calendarResourceArray = jsonArray!.map{CalendarResource(json: $0)}

Related

Managing Dynamic Keys in response through Codable Protocol

I need to make the codable model for the dynamic keys of dictionary coming from response below is the response I'm getting.
{
"data" : [
{
"desc1" : null,
"file1" : "uploads\/posts\/Aug-2021\/1629271422310452767"
},
{
"desc2" : "hello",
"file2" : "uploads\/posts\/Aug-2021\/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"
}
],
"status" : "success"
}
This desc1 and file1 is dynamic till like file1, file2 and so on, I need to have codable model for that below is my model that is not supportive.
struct ListModel: Codable {
public var data: [data]?
}
struct data: Codable {
let file : String?
let desc : String?
}
Anything support by codable protocol for that. Thanks in Advance.
You need a custom initializer. Of course this will only work if your json will always come formatted as described:
struct File {
var file: String? = ""
var desc: String? = ""
}
struct Response {
let files: [File]
let status: String
enum CodingKeys: String, CodingKey {
case files = "data", status
}
}
extension Response: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.status = try container.decode(String.self, forKey: .status)
let elements = try container.decode([[String: String?]].self, forKey: .files)
self.files = elements.reduce(into: []) {
var file = File()
for (key, value) in $1 {
if key.hasPrefix("file") {
file.file = value
} else if key.hasPrefix("desc") {
file.desc = value
}
}
$0.append(file)
}
}
}
Playground testing:
let json = """
{
"data" : [
{
"desc1" : null,
"file1" : "uploads/posts/Aug-2021/1629271422310452767"
},
{
"desc2" : "hello",
"file2" : "uploads/posts/Aug-2021/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"
}
],
"status" : "success"
}
"""
do {
let response = try JSONDecoder().decode(Response.self, from: Data(json.utf8))
print(response)
} catch {
print(error)
}
This will print:
Response(files: [File(file: Optional("uploads/posts/Aug-2021/1629271422310452767"), desc: nil), File(file: Optional("uploads/posts/Aug-2021/162927142279356160WhatsApp+Image+2021-07-02+at+12.09.14+PM.jpeg"), desc: Optional("hello"))], status: "success")

How to use Codable encode with object inside object

Im working on a JSON payload with my object payload. but im having trouble encoding object inside object.
My Payload class was this
class ConversationPayload :BaseObject {
var title : String? = ""
var messageDict: MessagePayload = MessagePayload()
var participants: [Int32] = []
var type: String = ""
enum CodingKeys: String, CodingKey {
case title = "title"
case messageDict = "message"
case participants = "participants"
case type = "type"
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
if title != nil {
try container.encode(title, forKey: .title)
}
try container.encode(messageDict, forKey: .messageDict)
try container.encode(participants, forKey: .participants)
try container.encode(type, forKey: .type)
}
}
class MessagePayload: BaseObject {
var body : String = ""
var isVideocallInvite: Bool = false
var attachmentsPayload: MessageAttachmentPayload? = nil
enum CodingKeys: String, CodingKey {
case body = "body"
case isVideocallInvite = "is_videocall_invite"
case attachmentsPayload = "attachment"
}
override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(body, forKey: .body)
try container.encode(isVideocallInvite, forKey: .isVideocallInvite)
if attachmentsPayload != nil {
try container.encode(attachmentsPayload, forKey: .attachmentsPayload)
}
}
}
class MessageAttachmentPayload: BaseObject {
var photo : String = ""
var photoType : String = "jpg"
}
BaseObject was this
class BaseObject:Codable{}
What i want to get in json payload was something like this
{"message": {"body": "body_string", "is_videocall_invite": 1}, "participants" : [user-id], "type" : "converstation_type","title":"title"}
anyone know whats wrong with my payload class? not that familiar yet on codable. thanks in advance.
I'm not sure what constrains you have here, but I would simplify all of this down. Keep the JSON data models as close to the JSON as you can get.
struct ConversationJsonModel: Codable {
var title: String?
var message: MessageJsonModel
var participants: [Int]
var type: String
}
struct MessageJsonModel: Codable {
var body: String
var is_videocall_invite: Int
var attachment: AttachmentJsonModel?
}
struct AttachmentJsonModel: Codable {
var photo: String
var photo_type: String // <-- assuming photo_type is the JSON member name.
}
If you need a view model or some other kind of local data model, then the two parts are separate.
class BaseObject {}
class ConversationPayload: BaseObject {
var title : String? = ""
var messageDict: MessagePayload = MessagePayload()
var participants: [Int32] = []
var type: String = ""
func makeConversationJsonModel() -> ConversationJsonModel {
ConversationJsonModel(title: title,
message: messageDict.makeMessageJsonModel(),
participants: participants.map { Int($0) },
type: type)
}
}
class MessagePayload: BaseObject {
var body : String = ""
var isVideocallInvite: Bool = false
var attachmentsPayload: MessageAttachmentPayload? = nil
func makeMessageJsonModel() -> MessageJsonModel {
MessageJsonModel(body: body,
is_videocall_invite: isVideocallInvite ? 1 : 0,
attachment: attachmentsPayload?.makeAttachmentJsonModel())
}
}
class MessageAttachmentPayload: BaseObject {
var photo : String = ""
var photoType : String = "jpg"
func makeAttachmentJsonModel() -> AttachmentJsonModel {
AttachmentJsonModel(photo: photo, photo_type: photoType)
}
}
Finally, encoding your JSON
let conversationPayload = ConversationPayload()
let json = try? JSONEncoder().encode(conversationPayload.makeConversationJsonModel())
This allows for clean separation between the JSON representation and the payload model. For example, in the JSON, is_videocall_invite is an Int (0 or 1); meanwhile, in the payload model, isVideocallInvite is a Bool.
What is the issue? I just tested your class, it has some minor issues but it does conforming to codable and it encoded without issue.
In playground:
var conversation = ConversationPayload()
var message = MessagePayload()
message.body = "message body"
message.isVideocallInvite = true
var attachment = MessageAttachmentPayload()
attachment.photo = "attachment_file"
message.attachmentsPayload = attachment
conversation.messageDict = message
conversation.title = "Title"
conversation.participants = [1, 2, 3, 4]
conversation.type = "Conversation Type"
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try! encoder.encode(conversation)
print(String(data: data, encoding: .utf8)!)
The output looks like the following:
{
"participants" : [
1,
2,
3,
4
],
"message" : {
"is_videocall_invite" : true,
"attachment" : {
"photo" : "attachment_file",
"photoType" : "jpg"
},
"body" : "message body"
},
"title" : "Title",
"type" : "Conversation Type"
}
I have added encode and CodingKey in MessageAttachmentPayload class to encode the values.
Isn't it what you were expecting?

swifty json parse array object

how can I set an array of Images to an images variable of the Car class
my json:
{
"Description": "test comment",
"GalleryId": 5548,
"ShortCode": "rzswig",
"Images": [
{
"Id": 9742,
"Link": "https://url/Images/5548/image9742_x560.jpg"
},
{
"Id": 9749,
"Link": "https://url/Images/5548/image9749_x560.jpg"
},
{
"Id": 9746,
"Link": "https://url/Images/5548/image9746_x560.jpg"
}
]
}
my class :
class Car: Hashable {
var shortCode: String
var description: String
var galleryId: Int
var imageItems: [ImageItem]?
init(response: JSON) {
shortCode = response["ShortCode"].stringValue
description = response["Description"].stringValue
galleryId = response["GalleryId"].intValue
imageItems = response["Images"].arrayObject.map {$0} as? [ImageItem]
}
init(shortCode: String, description: String, galleryId: Int, imageItems: [ImageItem]?) {
self.description = description
self.shortCode = shortCode
self.galleryId = galleryId
self.imageItems = imageItems
}
}
struct ImageItem {
var id: Int
var link: String
}
variant:
imageItems = response["Images"].arrayObject.map {$0} as? [ImageItem]
doesn't work for me
If you want to keep using SwiftyJSON, you can add an initialiser to ImageItem that takes a JSON, just like you did with Car:
init(response: JSON) {
id = response["Id"].intValue
link = response["Link"].stringValue
}
You might also want to add the autogenerated member wise initialiser back if you want it.
Then, to initialise imageItems, do:
imageItems = response["Images"].array?.map(ImageItem.init(response:))
This whole thing would be a lot easier if you used the Codable API built in to Swift.
Just conform both Car and ImageItem to Codable:
class Car: Codable {
var shortCode: String
var description: String
var galleryId: Int
var imageItems: [ImageItem]?
enum CodingKeys: String, CodingKey {
case shortCode = "ShortCode"
case description = "Description"
case galleryId = "GalleryId"
case imageItems = "Images"
}
}
struct ImageItem: Codable {
var id: Int
var link: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case link = "Link"
}
}
And then do this to deserialise the json:
let car = JSONDecoder().decode(Car.self, data: jsonData) // jsonData is of type "Data"
I think the problem is that you map it as array of Image item
imageItems = response["Images"].arrayObject.map {$0} as? ImageItem
try response["images"].arrayValue

Parse the array without a key before the square brackets in SwiftyJson?

Am fetching the country list from server. And the server response is like this.
[
{
"CountryID": 2,
"Name": "Afghanistan",
"Code": "AFG",
"CreatedDate": "2018-01-09T02:05:02.08"
},
{
"CountryID": 3,
"Name": "Aland Islands",
"Code": "ALA",
"CreatedDate": "2018-01-09T02:05:02.08"
}
]
Am using SwiftyJSON to convert the response as Json like this.
if let value = response.result.value {
let json = JSON(value)
let countryListData = CountryList(fromJson: json)
completionHandler(true, countryListData)
}
And the Country List class be like this.
class CountryList {
var countries: [Country]!
init(fromJson json: JSON!) {
let countryArray = json[0].arrayValue
for countryJson in countryArray {
let value = Country(fromJson: countryJson)
countries.append(value)
}
}
}
class Country {
var code : String!
var countryID : Int!
var createdDate : String!
var name : String!
init(fromJson json: JSON!){
if json == nil{
return
}
code = json["Code"].stringValue
countryID = json["CountryID"].intValue
createdDate = json["CreatedDate"].stringValue
name = json["Name"].stringValue
}
}
How can I parse this array without a key before the square brackets in SwiftyJson? It doesn't give the array objects correctly.
I know this to do it in normal way like will convert the response as dictionary. But client advised to use SwiftyJson. So only am trying this in SwiftyJson.
Give me some suggestion and don't mark this question as duplicate. Because I don't get any reference from the internet to convert this by using SwiftyJson.
Two issues in your class CountryList
class CountryList {
// 1. the countries var is not initialized
// var countries: [Country]!
// Initialize it like below
var countries = [Country]()
init(fromJson json: JSON!) {
// 2 issue is that json itself is an array so no need of doing json[0].arrayValue
let countryArray = json.arrayValue
for countryJson in countryArray {
let value = Country(fromJson: countryJson)
countries.append(value)
}
}
}
I Found the answer by myself. I converted the response as NSArray like this
do {
let array = try JSONSerialization.jsonObject(with: response.data!) as? NSArray
let countryListData = CountryList(fromArray: array!)
completionHandler(true, countryListData)
} catch {
print("Exception occured \(error))")
}
And changed the CountryList class like this
class CountryList {
var countries = [Country]()
init(fromArray array: NSArray!) {
for countryJson in array {
let value = Country(fromJson: countryJson)
countries.append(value)
}
}
}
class Country {
var code : String!
var countryID : Int!
var createdDate : String!
var name : String!
init(fromJson json: JSON!){
if json == nil{
return
}
code = json["Code"].stringValue
countryID = json["CountryID"].intValue
createdDate = json["CreatedDate"].stringValue
name = json["Name"].stringValue
}
}
Now its working what I expected. Thanks for all your comments and answer.

Swift 3 : How to convert struct to Parameters

I have a struct as follows
struct UserInfo
{
var userId : Int
var firstName : String
var lastName : String
}
How do I serialize an instance of UserInfo to type Parameters?
var user = UserInfo(userId: 1, firstName: "John", lastName: "Skew")
// Convert user to Parameters for Alamofire
Alamofire.request("https://httpbin.org/post", parameters: parameters)
Just implement a dictionaryRepresentation computed variable or function:
struct UserInfo {
var userId : Int
var firstName : String
var lastName : String
var dictionaryRepresentation: [String: Any] {
return [
"userId" : userId,
"firstName" : firstName,
"lastName" : lastName
]
}
}
Usage:
var user = UserInfo(userId: 1, firstName: "John", lastName: "Skew")
let userDict = user.dictionaryRepresentation
You could use CodableFirebase library. Although it's main purpose is to use it with Firebase Realtime Database and Firestore, it actually does what you need - converts the value into dictionary [String: Any].
Your model would look the following:
struct UserInfo: Codable {
var userId : Int
var firstName : String
var lastName : String
}
And then you would convert it to dictionary in the following way:
import CodableFirebase
let model: UserInfo // here you will create an instance of UserInfo
let dict: [String: Any] = try! FirestoreEncoder().encode(model)
You cannot directly pass struct as parameter. You need to convert your struct into [String:Any]. I have added a new variable description that converts your content to dictionary
struct UserInfo
{
var userId : Int
var firstname : String
var lastname : String
var description:[String:Any] {
get {
return ["userId":userId, "firstname": self.firstname, "lastname": lastname] as [String : Any]
}
}
}
And the usage is
var user = UserInfo(userId: 1, firstname: "John", lastname: "Skew")
// Convert user to Parameters for Alamofire
Alamofire.request("https://httpbin.org/post", parameters: user.description)
If you're going to make parameters to be sent as a POST request then it should take a dictionary format as follows:
let userDict = ["userId" : user.userId, "firstname" : user.firstname, "lastname" : user.lastname]
This should work as "parameters" for your networking
In Swift 4 you can use Decodable/Codable Struct

Resources