Hi i am new to iOS and I am using Alamofire for network calls. The things were going good and I am facing no trouble in making network calls. But since I have to post my custom object I am having no luck. so here are the things I was doing before
let parameters: Parameters = [
"Phone": phone,
"ApiKey":"x-y-z"
]
this was working fine.
but now I have to post my objects like
let parameters: Parameters = [
"ApiKey": Common.API_KEY,
"cardModel": cardModel,
"clientModel" : clientModel
]
My cardModel and client model are already converted in Json string i am just putting them into dictionary. the converted model looks like these
"cardModel": {
"ExpiryYear": 2018,
"ExpiryMonth": 1,
"CardNumber": "55555",
"CardHolderName": "xyz"
}
so I am putting these serialized models in the dictionary and post this data into request body using Alamofire.
But on server side these Models are null. Any idea how to put custom model in the way I want ? please help
PS I just print out my parameters dictionary and I have examined this output
["ApiKey": "x-y-z",
"\cardModel\": "{
"\ExpiryYear\": 2018,
"\ExpiryMonth\": 1,
"\CardNumber\": "\55555\",
"\CardHolderName\": "\xyz\"
}
]
I put that parameters json printed output in jsonLint and it was wrong format. I just removed the "\" and replaced [] with {} and then it appears to be valid Json
So what I should do now???? please help
Update1:
this is valid json for my endpoint (sending from android)
{
"ApiKey": "XXX-XXX-XXX",
"cardModel": {
"CardHolderName": "Fjj",
"CardNumber": "555",
"CardExpiryMonth": 1,
"CardExpiryYear": 2018
......
}
}
First you need to have a method/computed property that converts your model into a dictionary of type [String: AnyObject] or [String: Any].
Then instead of adding your model into your parameter dictionary add the model dictionary like in below example.
class CardModel: NSObject {
var expiryYear: Int?
var expiryMonth: Int?
var cardNumber: String?
var cardHolderName: String?
var jsonDict: [String: AnyObject] {
var json: [String: AnyObject] = [:]//Initializing an Empty Dictionary
json["expiryYear"] = expiryYear as AnyObject // Inserting expiryYear into Dictionary
json["expiryMonth"] = expiryMonth as AnyObject // Inserting expiryMonth into Dictionary
json["cardNumber"] = cardNumber as AnyObject // Inserting cardNumber into Dictionary
json["cardHolderName"] = cardHolderName as AnyObject // Inserting cardHolderName into Dictionary
return json // returning newly populated dictionary
}
}
func requestToServer(cardModel: CardModel) {
var parameters = [String: AnyObject]()
parameters["ApiKey"] = "dfv12345df234t" as AnyObject
parameters["cardModel"] = cardModel.jsonDict as AnyObject// adding card model dictionary into you paramters dictionary.
//Same logic will be use for your next clientModel
}
I suggest you read up on the Codable protocol, it is a very elegant way to map basic Swift-types to JSON-data. A Playground will help you with the following
import Cocoa
let jsonData = """
{
"ApiKey": "XXX-XXX-XXX",
"cardModel": {
"CardHolderName": "Fjj",
"CardNumber": "555",
"CardExpiryMonth": 1,
"CardExpiryYear": 2018
}
}
""".data(using: .utf8)!
// ... missing here (but added a closing brace for cardModel
struct CardModel: Codable {
let holderName: String
let number: String
let expiryMonth: Int
let expiryYear: Int
enum CodingKeys: String, CodingKey {
case holderName = "CardHolderName"
case number = "CardNumber"
case expiryMonth = "CardExpiryMonth"
case expiryYear = "CardExpiryYear"
}
}
struct ApiCardPayment: Codable {
let apiKey: String
let cardModel: CardModel
enum CodingKeys: String, CodingKey {
case apiKey = "ApiKey"
case cardModel
}
}
do {
let apiPayment = try JSONDecoder().decode(ApiCardPayment.self, from:jsonData)
print(apiPayment)
} catch {
print(error)
}
This is much easier to handle than the [String:AnyObject] casting nightmare you will probably have to interpret otherwise. Besides the error messages of the JSONDecoder have been improving rapidly and it is now rather good at pointing out what is going wrong.
P.S.: Of course there is also JSONEncoder().encode(), but that is just the easy part anyways.
Related
I want to call a API with a post and give it parameters about the body. I need the parameter in following style.
"linked_users": [ "5dc73d6e1c20540b24336681", "5dca64f4bf98ec2ada3b315e"]
In Postman the json above is working, but if I create it I Swift, I get a bad request parsing error from the API.
var linkedUserString = ""
if let data = try? JSONSerialization.data(withJSONObject: stringArray, options: []){
if let json = String(data: data, encoding: String.Encoding.utf8)
{
linkedUserString = json
}
}
The value of linkedUserString is
"[\"5dc73d6e1c20540b24336681\",\"5dca64f4bf98ec2ada3b315e\"]"
This String is converted with Codable to the final body value with the other parameters.
- description : "location description"
▿ location : LocationCodable
- latitude : "0.0"
- longitude : "0.0"
- description : "Test Ort"
- linked_users : "[\"5dc73d6e1c20540b24336681\",\"5dca64f4bf98ec2ada3b315e\"]"
If I call the API without the linked_users parameter it works fine.
Somebody knows what I am doing wrong?
Make correct model:
struct Model: Codable {
var description: String!
var location: LocationCodable!
var linked_users: [String]!
}
and encode it, instead of encoding parameter linked_users itself.
I previously made a post about how to save json data in a model here .
Good now I am developing a project on iOS with swift, and I face the following problem, it happens that sometimes the database administrators change the names of the columns constantly, I only consume services with the help of Alamofire, to save data in models, use camal case and snake case, At the moment everything is fine but I would like to know what is the best way to save json data in a swift model, in my experience with Android I used retrofit with #Serializename and it worked great because if the json attribute of the service it was modified I only had to update a line of code and my variable could be kept the same, this helped me maintain a better order and it made it scalable.
In some cases the json comes to me.
{
"price": "385.000000",
"nameusser": null,
"favorite": 43,
"short_nameProduct": "Génifique Repair Sc",
"description_product": "Génifique repair sc es la crema de noche antiedad de lancôme. Despiértese con una piel fresca y rejuvenecida con nuestra crema de noche.",
"alt": null,
"photo": "https://url/020021000112-1.png"
}
in swift it would generate my model in the following way.
struct Product : Codable {
let price : String?
let nameusser : String?
let favorite : Int
let shortNameProduct : [Product]
let description : [Galery]
let alt : Product
let success : Bool
}
The problem here is that my variables must fit the json I get to use the JSONDecoder() and the convertFromSnakeCase, I can not define them myself.
while in java android I just have to do it like that.
#SerializedName("price")
private String price;
#SerializedName("nameusser")
private String name;
#SerializedName("favorite")
private Int favorite;
#SerializedName("short_nameProduct")
private String shortName;
#SerializedName("description_product")
private String descriptionProduct;
#SerializedName("altitude")
private String altitude;
#SerializedName("photo")
private String photo;
I just have to create the get and set and I would be ready to use the model.
I need to know how to do in swift the same, maybe a library that helps me store data json in the same way that I do in android.
Any comment would be very appreciated.
The best way is to use the Coding Keys:
struct Product : Codable {
let price : String?
let nameusser : String?
let favorite : Int
let shortNameProduct : [Product]
let description : [Galery]
let alt : Product
let success : Bool
enum CodingKeys: String, CodingKey {
case price = "price"
case nameusser = "nameusser"
case favorite = "favorite"
case shortNameProduct = "short_nameProduct"
case description = "description_product"
case alt = "alt"
case success = "success"
}
}
The name of the enum case has to match the property name on the struct. This way you can define whatever keys you want without having to write any custom encoding or decoding code.
Feel free to use my gist here:
Storage
You can use it like this:
let fileName = "Product.json"
extension Product {
func store() {
Storage.store(self, to: .documents, as: fileName)
}
static func retrieve() -> Product? {
guard let product = Storage.retrieve(fileName, from: .documents, as: Product.self) else {
return nil
}
return product
}
}
With Alamofire you will get a key-value, an Dictionary<String,Any> or [String:Any]
So you can do the following with your dictionary:
var myProduct : Product?
if let price = myDictionary["price"] as? String{
myProduct.price = price
}
With that example you can create a method to mapping the entire JSON into your struct. Maybe if you want to make it more scalable, you can create an enum with String raw values and use it as the key for the dictionary, some like:
enum productProperty : String{
case Price = "price"
}
var myProduct : Product?
if let price = myDictionary[productProperty.Price] as? String{
myProduct.price = price
}
And maybe create a more complex class to iterate trough the dictionary and check the key using the enum, but that implementation depends of your own skills.
Edit1:
To use't with alamofire, you need to get the jsonResponse of the request, some like that:
.request(yourURL, method: .get, parameters: yourParameter).responseJSON(options: .allowFragments , completionHandler: { response in
if let object = response.result.value as? Dictionary<String,Any> {
yourMethodToSave(object)
}
})
and inside yourMethodToSave(_ object: Dictionary<String,Any>) you need to put the logic above.
Ps: The #sendtobo answer have the enum example that i tell that you can use to a more scalable mapping for your object
I have an API and I need to call, to get a list of holidays with some additional info along with it. The link of my API - http://mahindralylf.com/apiv1/getholidays
The structure I created using the website app.quicktype.io
struct Holiday: Codable {
let responseCode, responseMsg: String
let holidayCount: Int
let holidays: [HolidayElement]
enum CodingKeys: String, CodingKey {
case responseCode = "response_code"
case responseMsg = "response_msg"
case holidayCount = "holiday_count"
case holidays
}
}
struct HolidayElement: Codable {
let month: String
let image: String
let details: [Detail]
}
struct Detail: Codable {
let title, date, day: String
let color: Color
}
enum Color: String, Codable {
case b297Fe = "#B297FE"
case e73838 = "#E73838"
case the0D8464 = "#0D8464"
}
I can get to the "Holiday" object, print it, display my tableViewCells with a colour for the "holidayCount". What I want to do is, without using the normal json parsing and making my own arrays and dicts, to access the "Detail" for each "holidays".
tl;dr - I need to know how to access Detail for the holidays element
Thanks!!
Your data's coming back with an array of HolidayElements and each HolidayElement has an array of Details.
So for each HolidayElement, you'd want to get access to the details array. You'd do that like so:
let jsonResponse = try JSONDecoder().decode(Holiday.self, from: responseData)
print(jsonResponse.holidays[0].details)
Here's a repo to play around with.
Additionally, your coding keys are just converting from snake_case, so you don't really need them for that endpoint. Instead, you can just tell the decoder to convertFromSnakeCase
You can ditch the coding keys in this case and just decode as follows:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let jsonResponse = try decoder.decode(Holiday.self, from: responseData)
print(jsonResponse.holidays[0].details)
Hello I have a json the returns me some parameters as variables.
It has Parameter1, Parameter2, Parameter3 etc..
I don't know how many parameters will it give me. It's not a list it's just different variables in the json.
Which is the best way to map a json like that? I use Object Mapper
For Example:
First Time the json is
{
"MyObject": {
"Parameter1": "p1",
"Parameter2": "p2",
"Parameter3": "p3",
"Parameter4": "p4"
}
}
And a second time the json is
{
"MyObject": {
"Parameter1": "p1",
"Parameter2": "p2",
"Parameter3": "p3"
}
}
You can try this.
let keyvalue = parentDict.value(forKey: "MyObject") as! NSDictionary
var lastValue = Keyvalue.allValues
var lastKey = Keyvalue.allKeys
for Keyname in Keyvalue.allKeys
{
print("Keyname %#",Keyname)
print("Value %#",Keyvalue.value(forKey:Keyname))
}
the first step to parse any JSON to make it reusable is to create your Model class or struct accordingly.
Create a class called MyObject as same as your json dictionary
Create a let/var property parameters: [String]?. It's optional as API isn't reliable and maybe wont send anything at all.
See the example below how I parse the json object below.
class MyObject {
let parameters: [String]?
// it's failable because maybe json different at runtime & couldn't parse
init?(json: [String:AnyObject]) {
var key = "Parameter"
var parms = [String]()
for i in 0..<json.count {
guard let item = json["\(key)\(i+1)"] as? String else { continue }
params.append(item)
}
self.parameters = params
}
}
Now you can access the parameters array with index.
Well this could be refactored and you can get the idea how you will handle this with that library.
I have a custom class
public class Balance: NSObject {
var details: String
var date: Date
var amount: Double
}
I have tried as a struct and as a class both fail
I have an array of balances list: [Balance]
Now I'm need to convert this array into a JSON String
something like
[ {details = "text"; date = "2016-11-20"; amount = 0;} ,
{details = "text2"; date = "2016-11-25"; amount= 10;} ]
I also need to be able to convert the String back into the array.
But I can't even get the array to JSON string to work
var resStr = ""
var list: [Balance]
do {
let data = try JSONSerialization.data(withJSONObject: list, options: .prettyPrinted)
resStr = String.init(data: data, encoding: .utf8) ?? ""
} catch { fatalError("Error BALANCE HISTORY ") }
Fails with
'Invalid type in JSON write (Balance)'
Please advise
Thanks in advance
What about defining your class like this:
public class Balance: NSObject {
var details: String
var date: Date
var amount: Double
func toJSONString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
return "{details = \(self.details); date = \(dateFormatter.string(from: self.date)); amount = \(self.amount);}"
}
}
Then you can create the full JSON string like this:
var fullJSONArray = [String]()
for balance in list {
fullJSONArray.append(balance.toJSONString)
}
let fullJSONString = fullJSONArray.description
I hope this helps you out! Good luck and let me know if you have any further questions or issues!
You can only store a quite small list of data types to JSON.
#CoolPenguin offers one solution - a custom method that will convert your object to a JSON string.
I would advise against building JSON strings yourself.
Instead, I would suggest creating a toDictionary() method for your class, and an init(dictionary:) method to create a Balance object from a dictionary.
You can convert the dates to TimeIntervals since 1970 (the UNIX "epoch date".) That seems easier than converting them to date strings.
You can then map your array of Balance objects to an array of dictionaries, and convert that to JSON using normal JSONSerialization.
let mappedArray = balanceArray.map{$0.toDictionary()}
And then easily convert your array of dictionaries to JSON.
Since others have pointed out that you can achieve the desired effect by first converting your object to a Dictionary, I will provide a method to achieve what is required by using JSONEncoder instead.
If you maintain the values you need to represent in the JSON encoded string (for example the date as a formatted String), the only required bits are,
Make your type conform to Codable
Create a CodingKeys enum that represent the JSON keys for your type's properties.
Please see below for an example that applies to your object.
public class Balance: NSObject, Codable {
var details: String
// Used to represent formatted date.
var dateString: String
var date: Date = Date(){
didSet{
updateDateString()
}
}
var amount: Double
enum CodingKeys: String, CodingKey{
case details, dateString, amount
}
init(_ d: String, amt: Double){
details = d
dateString = ""
date = Date()
amount = amt
super.init()
updateDateString()
}
private func updateDateString(){
let df = DateFormatter()
df.locale = Locale(identifier: "en_US_POSIX")
df.dateFormat = "yyyy-MM-dd"
dateString = df.string(from: date)
}
}
var arr: [Balance] = [
Balance("Shared Balance", amt: 100),
Balance("Coupon Balance", amt: 120),
Balance("Account Balance", amt: 150)
]
do{
let data = try JSONEncoder().encode(arr)
// Convert to JSON Encoded String
let str = String(data: data, encoding: .utf8)!
print(str)
// Convert back from JSON Encoded String
let raw = try JSONDecoder().decode([Balance].self, from: str.data(using: .utf8)!)
print(raw)
}
catch(let err){
print(err)
}
The additional updateDateString() boiler plate code is to produce the String with the date format, "yyyy-MM-dd".
The above code can product a JSON encoded version of your Balance array as follows.
[{"details":"Shared Balance","amount":100,"dateString":"2021-01-14"},
{"details":"Coupon Balance","amount":120,"dateString":"2021-01-14"},
{"details":"Account Balance","amount":150,"dateString":"2021-01-14"}]
The key takeaway is to use JSONEncoder instead of JSONSerialization.
Hope I helped!
Well, I believe first you need to convert your object to some form of dictionary.
Let me show you an example:
class Balance: NSObject {
var details: String
var date: Date
var amount: Double
func asDictionary() -> [String: AnyObject] {
return ["details": details as AnyObject, "date": "\(date)" as AnyObject, "amount": amount as AnyObject]
}
}
You use the method asDictionary to convert your objects to a dictionary so that you can serialize it into JSON.
Suppose you have a list of Balance objects.
You need to first convert each of those objects to dictionary using the method above, and then try to serialize the objects to JSON. Note that the list is now a list of [String: AnyObject] dictionaries, and not a list of Balance objects.
var resStr = ""
var list: [[String: AnyObject]] = [balance1.asDictionary(), balance2.asDictionary()]
do {
let data = try JSONSerialization.data(withJSONObject: list, options: .prettyPrinted)
resStr = String.init(data: data, encoding: .utf8) ?? ""
} catch { fatalError("Error BALANCE HISTORY ") }
For certain types, like the date field, you need to find some way to convert it to String, as the JSONSerializer is very picky. In this case I just used String interpolation, but the format may not be what you want it to be like.
If you want to convert back to a Balance object from JSON, first you need to get JSON into a dictionary, and then check for each field if it exists, and if so, construct your Balance object.
Supposing you have converted your JSON data into a dictionary in the variable named dict, you could do something like the following:
// supposing you have a single object in dict
var balance: Balance
balance.details = dict["details"]
balance.amount = dict["amount"]
balance.date = parseDate(dict["date"])
Supposing you have a function parseDate to parse the date from String into a Date object.
You can take a look here for converting String date into an object: Use SwiftyJSON to deserialize NSDate