JSONSerialization of Custom Object Array - ios

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

Related

Retrieving Values from array of objects

I have this Object Array mention below . Can someone please guide me how can i access various values inside the Object Array.
CellInfo(date: "Sep 2018",
audioFileInfos: [In.PtV.AudioFileInfo(urlString: "https://GeorgeB.m4a",
text: "9/11")
])
i want to Access Date ,urlString and text
struct AudioFileInfo {
let urlString: String
let text: String
init(dict: [String: String]) {
urlString = dict["AudioFileURL"] ?? ""
text = dict["Title"] ?? ""
}
}
struct CellInfo {
let date: String
let audioFileInfos: [AudioFileInfo]
}
Consider the following code.
let cellInfo = CellInfo(date: "Sep 2018",
audioFileInfos: [In.PtV.AudioFileInfo(urlString: "https://GeorgeB.m4a",
text: "9/11")
])
print(cellInfo.date) // prints date
print(cellInfo.audioFileInfos[0].urlString) // prints urlString
print(cellInfo.audioFileInfos[0].text) // prints urlString
The things is happening here is as follows
You create CellInfo struct with date and audioFileInfos.
while providing audioFileInfos you create another struct using the same way as #1
You pass the AudioFileInfo inside of array.
So incase of accessing the date you can directly access the date property using dot . operator.
For accessing the AudioFileInfo struct object, same way just with indexing added.
As audioFileInfos is an array, safe & complete way to access it's values is to traverse the array, meanwhile accessing the array elements.
for audioFileInfo in cellInfo.audioFileInfos {
print(audioFileInfo.urlString)
print(audioFileInfo.text)
}

How to use nested json with structs in swift

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)

Ios how to post multiple Custom objects using Alamofire

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.

How to convert string to array in Swift 3

I am inserting an Array into my database as a String and after fetching it I want it to convert it again to Array. So that I can fetch my values again and I can do next operation.
Here below is my array inserting into database(TestQuestion) as a String:
let testQuestionModel : TestQuestion = NSEntityDescription.insertNewObject(forEntityName: "TestQuestion", into: AppDelegate.getContext()) as! TestQuestion
testQuestionModel.optionsArray = "\(question["options"] as! NSArray)"
Example: String Array I am getting from Database
(\n \"Rahul Abhyankar\",\n \"Pinkesh Shah\",\n \"Ramanan
Ganesan\",\n \"Dr. Marya Wani\",\n \"\",\n \"\"\n)".
Here is 4 options you can see this is my string after fetching from Database.
1) Rahul Abhyankar.
2) Pinkesh Shah.
3) Ramanan Ganesan.
4) Dr. Marya Wani.
Now how can I convert it into array?
I tried some methods.
let arr = NSArray(object: quetion.optionsArray!).
But I am getting only one object. How can I get my array values same as previous from this string array?
I don't know about the actual type of the "option" in your code, so I set up a fake Elem struct to represent it. The remaining logic is independent of the type as long as you provide a conversion logic to and from String.
struct Elem {
// let's say this is your element type in your array
let foo: Int;
}
extension Elem: CustomStringConvertible {
var description: String {
// provide a logic to convert your element to string
return "\(foo)";
}
}
let arrayToSave = [
Elem(foo: 1),
Elem(foo: 2),
Elem(foo: 3)
]
extension Elem {
init(string: String) {
// provide a function to construct your element type from a string
self.init(foo: Int(string)!)
}
}
let stringToSave = arrayToSave.map { $0.description }.joined(separator: "|")
// save this string
// at some point retrieve it from database, which hopefully same as the saved one
let retrivedString = stringToSave;
let retrivedArray = retrivedString.split(separator: "|").map { Elem(string: String($0)) }
print(retrivedArray) // [1, 2, 3]
Here below is my array inserting into database (TestQuestion) as a
String :
let testQuestionModel : TestQuestion = NSEntityDescription.insertNewObject(forEntityName: "TestQuestion", into: AppDelegate.getContext()) as! TestQuestion
testQuestionModel.optionsArray = "\(question["options"] as! NSArray)"
No, and No.
You are using -description method of an array to save it. Clearly no.
What's wrong? Apple can't affirm that in next OS release, it won't add an extra character. In some more complex cases, it's added <NSArray <0x address> or stuff similar like that.
Suggestion 1:
Modify your entity to have an ARRAY (or usually a Set) of String.
Learn about Core-Data relationship (but that's clearly a DataBase basic knownledge). A relationship one to many should be the thing to do.You could even keep in memory what were the choices, by adding for creating the entity Options, with a String property name (name of the option), another one boolean isChecked, etc.
Suggestion 2:
If you have a limited number of options (like says one to 5), add 5 options string to your entity, and iterate to set them
testQuestionModel.option1 = question["option"][0]
testQuestionModel.option2 = question["option"][1] (if it's of course not out of range for the array)
...
Suggestion 3:
Not really recommended (in my opinion it's missing the whole advantage of the database, especially fetch and predicates, on previous sample you could fetched easily which options were checked), but if you still want to save them as a String, save them as JSON (ie. stringified).
In pseudo code (I'm not sure about the exact syntax, there are no fail safe like try/catch, optional/wrapping):
let options = questions["options"] as [String]
let jsonData = JSONSerialization.data(withJSONObject: (question["options"], options:[])
let jsonString = String.init(data:jsonData encoding:.utf8)
To retrieve them:
let options = JSONSerialization.jsonObject(with data: myJSONString.data(encoding:.utf8), options:[]) as [String]
done using Library SwiftyJSON.
if let dataFromString = yourString?.data(using: String.Encoding.utf8, allowLossyConversion: false) {
do{
let json = try JSON(data: dataFromString)
print(json)
let arrayValue = json.rawValue as! NSArray
print(arrayValue)
} catch{
print(error.localizedDescription)
}
}
Source: https://github.com/SwiftyJSON/SwiftyJSON

How to fetch the key from JSON in Swift?

I am working on JSON parsing in Swift.
var results = [String:[AnyObject]]()
The above results is having the data as shown below,
"fruit" = (
"apple",
"orange"
);
Here, data is appended dynamically during runtime. All I need is to get the keys and display them in table view as header.
How to get thekey from results in swift?
NSJSONSerialization code example...
var results = [String:[AnyObject]]()
let jsonResult = try NSJSONSerialization.JSONObjectWithData(results, options:NSJSONReadingOptions.MutableContainers);
for (key, value) in jsonResult {
print("key \(key) value2 \(value)")
}
You can convert JSON to dictionary as mentioned in the above link proposed by Birendra. Then suppose jsonDict is your json parsed dictionary. Then you can get collection of all keys using jsonDict.keys.
You need to use NSJSONSerialization class to convert in json format (eg. to convert in dictionary) and then get all keys from it.
I have used,
var results = [String:Array<DataModel>]
where,
class DataModel {
var name: String?
}
and to fetch the keys and value,
for i in 0...(results.length-1){
// To print the results keys
print(results.keys[results.startIndex.advancedBy(i)])
// To get value from that key
let valueFromKeyCount = (results[results.keys[results.startIndex.advancedBy(i)]] as Array<DataModel>).count
for j in 0...(valueFromKeyCount-1) {
let dm = results[results.keys[results.startIndex.advancedBy(i)]][j] as DataModel
print(dm.name)
}
}
Tested with Swift 4.2 to get first key or list of keys:
This "one-liner" will return the first key, or only key if there is only one.
let firstKey: String = (
try! JSONSerialization.jsonObject(
with: data,
options: .mutableContainers
) as! [String: Any]).first!.key
This one will get a list of all the keys as and array of Strings.
let keys: [String] = [String] ((
try! JSONSerialization.jsonObject(
with: data,
options: .mutableContainers
) as! [String: Any]).keys)
Both of the above examples work with a JSON object as follows
let data = """
{
"fruit" : ["apple","orange"],
"furnature" : ["bench","chair"]
}
""".data(using: .utf8)!

Resources