How to convert a json payload to a custom object? - json

We have a json payload:
{
"aps": {
"alert": {
"title": "Payload",
"body": "Lets map this thing"
},
},
"type": "alert",
"message": "This is a message",
}
The custom object has been created:
class PushNotificationDetail {
var title: String //operation
var body: String //message
var type: detailType
var message: String?
init(title: String, body: String, type: detailType, message: string?){
self.title = title
self.body = body
self.type = type
self.message = message
}
}
The problem is mapping it correctly to the created object, what would be the best way to achieve this?

You should use Swift4 Codable protocol to initialise your object from the json returned by the api. You will need to restructure your structure to match the data returned by the api:
struct PushNotificationDetail: Codable, CustomStringConvertible {
let aps: Aps
let type: String
let message: String?
var description: String { return aps.description + " - Type: " + type + " - Message: " + (message ?? "") }
}
struct Aps: Codable, CustomStringConvertible {
let alert: Alert
var description: String { return alert.description }
}
struct Alert: Codable, CustomStringConvertible {
let title: String
let body: String
var description: String { return "Tile: " + title + " - " + "Body: " + body }
}
extension Data {
var string: String { return String(data: self, encoding: .utf8) ?? "" }
}
Playground Testing
let json = """
{"aps":{"alert":{"title":"Payload","body":"Lets map this thing"}},"type":"alert","message":"This is a message"}
"""
if let pnd = try? JSONDecoder().decode(PushNotificationDetail.self, from: Data(json.utf8)) {
print(pnd) // "Tile: Payload - Body: Lets map this thing - Type: alert - Message: This is a message\n"
// lets encode it
if let data = try? JSONEncoder().encode(pnd) {
print(data.string) // "{"aps":{"alert":{"title":"Payload","body":"Lets map this thing"}},"type":"alert","message":"This is a message"}\n"
print(data == Data(json.utf8)) // true
}
}

You can do this with a failable initialiser in your PushNotificationDetail class and a chained guard statement:
init?(jsonDict: [String : Any]) {
guard let typeString : String = jsonDict[“type”] as? String,
let message: String = jsonDict[“message”] as? String,
let aps : [String : Any] = jsonDict[“aps”] as? [String : Any],
let alert : [String : String] = aps[“alert”] as? [String : String],
let title : String = alert[“title”],
let body : String = alert[“body”] else { return nil }
// implement some code here to create type from typeString
self.init(title: title, body: body, type: type, message: message)
}
Hope that helps.

Related

Swift Parsing decode 2 different json with 1 url api

Hi im new in swift and im kinda still learning, so i try to make login controller and parse a json data if it corrects it parse a json data with id and stuff and if login is failed than the json will show a kinda message. i already make a struct for all the value data that required but i got this error that said its nil.
so, this is the json if the login is success :
[
{
"id": 891,
"name": "User",
"email": "qdpim#immobisp.com",
"status": "1"
} ]
and this is the json if login is failed :
[
{
"message": "Login Failed..",
"status": "0"
} ]
so basicly it has a same url i guess? but i dont know im kinda stuck in here and i need help
struct login : Codable {
let id : Int
let name : String
let email : String
let status : String
let message : String
init(dictionary : [String : Any]) {
id = (dictionary ["id"] as? Int)!
name = (dictionary ["name"] as? String)!
email = (dictionary ["email"] as? String)!
status = (dictionary ["status"] as? String)!
message = (dictionary ["message"] as? String)!
}
enum CodingKeys : String, CodingKey {
case id = "id"
case name = "name"
case email = "email"
case status = "status"
case message = "message"
}
}
func Login() {
let Email = EmailField.text!
let Pass = PasswordField.text!
print(api)
guard let JsonUrl = URL(string: api) else {return}
URLSession.shared.dataTask(with: JsonUrl) { (data, response, error) in
guard let data = data else {return}
do{
let parsing = try JSONDecoder().decode([login].self, from: data)
print(parsing)
self.Loginnn = parsing
let stats = self.Loginnn.map { $0.status}
if stats.contains("1"){
print("Login Success")
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
}else if stats.contains("0") {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: "Please Try Again ", preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
// so basicly i wanna run this alert action by search status if its contains "0"
}
}
}catch{
print(error)
}
}.resume()
}
so when i try to test to failed my login, i doesnt show the message in my json in my log, instead it show this error
"keyNotFound(CodingKeys(stringValue: "id", intValue: nil),
Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index
0", intValue: 0)], debugDescription: "No value associated with key
CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").",
underlyingError: nil))"
i just wanna pop some message or alert if the login is failed because or wrong password or email.....so maybe can someone help me how to do it the best way?
You can declare Success and Failure response types as below,
struct LoginSuccess: Decodable {
var id: Int
var name: String
var email: String
var status: String
}
struct LoginFailure: Decodable {
var status: String
var message: String
}
and then use as,
guard let JsonUrl = URL(string: api) else { return }
URLSession.shared.dataTask(with: JsonUrl) { (data, response, error) in
guard let data = data else { return }
if let success = try? JSONDecoder().decode([LoginSuccess].self, from: data).first {
GlobalVariable.UserId = String(success.id)
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
} else if let failure = try? JSONDecoder().decode([LoginFailure].self, from: data).first {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: failure.message, preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}.resume()
In this situation I would use JSONSerialization to decode the data to a [[String: Any]] and look at the content to determine what kind of message it is.
In my code I have assumed the "status" item tells us if it was a successful login or not but one could for instance look for the presence of "id" or the count of elements in the dictionary as well to determine the type of response
do {
let result = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
if let response = result.first, let status = response["status"] as? String {
if status == "1" {
if let id = response["id"] as? Int {
let ids = String(id)
//...
}
} else {
if let message = response["message"] as? String {
print(message)
}
}
}
} catch {
print(error)
}
Below is my solution used in the code from your question. Note that I have simplified the Login struct since it is only used when login was successful
struct Login {
let id : Int
let name : String
let email : String
}
do {
let result = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
if let response = result.first, let status = response["status"] as? String {
if status == "1" {
//handle success
let login = Login(id: response["id"] as? Int ?? 0,
name: response["name"] as? String ?? "",
email: response["email"] as? String ?? "")
self.Loginnn = login
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
} else {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: "Please Try Again ", preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
} catch {
print(error)
}
The success response only contains the keys ("id", "name", "email", "status")
[ { "id": 891, "name": "User", "email": "qdpim#immobisp.com", "status": "1" } ]
and the failure response only contains the keys ("message", "status")
[ { "message": "Login Failed..", "status": "0" } ]
If you want to use the same struct for both JSON responses, you should make the properties optional
struct login : Codable {
var id: Int?
var name: String?
var email: String?
var status: String?
var message: String?
}
Also, since your keys are the same as your properties, you don't need enum CodingKeys or init for that matter if you use JSONDecoder().decode
You've already got an answer (or three) for this, but I want to show you how to do it without using JSONSerialization or speculative decoding.
So we have some LoginSuccess and LoginFailure types that you want to decode:
struct LoginSuccess: Decodable {
var id: Int
var name: String
var email: String
}
struct LoginFailure: Decodable {
var message: String
}
And we want to discriminate between them based on a status that is in the same container as the fields of those types. So we create an enum:
enum LoginResult: Decodable {
case success(LoginSuccess)
case failure(LoginFailure)
enum Keys: CodingKey {
case status
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
if try container.decode(String.self, forKey: .status) == "1" {
self = .success(try LoginSuccess(from: decoder))
} else {
self = .failure(try LoginFailure(from: decoder))
}
}
}
Note that the enum's init does not call decoder.decode(LoginSuccess.self). It passes the decoder it was given to the LoginSuccess initializer. Same with LoginFailure. This means those initializers will extract values from the same container as the status field.
Test:
let successData = #"[ { "id": 891, "name": "User", "email": "qdpim#immobisp.com", "status": "1" } ]"#.data(using: .utf8)!
print(try JSONDecoder().decode([LoginResult].self, from: successData))
// Output:
[__lldb_expr_1.LoginResult.success(__lldb_expr_1.LoginSuccess(id: 891, name: "User", email: "qdpim#immobisp.com"))]
let failureData = #"[ { "message": "Login Failed..", "status": "0" } ]"#.data(using: .utf8)!
print(try JSONDecoder().decode([LoginResult].self, from: failureData))
// Output:
[__lldb_expr_1.LoginResult.failure(__lldb_expr_1.LoginFailure(message: "Login Failed.."))]
Note that because your example data is wrapped in [...], I decoded arrays of LoginResult.

Get user ID from JWT (JSON web token)

I am using the plugin to authenticate WordPress using api-rest : JWT Authentication for WP REST API
From the request to the server I get the following answer:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvbWlob3N0Lm9yZ1wvcHJ1ZWJhcyIsImlhdCI6MTU1MzcyNDM4MSwibmJmIjoxNTUzNzI0MzgxLCJleHAiOjE1NTQzMjkxODEsImRhdGEiOnsidXNlciI6eyJpZCI6IjIifX19.rgi5Q2c8RCoHRp-lJiJN8xQaOavn9T_q8cmf8v1-57o",
"user_email": "abc#test.com",
"user_nicename": "test",
"user_display_name": "Test"
}
So far everything works fine, but I need to know the user ID.
I have read that the token is coded in base64 and within this is the ID. Trying to decode, I see if the ID that I need is there.
In swift with this function I decode the token, but I can not get the dictionary ID.
func decode(_ token: String) -> [String: AnyObject]? {
let string = token.components(separatedBy: ".")
let toDecode = string[1] as String
var stringtoDecode: String = toDecode.replacingOccurrences(of: "-", with: "+") // 62nd char of encoding
stringtoDecode = stringtoDecode.replacingOccurrences(of: "_", with: "/") // 63rd char of encoding
switch (stringtoDecode.utf16.count % 4) {
case 2: stringtoDecode = "\(stringtoDecode)=="
case 3: stringtoDecode = "\(stringtoDecode)="
default: // nothing to do stringtoDecode can stay the same
print("")
}
let dataToDecode = Data(base64Encoded: stringtoDecode, options: [])
let base64DecodedString = NSString(data: dataToDecode!, encoding: String.Encoding.utf8.rawValue)
var values: [String: AnyObject]?
if let string = base64DecodedString {
if let data = string.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: true) {
values = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String : AnyObject]
}
}
return values
}
The dictionary that returns this function is:
["iss": https://myhost.me/test, "exp": 1554235730, "nbf": 1553630930, "iat": 1553630930, "data": {
user = {
id = 2;
};
}]
How do I get the ID from this dictionary?
Your code is pretty unswifty.
Basically don't use NS... classes in Swift if there is a native equivalent and a JSON dictionary is always value type ([String:Any]).
I recommend to add an Error enum, make the function can throw, decode the serialized token with Decodable and return the Token instance on success
struct Token : Decodable {
let data : UserData
struct UserData : Decodable {
let user : User
struct User : Decodable {
let id : String
}
}
}
You are encouraged to keep the parameter label in the method declaration
enum TokenError : Error {
case invalidJWTFormat, invalidBase64EncodedData
}
func decode(token: String) throws -> Token {
let components = token.components(separatedBy: ".")
guard components.count == 3 else { throw TokenError.invalidJWTFormat }
var decodedString = components[1]
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
while decodedString.utf16.count % 4 != 0 {
decodedString += "="
}
guard let decodedData = Data(base64Encoded: decodedString) else { throw TokenError.invalidBase64EncodedData }
return try JSONDecoder().decode(Token.self, from: decodedData)
}
and call it
do {
let userID = try decode(token: "eyJ0eXAi.....").data.user.id
} catch { print(error) }

How to parse JSON with custom parameters using Codable protocol

I have a JSON with keys
{
"yearOfManufacture":"20/9/2018",
"carSize":8,
"isNew":true,
"carAssets":[
{
"color":"5761807993001",
"nativeId":"{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}"
}
]
}
I am trying to parse using Codable protocol with struct models
struct Cars: Codable {
var yearOfManufacture: String?
var carSize: Int = 0
var isNew: Bool = true
var carAssets: [CarAssests]?
}
struct CarAssests: Codable {
var color: String?
var nativeId: String?
}
I am getting error like The data couldn’t be read because it isn’t in the correct format. I tried using CodingKeys with decoder container not getting the exact type of "nativeId": "{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}" not getting exact data type of this.
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .deferredToData
if let jsonData = jsonString.data(using: .utf8) {
do {
print(jsonData)
let assets = try decoder.decode(Cars.self, from: jsonData)
print(assets)
} catch {
print(error.localizedDescription)
}
}
I bet you are doing something like this:
let jsonString = """
{
"yearOfManufacture": "20/9/2018",
"carSize": 8,
"isNew": true,
"carAssets": [
{
"color": "5761807993001",
"nativeId": "{\"app\":\"1234/Car/Native_App\",\"web\":\" /8888/Car/Native_Car_Desktop\"}"
}
]
}
"""
In a multiline string, both \" and " mean the character ". So you have to write \\" to get the two characters \ and ":
let jsonString = """
{
"yearOfManufacture": "20/9/2018",
"carSize": 8,
"isNew": true,
"carAssets": [
{
"color": "5761807993001",
"nativeId": "{\\"app\\":\\"1234/Car/Native_App\\",\\"web\\":\\" /8888/Car/Native_Car_Desktop\\"}"
}
]
}
"""

Decode json output to a model

I have this json output that I want to parse using Codable:
{
"success": true,
"total": 1,
"users": [
{
"user": {
"id": "1",
"fname": "admin",
"lname": "admin",
"login": "admin",
"actif": "0",
"last_connection_date": "2018-01-18 16:02:34"
}
}
],
"msg": ""
}
And I just want to exctact the user's informations out of it.
My user's model
import RealmSwift
class User: Object, Codable {
#objc dynamic var id: String = ""
#objc dynamic var fname: String = ""
#objc dynamic var lname: String = ""
#objc dynamic var login: String = ""
// private enum CodingKeys : String, CodingKey {
// case id = "users[0].user.id"
// case fname = "users[0].user.fname"
// case lname = "users[0].lname"
// case login = "users[0].user.login"
// case password = "users[0].user.password"
// }
}
// Somewhere in my code
Alamofire.request(Path.userInformations(id: userId).rawValue).
responseJSON(completionHandler: { response in
do {
let user = try JSONDecoder().decode(User.self, from: response.data!)
} catch (let error) {
print(error.localizedDescription)
}
})
I've tried extracting the user's object, but wasn't successful casting it to Data to feed it to JSONDecoder().decode() method.
Responding to Vishal16 's comment
I've tried you first approach. It does not seem to work because, I think, of keyword "user" before the user's object. I've tried adding a new struct that wrap the user's object, but does not solve it.
struct ResponseBody : Codable {
var success : Bool?
var total : Int?
var users : [UserHolder]?
var msg : String?
var query_start : String?
var query_end : String?
var query_time : String?
var paging : Bool?
}
struct UserHolder : Codable {
var user: User?
enum CodingKeys: String, CodingKey {
case user = "user"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
user = try values.decodeIfPresent(User.self, forKey: .user)
}
}
I think your response class structure should be like:
import Foundation
struct ResponseBody : Codable {
var status : Bool?
var total : Int?
var users : [User]? //list of users
var msg : String?
enum CodingKeys: String, CodingKey {
case status = "status"
case total = "total"
case users = "users"
case msg = "msg"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
status = try values.decodeIfPresent(Bool.self, forKey: . status)
total = try values.decodeIfPresent(Int.self, forKey: . total)
users = try values.decodeIfPresent([User].self, forKey: . users)
msg = try values.decodeIfPresent(String.self, forKey: . msg)
}
}
Now you will able to retrive your JSON data to object
let jsonDecoder = JSONDecoder()
let response = try jsonDecoder.decode(ResponseBody.self, from: data)
for user in response.users {
// user object is here
}
#edit
If you do not want to parse full response to JSON object
First convert Data to JSON Object using
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as! Dictionary
Get users list string JSON then convert it to Data and after that data to User List object
if let responseBody = jsonResponse["users"] {
let dataBody = (responseBody as! String).data(using: .utf8)!
if let obj = Utils.convertToArray(data: dataBody) {
print(obj) // list of user obj
}
}
Hear is the method using in above implementation
class func convertToArray(data: Data) -> [AnyObject]? {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? [AnyObject]
} catch {
Constants.print(items: error.localizedDescription)
}
return nil
}
Hope this help you. Happy codding :)
So hear is the working code for you
It's just working fine in my Playground. Please see below screenshots
1.
2.
3.
Decode json output to a model
Result:
class User: Object, Codable {
#objc dynamic var id: String = ""
#objc dynamic var fname: String = ""
#objc dynamic var lname: String = ""
#objc dynamic var login: String = ""
}
class Users: Object, Codable {
#objc dynamic var users: [User]
}
And for decoding
let user = try JSONDecoder().decode(Users.self, from: response.data!)
I think it should resolve the issue.
The other way is, you have to convert your response.data to Dictionary to dig down to user object.

building objects from json response using NSJSONSerialization

Using swift 1.2, xcode 6.3 and IOS 8, Im trying to build an object from a json response using NSJSONSerialization class.
the json response is:
[{
"_id" : "5470def9e0c0be27780121d7",
"imageUrl" : "https:\/\/s3-eu-west-1.amazonaws.com\/myapi-static\/clubs\/5470def9e0c0be27780121d7_180.png",
"name" : "Mondo",
"hasVip" : false,
"location" : {
"city" : "Madrid"
}
}, {
"_id" : "540b2ff281b30f3504a1c72f",
"imageUrl" : "https:\/\/s3-eu-west-1.amazonaws.com\/myapi-static\/clubs\/540b2ff281b30f3504a1c72f_180.png",
"name" : "Teatro Kapital",
"hasVip" : false,
"location" : {
"address" : "Atocha, 125",
"city" : "Madrid"
}
}, {
"_id" : "540cd44581b30f3504a1c73b",
"imageUrl" : "https:\/\/s3-eu-west-1.amazonaws.com\/myapi-static\/clubs\/540cd44581b30f3504a1c73b_180.png",
"name" : "Charada",
"hasVip" : false,
"location" : {
"address" : "La Bola, 13",
"city" : "Madrid"
}
}]
the object class (Club.swift) with the NSJSONSerialization.JSONObjectWithData implementation is:
class Club: NSObject {
var id: String = ""
var name: String = ""
var imageUrl: String = ""
var hasVip: Bool = false
var desc: String = ""
var location: [Location] = []
init(JSONString: String) {
super.init()
var error : NSError?
let JSONData = JSONString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let JSONDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(JSONData!, options: nil, error: &error) as! NSDictionary
self.setValuesForKeysWithDictionary(JSONDictionary as [NSObject : AnyObject])
}
}
and finally the ApiClient class is
class ApiClient {
func getList(completionHandler: ([JSON]) -> ()) {
let URL = NSURL(string: "https://myapi.com/v1/clubs")
let mutableURLRequest = NSMutableURLRequest(URL: URL!)
mutableURLRequest.setValue("Content-Type", forHTTPHeaderField: "application/json")
mutableURLRequest.HTTPMethod = "GET"
mutableURLRequest.setValue("Bearer R01.iNsG3xjv/r1LDkhkGOANPv53xqUFDkPM0en5LIDxx875fBjdUZLn1jtUlKVJqVjsNwDe1Oqu2WuzjpaYbiWWhw==", forHTTPHeaderField: "Authorization")
let manager = Alamofire.Manager.sharedInstance
let request = manager.request(mutableURLRequest)
request.responseJSON { (request, response, json , error) in
if (json != nil){
var jsonObj = JSON(json!)
if let data = jsonObj["hits"].arrayValue as [JSON]?{
var aClub : Club = Club(JSONString: data)
println(aClub.name)
completionHandler(data)
}
}
}
}
}
but the problem is when I try to println(aClub.name) the error is
"cannot invoke initializer for type'Club' with an argument list of type (JSONString [JSON])"
I dont know, how could I use NSJSONSerialization class with a complex JSON response.
The jsonObj would appear to be a SwiftyJSON object, or something like that, which one uses in lieu of NSJSONSerialization, not in conjunction with it. The data variable is an array of JSON objects (i.e. it's a [JSON]), not a string.
But you're using Alamofire's responseJSON method, which does the JSON parsing for you. So you don't need to use either NSJSONSerialization or SwiftyJSON. It's already parsed it into an array of dictionaries.
If you want an array of Club objects, you could do can just iterate through this array, building Club objects from the dictionaries:
class ApiClient {
func getList(completionHandler: ([Club]?, NSError?) -> ()) {
let URL = NSURL(string: "https://myapi.com/v1/clubs")
let mutableURLRequest = NSMutableURLRequest(URL: URL!)
mutableURLRequest.setValue("Content-Type", forHTTPHeaderField: "application/json")
mutableURLRequest.HTTPMethod = "GET"
mutableURLRequest.setValue("Bearer R01.iNsG3xjv/r1LDkhkGOANPv53xqUFDkPM0en5LIDxx875fBjdUZLn1jtUlKVJqVjsNwDe1Oqu2WuzjpaYbiWWhw==", forHTTPHeaderField: "Authorization")
let manager = Alamofire.Manager.sharedInstance
let request = manager.request(mutableURLRequest)
request.responseJSON { (request, response, json, error) in
var clubs = [Club]()
if let arrayOfDictionaries = json as? [[String: AnyObject]] {
for dictionary in arrayOfDictionaries {
clubs.append(Club(dictionary: dictionary))
}
completionHandler(clubs, nil)
} else {
completionHandler(nil, error)
}
}
}
}
You obviously have to change Club to handle the dictionary object:
class Club {
var id: String!
var name: String!
var imageUrl: String!
var hasVippler: Bool!
var location: [String: String]!
init(dictionary: [String: AnyObject]) {
id = dictionary["_id"] as? String
name = dictionary["name"] as? String
imageUrl = dictionary["imageUrl"] as? String
hasVippler = dictionary["hasVip"] as? Bool
location = dictionary["location"] as? [String: String]
}
}
Finally, your table view controller could call the API:
let apiClient = ApiClient()
var clubs: [Club]!
override func viewDidLoad() {
super.viewDidLoad()
apiClient.getList() { clubs, error in
if clubs != nil {
self.clubs = clubs
self.tableView.reloadData()
} else {
println(error)
}
}
}