How to Convert a model object into JSON?
I want "Answer" object.
// Answers Class
class Answers {
var cat_id: String!
var responses = [Response]()
var date: String!
var comment: String!
var time: String!
var lat: String!
var lon: String!
var address: String!
}
// Response Class
class Response {
var que_id: String!
var question: String!
var response: String!
}
Make both types conform to Codable:
class Answers: Codable {
...
}
class Response: Codable {
...
}
And then use JSONEncoder:
let answers: Answers = ...
do {
let data = try JSONEncoder().encode(answers)
// use data here
} catch {
print(error)
}
See Encoding and Decoding Custom Types.
if you are using swift4, You can use encodable and decodable protocol. I'm still working on a heterogeneous list of objects. But this should work for you. Make your class conform to ABEncodable.
protocol ABDecodable: Decodable {
static func decodeFromData(_ data: Data) -> Decodable?
}
protocol ABEncodable: Encodable {
static func encodeFromObject<T>(_ object: T) -> Data? where T: Encodable
}
extension ABDecodable {
static func decodeFromData(_ data: Data) -> Decodable? {
do {
return try JSONDecoder().decode(self, from: data)
}
catch {
print(error)
}
return nil
}
}
extension ABEncodable {
static func encodeFromObject<T>(_ object: T) -> Data? where T: Encodable {
do {
return try JSONEncoder().encode(object)
}
catch {
return nil
}
}
}
//MARK: Decode mapper class
//Send jsonString or data to decode it into an required Object
final class Decode<T: Decodable> {
private func decodeData(_ data: Data) -> T? {
if let klass = T.self as? ABDecodable.Type {
if let object = klass.decodeFromData(data) as? T {
return object
}
}
else {
do {
return try JSONDecoder().decode(T.self, from: data)
}
catch {
print(error)
}
}
return nil
}
func fromJsonString(_ json: String) -> T? {
guard let data = json.data(using: String.Encoding.utf8) else { return nil }
if let object = decodeData(data) {
return object
}
return nil
}
func fromData(_ data: Data) -> T? {
if let object = decodeData(data) {
return object
}
return nil
}
}
//MARK: Encode mapper class
//Send jsonString or data to decode it into an required Object
final class Encode<N:Encodable> {
private func encodeObject(_ object: N) -> Data? {
if let klass = N.self as? ABEncodable.Type {
if let data = klass.encodeFromObject(object) {
return data
}
}
else {
do {
return try JSONEncoder().encode(object)
}
catch {
print(error)
}
}
return nil
}
func toJsonString(_ object: N) -> String? {
if let data = encodeObject(object) {
return String(data: data, encoding: .utf8)
}
return nil
}
func toData(_ object: N) -> Data? {
if let data = encodeObject(object) {
return data
}
return nil
}
}
Related
I want to determine in my method whether the generic type is an array and if so, read some static variable of the type that is the Element of the array.
I thought that this would work:
print(T.self)
print(T.self is Array<SPTBaseObject>.Type)
if let A = T.self as? Array<SPTBaseObject>.Type {
print(A.Element.pluralKey)
}
But the output from the console reads:
Array<SPTArtist>
false
Changing the second line to
print(T.self is Array<SPTArtist>.Type)
Prints true, but it seems pointless to check against every possible type. I just want it to return true when the Element is a class inheriting from the SPTBaseObject.
The SPTArtist is a class inheriting from the SPTBaseObject.
public class SPTArtist: SPTBaseObject {
...
}
public class SPTBaseObject: Codable {
internal class var pluralKey: String? {
return nil
}
}
EDIT:
My method looks like this
private class func perform<T>(request: URLRequestConvertible, completion: #escaping (Result<T, Error>) -> Void) where T: Decodable {
AF.request(request).responseData { response in
if let error = response.error {
completion(.failure(error))
}
guard let data = response.value else {
completion(.failure(SPTError.noDataReceivedError))
return
}
if let error = try? JSONDecoder().decode(SPTErrorResponse.self, from: data) {
completion(.failure(error.error))
return
}
// Check if T is an array, if so try to decode a Root<T> object. Here's an error, I cannot construct the Root type like this although the Element is guaranteed to be a SPTBaseObject subclass
if let A = T.self as? Array<SPTBaseObject>.Type,
let root = try? JSONDecoder().decode(Root<A.Element.self>, from: data) {
completion(.success(root.items))
} else if let object = try? JSONDecoder().decode(T.self, from: data) {
completion(.success(object))
} else {
completion(.failure(SPTError.decodingError))
}
}
}
The Root class which will utilize the pluralKey property of SPTBaseObject
class Root<T>: Decodable where T: SPTBaseObject {
let items: [T]
private struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let key = T.pluralKey, let codingKey = CodingKeys(stringValue: key) else {
fatalError()
}
items = try container.decode([T].self, forKey: codingKey)
}
}
If I understood correctly, you can use a function that takes an array of [T] where T should be subclass of SPTBaseObject and an overload that takes only one T object, like this:
func getPluralKey<T: SPTBaseObject>(_ array: [T]) {
print(T.pluralKey ?? "nil")
}
func getPluralKey<T: SPTBaseObject>(_ object: T) {
print(T.pluralKey ?? "nil")
}
I am trying to parse the data and display on the screen but i am getting " Value of type 'EmployeeData' has no member 'employee_name' "
What i am missing ?
I created my struct, parsed data and tried to divide into two parts. first part will be related with listing, second part is all data.
struct EmployeeData: Codable {
var data: Employee
var status: String
}
struct Employee: Codable {
var employee_name: String
var employee_salary: String
var employee_age: String
}
class WebServices {
func getData(completion: #escaping (EmployeeData?) -> ()){
guard let url = URL(string:"http://dummy.restapiexample.com/api/v1/employees")
else { fatalError("There is error!") }
URLSession.shared.dataTask(with: url) { (data, response,error) in
guard let data = data, error == nil else {
DispatchQueue.main.async{
completion(nil)
}
return
}
let empleyees = try? JSONDecoder().decode(EmployeeData.self, from: data)
DispatchQueue.main.async {
completion(empleyees)
}
}.resume()
}
}
class MVDesingnListView: ObservableObject {
}
struct MVDesignCellView {
let employeeDatas: EmployeeData
init(employeeDatas: EmployeeData) {
self.employeeDatas = employeeDatas
}
var employee_name: String {
self.employeeDatas.employee_name
}
}
The compiler is all right. Your struct EmployeeData has no member employee_name.
You need to go to the employee first, to get her name:
var employee_name: String {
self.employeeDatas.data.employee_name
}
should do the job.
Here, we have a scenario where I am facing issue for parsing the model class using "JSONDecoder".
Let me share you what I have done in this example and where I am facing issue:
There's a model protocol derived through the Codable.
A struct model and A class GenericExample
GenericExample class have following things:
shared instance
typeContainer which is the type of Protocol
There's two more methods staticClassParsing and dynamicClassParsing with one parameter dynamicType
In the last, One generic method which return the parsed data model.
Calling method for parsing the model:
GenericExample.shared.dynamicClassParsing(Numbers.self,data: "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]")
Compile Time Error:
Generic parameter 'T' could not be inferred
Occurred here
returnModelType: typeContainer.self
**For more details, please go through following code: **
protocol BaseMapModel : Codable { }
struct Numbers: BaseMapModel {
var one: String?
var two: String?
}
class GenericExample: NSObject {
static let shared = GenericExample()
var typeContainer : BaseMapModel.Type?
private override init() {
super.init()
}
}
extension GenericExample {
// Static Class Parsing passed through the conversion
func staticClassParsing() {
let dataJson = "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]"
convertTypeContainer(data: Data(dataJson.utf8), returnModelType: Numbers.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
// Dynamic Class Parsing can't passed through the conversion
// Error:- Generic parameter 'T' could not be inferred
// Error Parameter:- in "returnModelType: typeContainer.self"
func dynamicClassParsing(_ dynamicType: BaseMapModel.Type, data:String) {
typeContainer = dynamicType.self
convertTypeContainer(data: Data(data.utf8), returnModelType: typeContainer.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
}
extension GenericExample {
private func convertTypeContainer<T : BaseMapModel>(data:Data, returnModelType: T.Type, completion: ((_ result:T?)->Void)) {
guard let responseModel = try? JSONDecoder().decode(returnModelType.self, from: data) else {
completion(nil)
return
}
completion(responseModel)
}
}
typeContainer must be a concrete type, it cannot be a protocol. And the completion handler is pointless as JSONDecoder works synchronously.
To decode JSON with generics you have to use something like this, it's highly recommended to handle also the Decoding error
struct Numbers: Decodable {
var one: String?
var two: String?
}
class GenericExample: NSObject {
static let shared = GenericExample()
}
extension GenericExample {
func dynamicClassParsing<T : Decodable>(_ dynamicType: T.Type, data: String) -> Result<T,Error> {
return Result { try JSONDecoder().decode(T.self, from: Data(data.utf8)) }
}
}
let dataJson = """
[{"one": "1"},{"two":"2"}]
"""
let result = GenericExample.shared.dynamicClassParsing([Numbers].self, data: dataJson)
switch result {
case .success(let numbers): print(numbers)
case .failure(let error): print(error)
}
First of all, Thanks to All for your support. And Yes, I would like to post answer to my question.
class BaseMapModel : Codable { }
class Numbers: BaseMapModel {
var one: String?
var two: String?
enum CodingKeys: String, CodingKey {
case one = "one"
case two = "two"
}
// Decoding
required init(from decoder: Decoder) throws {
let response = try decoder.container(keyedBy: CodingKeys.self)
one = try? response.decode(String.self, forKey: .one)
two = try? response.decode(String.self, forKey: .two)
let superDecoder = try response.superDecoder()
try super.init(from: superDecoder)
}
}
class GenericExample: NSObject {
static let shared = GenericExample()
var defaultTypeContainer : Numbers.Type!
var typeContainer : BaseMapModel.Type?
private override init() {
super.init()
}
}
extension GenericExample {
// Static Class Parsing passed through the conversion
func staticClassParsing() {
let dataJson = "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]"
convertTypeContainer(data: Data(dataJson.utf8), returnModelType: Numbers.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
// Dynamic Class Parsing passed through the conversion
func dynamicClassParsing(_ dynamicType: BaseMapModel.Type, data:String) {
typeContainer = dynamicType.self
convertTypeContainer(data: Data(data.utf8), returnModelType: (typeContainer ?? defaultTypeContainer).self) { (mappedResult) in
print((mappedResult as? Numbers)?.one ?? "")
print((mappedResult as? Numbers)?.two ?? "")
}
}
}
extension GenericExample {
private func convertTypeContainer<T : BaseMapModel>(data:Data, returnModelType: T.Type, completion: ((_ result:T?)->Void)) {
guard let responseModel = try? JSONDecoder().decode(returnModelType.self, from: data) else {
completion(nil)
return
}
completion(responseModel)
}
}
Previously I successfully access model objects anywhere in the class but while populating data on badoo/chatto text view I am stuck.
I am integrating chat message api into badoo chat view
Basically, the issue is alamofire response is not getting outside of the scope.
Did I try with compilation handler but no luck? Is there any way to resolve this issue?
Thanks in advance .
Here is code snippet :
import Foundation
import Chatto
import ChattoAdditions
import SwiftyJSON
import Alamofire
class DemoChatMessageFactory {
public static var chats = [ChatModel]()
class func makeMessage(_ uid:String) -> DemoTextMessageModel{
print("uid makeMessage : \(uid)")
return self.makeMessageData(uid, isIncoming:false)
}
class func makeMessageData(_ uid: String,isIncoming:Bool) -> DemoTextMessageModel {
if isIncoming == true{
return self.makeTextFinalMessage(uid, isIncoming:isIncoming)
} else {
return self.makeTextFinalMessage(uid, isIncoming: isIncoming)
}
}
public class func makeTextMessage(_ uid: String, isIncoming: Bool,text:String) -> DemoTextMessageModel {
let messageModel = self.makeMessageModel(uid, isIncoming: isIncoming,
type: TextMessageModel<MessageModel>.chatItemType)
let textMessageModel = DemoTextMessageModel(messageModel:messageModel,
text: text)
return textMessageModel
}
public class func makeTextFinalMessage(_ uid: String, isIncoming: Bool) -> DemoTextMessageModel {
var text = String()
var uidInt = Int(uid)
print("string uid 121 \(uid)")
print("print is Incomming data or not 1: \(isIncoming)")
print("uid count :\(uid.count)")
let urlString = "[My message Api]"
Alamofire.request(urlString, method: .get).validate().responseJSON {
(response) -> Void in
if let value = response.data {
do {
let json = try JSON(data: value)
if let dictionnary = json.dictionaryObject {
if let messageArray = dictionnary["message"] as?[[String: Any]] {
self.chats.removeAll()
for arr in messageArray {
self.chats.append(ChatModel(ChatListJSON: arr))
}
}
}
} catch {
print("cannot convert to Json")
}
}
print("print int 122 : \(uidInt!)")
print("Chat List Id DemoChatMessageFactory \(self.chats[uidInt!].chatId)")
print("chat message: \(String(describing: uidInt!)) th \(self.chats[uidInt!].chatMessage)")
self.textData = "\(self.chats[uidInt!].chatMessage)"
self.makeTextMessage(uid, isIncoming: isIncoming, text:self.textData) //Here I am bale to pass textData but ouside the Alamofire block can't access
}
//Here getting empty values
print("uid makeTextFinalMessage \(uid)")
print("in coming makeTextFinalMessage \(isIncoming)")
print("text makeTextFinalMessage \(text)")
//chat count also getting zero count
print("chat count final text\(chats.count)")
print("print chat count : \(self.chats.count)")
return self.makeTextMessage(uid, isIncoming: isIncoming, text:self.textData)
}
}
Test for completion handler
public var res: Any = ""
func getAllChatData(completionhandler:#escaping ([String: Any]?) -> ()){
let URL = "my api"
Alamofire.request(URL).responseJSON {
response in
if let json = response.result.value as? [String: Any] {
completionhandler(json, nil)
}
else if let error = response.result.error as Error? {
completionhandler(nil, error)
}
}
}
and call using like below inside the function
DemoChatMessageFactory.getAllChatData {
(result) in
res = result
print("response (res)")
}
please suggest me the proper way to alamofire with compilation handler
This is an example of converting all methods using the result of asynchronous call. As I have never used Chatto and you are not showing all the types in your code, so you may need to modify many parts of my code, but I believe you can see what you need to do with this code.
import Foundation
import Chatto
import ChattoAdditions
import SwiftyJSON
import Alamofire
class DemoChatMessageFactory {
public static var chats = [ChatModel]()
class func requestMessage(_ uid:String,
completion: #escaping (DemoTextMessageModel?, Error?)->Void) {
print("uid makeMessage : \(uid)")
self.requestMessageData(uid, isIncoming: false) { (model, error) in
completion(model, error)
}
}
class func requestMessageData(_ uid: String, isIncoming: Bool,
completion: #escaping (DemoTextMessageModel?, Error?)->Void) {
if isIncoming {
//...put any code needed when isIncoming is true
self.requestTextFinalMessage(uid, isIncoming: isIncoming) { model in
completion(model, error)
}
} else {
//...put any code needed when isIncoming is false
self.requestTextFinalMessage(uid, isIncoming: isIncoming) { model in
completion(model, error)
}
}
}
public class func makeTextMessage(_ uid: String, isIncoming: Bool, text: String) -> DemoTextMessageModel {
let messageModel = self.makeMessageModel(uid, isIncoming: isIncoming,
type: TextMessageModel<MessageModel>.chatItemType)
let textMessageModel = DemoTextMessageModel(messageModel:messageModel,
text: text)
return textMessageModel
}
public class func requestTextFinalMessage(_ uid: String, isIncoming: Bool,
completion: #escaping (DemoTextMessageModel?, Error?)->Void) {
var text = String()
var uidInt = Int(uid)
print("string uid 121 \(uid)")
print("print is Incomming data or not 1: \(isIncoming)")
print("uid count :\(uid.count)")
let urlString = "[My message Api]"
Alamofire.request(urlString, method: .get).validate().responseJSON {
(response) -> Void in
if let value = response.data {
do {
let json = try JSON(data: value)
if let dictionnary = json.dictionaryObject {
if let messageArray = dictionnary["message"] as?[[String: Any]] {
self.chats.removeAll()
for arr in messageArray {
self.chats.append(ChatModel(ChatListJSON: arr))
}
}
}
print("print int 122 : \(uidInt!)")
print("Chat List Id DemoChatMessageFactory \(self.chats[uidInt!].chatId)")
print("chat message: \(String(describing: uidInt!)) th \(self.chats[uidInt!].chatMessage)")
self.textData = "\(self.chats[uidInt!].chatMessage)"
completion(self.makeTextMessage(uid, isIncoming: isIncoming, text: self.textData), nil)
} catch {
print("cannot convert to Json")
completion(nil, error)
}
} else {
//better generate an error case result, and call completion.
//...
}
}
}
}
I changed some method names from make... to request... to show clarify they are asynchronous methods.
And the usage, if you intend to use your original code as:
let model = DemoChatMessageFactory.makeMessage(uid)
//Do some UI updates using `model`...
You may need to use asynchronous methods like:
DemoChatMessageFactory.requestMessage(uid) { (model, error) in
if let model = model {
//Do some UI updates using `model`...
} else {
//Do something for the error...
}
}
I have a request
Alamofire.request(.GET,HttpHelper.baseURL+HttpHelper.tripsURL,encoding:.JSON).responseJSON {
response in
var json = JSON(data: response.data!)
print(json)
print(json["res"])
}
followed by the result
{
"res" : "[{\"name\":\"testName\",\"lastName\":\"testLastName\"},{\"name\":\"testName\",\"lastName\":\"testLastName\"}]",
"status" : "success",
"out" : "{\"name\":\"testName\",\"lastName\":\"testLastName\"}"
}
[{"name":"testName","lastName":"testLastName"},{"name":"testName","lastName":"testLastName"}]
how i can set data from res to struct or class User
struct User {
var name : String?
var lastName : String?
}
please help to solve this problem) thank you very much !!)
You can do something like that
var result: [User]()
for user in json["res"] {
let userTmp = User(name: user["name"], lastName: user["lastName"])
result.append(userTmp)
}
Regards
Basically, it would be:
class User {
var name : String?
var lastName : String?
}
var theUsers = [User]()
Alamofire.request(.GET,HttpHelper.baseURL+HttpHelper.tripsURL,encoding:.JSON)
.responseJSON { response in
var json = JSON(data: response.data!)
print(json)
theUsers = json["res"].map {
return User (name: $0["name"], lastName: $0.["lastName"])
}
})
However, along the way, you might need some typecasting. For example, maybe replace json["res"] with (json["res"] as Array<Dictionary<String,String>>) in order to keep the type checker and type inferencer happy.
I'm using native Codable protocol to do that:
class MyClass: Codable {
var int: Int?
var string: String?
var bool: Bool?
var double: Double?
}
let myClass = MyClass()
myClass.int = 1
myClass.string = "Rodrigo"
myClass.bool = true
myClass.double = 2.2
if let json = JsonUtil<MyClass>.toJson(myClass) {
print(json) // {"bool":true,"string":"Rodrigo","double":2.2,"int":1}
if let myClass = JsonUtil<MyClass>.from(json: json) {
print(myClass.int ?? 0) // 1
print(myClass.string ?? "nil") // Rodrigo
print(myClass.bool ?? false) // true
print(myClass.double ?? 0) // 2.2
}
}
And I created a JsonUtil to help me:
public struct JsonUtil<T: Codable> {
public static func from(json: String) -> T? {
if let jsonData = json.data(using: .utf8) {
let jsonDecoder = JSONDecoder()
do {
return try jsonDecoder.decode(T.self, from: jsonData)
} catch {
print(error)
}
}
return nil
}
public static func toJson(_ obj: T) -> String? {
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(obj)
return String(data: jsonData, encoding: String.Encoding.utf8)
} catch {
print(error)
return nil
}
}
}
And if you have some issue with Any type in yours objects. Please look my other answer:
https://stackoverflow.com/a/51728972/3368791
Good luck :)