Mapping Json to object in swift 3.0 - ios

I get a response from the server, lets say in the following format:
{
//Array of models
"A_Key" : [
{
"B_data" : ""
}
]
//dictionary
"A_Key_2" : {
"C_data" : ""
}
"A_Key_3" : "0"
"A_Key_4" : 0
}
Now I'm trying to create a model for the above response (I don't want to use any pod or library).
So what I do is:
class A{
let A_Key : Array<B>!
let A_Key_2 : C!
let A_Key_3 : String!
let A_Key_4 : Int!
init(withDictionary dict:Dictionary<String, Any>){
self.A_Key = A.getArrayOfModel(fromArrayDictionary:dict["A_Key"] as! Array<Dictionary<String, Any>>)
self.A_Key_2 = C(withDictionary : dict["A_Key_2"] as! Dictionary<String, Any>)
self.A_Key_3 = dict["A_Key_3"] as? String ?? ""
self.A_Key_4 = dict["A_Key_4"] as? Int ?? 0
}
class func getArrayOfModel(fromArrayDictionary array:[Dictionary<String, Any>]){
var tempArray = [B]()
for dict in array{
tempArray.append(B(withDictionary: dict))
}
return tempArray
}
}
Assume there are classes B and C with appropriate init methods. Something similar to this compiles properly and all the iterations occur without any crash, so i'm assuming that this will work.
Now my query is, is this the best way to initialize the object in swift with a dictionary? As I'm concerned about a scenario in which the API responds with a nil value for any of "A_Key" (It will cause a crash i believe)?
OR
Is the following the proper way to map a json to an object in swift?
If it is, isn't it a bit tedious to check every value for nil? As the number of checks will keep increasing with having optional init methods for all nested models?
init(withDictionary dict:Dictionary<String, Any>){
if let a_key_3 = dict["A_Key_3"] as? String{
self.A_Key_3 = a_key_3
}
}

Related

How to implement model class for multiple values in swift 3?

Here I am having value in JSON in which for some of multiple key value pairs it returning string and for some it is returning array here in custom attributes array in first dictionary in that value key value pair the data present is different and in the second dictionary value key value pair is different here then how to implement the model class for inside array for different key values ?
struct MediaGallery {
let id : Int
let mediaType : String
let label : Any
let position : Int
let disabled : Any
let file : String
init(dict : [String:Any]) {
self.id = (dict["id"] as? Int)!
self.mediaType = (dict["media_type"] as? String)!
self.label = dict["label"]!
self.position = (dict["position"] as? Int)!
self.disabled = dict["disabled"]!
self.file = (dict["file"] as? String)!
}
}
struct AttributeList {
let label : String
let value : String
let code : String
init(dict : [String:Any]){
self.label = (dict["label"])! as! String
self.value = (dict["value"])! as! String
self.code = (dict["code"])! as! String
}
}
struct DetailsListAttribute {
let attributeCode : String
let value : Any
init?(dict : [String:Any]) {
self.attributeCode = dict["attribute_code"] as! String
print(self.attributeCode)
if let values = dict["value"] as? String {
self.value = values
}
else {
if let arr = dict["value"] as? [[String:Any]]{
var filterArr = [AttributeList]()
for obj in arr {
filterArr.append(AttributeList(dict: obj))
}
self.value = filterArr
} else {
self.value = [AttributeList]()
}
}
}
}
I would suggest please save some time by using this great GIT Library ObjectMapper . it will help you to model your object and convert your model objects (classes and structs) to JSON and vice versa.
I've tried multiple JSON-mapping frameworks that were mentioned in Tj3n comment. They all have pros and cons. Apple suggests you to follow the recommendation given here. Also you should check Codable protocol (swift 4 is required).
Ok I don't have the whole JSON, and it doesn't seem clear to me.
But here is how you can parse and create your model Class easily in Swift with the Codable protocol.
You can read more about it and/or some examples, tutorials : Ultimate Guide.
Briefly, what is the Codable protocol ?
You don't need third party library anymore in order to parse and set the json data to your model class.
You juste have to create your class like the JSON is represented. And according to the key-name, it will create the class, properties and everything for you.
Here is an example with your JSON, I don't know if I understood your JSON formatting, but you got the trick :
struct Response: Codable {
let ca: [CustomAttribute]?
enum CodingKeys: String, CodingKey {
case ca = "custom_attributes"
}
}
struct CustomAttribute: Codable {
let code: String?
let value: [Value]?
struct Value: Codable {
let label: String?
let value: String?
let code: String?
let avg: String? // I don't know how your value array is composed
let count: Int? // I don't know how your value array is composed
}
enum CodingKeys: String, CodingKey {
case code = "attribute_code"
case avg = "avg_rating_percent"
}
}
For me, it looks like something like that.
I don't see the whole JSON, but imagine you have the whole JSON as the Response Struct, it contains several objects, like the CustomAttribute Array for example.
Then you can define the CustomAttribute structure, and add as many properties as the JSON has.
Anyway, you can call it this way :
When you have the response from your API call, you can go :
if let data = response.data {
let decoder = JSONDecoder()
let response = try! decoder.decode(Response.self, from: data)
print("Only printing the Custom Attribute : \(response.ca!)")
}
I decode the whole json data as an Object Response (like my Struct).
And I pass to my response callback, or
this might be late but I think this will helps others
The model class which are varies for frameworks like SwiftyJSON, simple swift class, Gloss or swift codable (Swift 4). you can easily generate model class online with your customization jsoncafe.com

Gives all of them optional value

Hello i have variables but gives all of them Optional(). How can i resolve them my codes under below.
Json append codes for koltuklar koltuklaridler array under below you can see
for name in json as! [AnyObject] {
let SeatName = name["SeatName"]
let SeatDesignId = name["SeatDesignId"]
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
let TicketTypeId = blog["TicketTypeId"]
let TicketTypeName = blog["TicketTypeName"]
let Amount = blog["Amount"]
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
Under below you can see tableview inside codes ( That codes doing open koltuklar index path item after search id inside koltuklaridler and when found take some varibles from it )
var koltuklar = [""]
var koltuklaridler = [""]
if let myStrings:String! = koltuklar[indexPath.row]{
print("\(myStrings!)")
let myStringArrf = myStrings.componentsSeparatedByString("*")
print("\(myStringArrf)")
if let koltukisims:String! = String(myStringArrf[0]) {
cell.koltukName.text = koltukisims
}
print(" STR - \(myStringArrf[1] as String!)")
if let index = koltuklaridler.indexOf(myStringArrf[1] as String!) {
let myStringdetaysecilen = koltuklaridler[index]
print("myStringdetaysecilen \(myStringdetaysecilen)")
}
Also my json file
[
{
"SeatDesignId": 16484,
"SeatName": "A6",
"SaloonId": 148,
"SeatDetail": [
{
"TicketTypeId": 1,
"TicketTypeName": "Okay",
"Amount": 13
}
]
},
Output
Optional("A17")*Optional(16254)*
["Optional(\"A17\")", "Optional(16254)", ""]
STR - Optional(16254)
All variables output Optional i try everything but doesn't fix.
As mentioned in my comments, whenever you use String Interpolation "\(...)" make sure that all optional strings are unwrapped. Values read from dictionaries are always optional.
This code unwraps all optional strings
for name in json as! [[String:AnyObject]] {
guard let SeatName = name["SeatName"] as? String,
SeatDesignId = name["SeatDesignId"] as? Int else {
continue
}
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
if let TicketTypeId = blog["TicketTypeId"] as? Int,
TicketTypeName = blog["TicketTypeName"] as? String,
Amount = blog["Amount"] as? Int {
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
}
Edit: I updated the casting to the actual types according to the JSON
Now declare both arrays as empty string arrays.
var koltuklar = [String]()
var koltuklaridler = [String]()
and remove the optional binding in the first line
let myStrings = koltuklar[indexPath.row]
print("\(myStrings)")
...
By the way: Your way to "serialize" the strings with asterisks and deserialize it in the table view is very, very clumsy and inefficient. Use a custom class or struct for the data records.
Your problem is that you are creating a string from values from dict without a if let statement so it returns an optional value:
for name in json as! [AnyObject] {
if let SeatName = name["SeatName"],
let SeatDesignId = name["SeatDesignId"] {
self.koltuklar.append("\(SeatName)*\(SeatDesignId)*")
}
if let blogs = json["SeatDetail"] as? [[String: AnyObject]] {
for blog in blogs {
if let TicketTypeId = blog["TicketTypeId"],
let TicketTypeName = blog["TicketTypeName"],
let Amount = blog["Amount"] {
self.koltuklaridler.append("\(SeatDesignId)*\(TicketTypeId)*\(TicketTypeName)*\(Amount)*")
}
}
}
There is a two way of operate optional.
unwrapped using "!" but in this chances of crash if value is nil.
unwrapped using term call "optional binding" using "if let" condition.
if let var = "assigned your optional variable"{
print(var)
}
You will get your variable without optional.

Extract value from dictionary of annoying format

I apologise for the title of this question. I have no idea what else to call it.
So... When calling the following:
let testData: [NSObject : AnyObject] = getTestData()
print(testData)
I get this output:
[data: {"TypeId":7,"DataList":null,"TypeName":"This is a test"}]
How would I be able to access the value 7 for the key "TypeId"?
EDIT:
Please note that it's holding { } brackets, not only [ ], thus a cast to NSDictionary is not possible as far as I have tried.
Kind regards,
Anders
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
The way most people do it is to parse annoying JSON data as custom objects. That should be done as soon as you get the JSON. Ideally, data as JSON should not be used outside your communication code, example:
First, let's define a class to hold your server data:
class MyServerObject {
let typeId: Int
let typeName: String
let dataList: [AnyObject]?
init(dictionary: Dictionary<String, AnyObject>) {
let dataDictionary = dictionary["data"] as! Dictionary<String, AnyObject>
self.typeId = dataDictionary["TypeId"] as! Int
self.typeName = dataDictionary["TypeName"] as! String
self.dataList = dataDictionary["DataList"] as? [AnyObject]
}
}
Note that init method is already parsing the JSON. This doesn't have to be done in init, you could also create a static parse method that will return a new instance.
Usage:
// demo data
let jsonString = "{\"data\": {\"TypeId\":7,\"DataList\":null,\"TypeName\":\"This is a test\"}}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// parsing
let myServerObject = MyServerObject(dictionary: json as! Dictionary<String, AnyObject>)
// now we can simply read data as properties
print(myServerObject.typeId)
print(myServerObject.typeName)
One of the good thing about this solution is that we can check the JSON format and all the properties are parsed with the correct types.
Parsing can be hierarchical, for example, if your dataList contains complex objects, let's call them DataListItem, your parsing method can parse each item separately and put them into a [DataListItem], e.g.
if let dataListJSON = dataDictionary["DataList"] as? [Dictionary<String, AnyObject>] {
self.dataList = dataListJSON.map({ DataListItem($0) })
}
Also note that when parsing as! will crash the app when the format is invalid. as? will return nil if the types don't match. as? is very useful for types that can be nil because they are parsed as NSNull instances.
taking in account your data ...
print(testData)
/*
[data: {
DataList = null;
TypeId = 7;
TypeName = "This is a test";
}]
*/
// DataList type should be declared somewhere
class DataList {}
// parse data or set default value, if 'key' doesn't exist
if let data = testData["data"] as? [String:AnyObject] {
let dataList = data["DataList"] as? DataList // nil
let typeId = data["TypeId"] as? Int ?? 0 // 7
let typeName = data["TypeName"] as? String ?? "" // This is test
}

Swift 2- "Type 'String' has no member "PopulateArray"

I have the following class which populates all the "Breakfast" entries from a JSON file. Note that in my JSON file, "ingredients" is an array and "instructions" could be an array of arrays.
In the "Populate" function below, Swift is reporting 2 errors saying, "Type 'String' has no member "PopulateArray". "Type 'AnyObject' has no member "PopulateArray".
How do I fix this?
Here's the Swift Source.
import Foundation
class Breakfast
{
var id:String = ""
var name:String = ""
var image:String = ""
var servings:String = ""
var ingredients:[String] = []
var instructions:[AnyObject] = []
func Populate(dictionary:NSDictionary) {
id = dictionary["id"] as! String
name = dictionary["name"] as! String
image = dictionary["image"] as! String
servings = dictionary["servings"] as! String
ingredients = String.PopulateArray(dictionary["ingredients"] as! [NSArray])
instructions = AnyObject.PopulateArray(dictionary["instructions"] as! [NSArray])
}
class func PopulateArray(array:NSArray) -> [Breakfast]
{
var result:[Breakfast] = []
for item in array
{
let newItem = Breakfast()
newItem.Populate(item as! NSDictionary)
result.append(newItem)
}
return result
}
}
The JSON Source can be found here: JSON Source
Neither String nor AnyObject seem to declare a method PopulateArray (unless it's in class extension you haven't included)
If your JSON has contains an array of strings for the "ingredients" key, then it will be an NSArray of NSString, which can be trivially converted to [String]:
ingredients = dictionary["ingredients"] as! [String]
Likewise, if your JSON always contains an array of AnyObject ([AnyObject]) you can populate it with:
instructions = dictionary["instructions"] as! [AnyObject]
Typically when working with JSON, you'll want to use NSJSONSerialization to convert top level JSON data into their corresponding Swift types, here's a quick example of getting data, running it through a serializer and then accessing the JSON data by casting it into known Swift data types:
if let someJsonData = someOptionalData {
if let jsonTopLevelDictionary = try? NSJSONSerialization.JSONObjectWithData(someJsonData, readingOptions: NSJSONReadingOptions.MutableContainers) as? [String:AnyObject] {
if let stringsArr = jsonTopLevelDictionary!["keyWithArrayOfStrings"] as? [String] {
for string in stringsArr {
// do stuff
}
}
}
}
There are a few different ways to skin this cat, you can even check out something like SwiftyJSON which helps parse JSON data into their native swift types.

What is the best way to parse JSON files and create Model objects and then map them to the Core Data database?

I have the following instance method in my class, where "jsonObj" is a Dictionary:
func getSurvey(countryId: String, languageId: String, surveyId: String, userId: String, completionHandler: ((ICSurvey!, NSError!) -> Void)?)
{
ICWebServicesManager.downloadSurvey("", languageId: "", surveyId: "", userId: "") {
(jsonObj, error) -> Void in
if completionHandler != nil
{
if error != nil
{
completionHandler!(nil, error)
}
else
{
let surveyJSONObject = jsonObj
let survey = ICSurvey()
if let surveyIdObj = surveyJSONObject["id"] as? Dictionary<String, String>
{
self.dateFormatter!.dateFormat = "y-M-d'T'HH:mm:ssZZZZ"
survey.id = ICSurveyId(
surveyId : surveyIdObj["survey_id"]!,
countryId : surveyIdObj["country_id"]!,
languageId : surveyIdObj["language_id"]!
)
survey.uri = surveyJSONObject["uri"] as? String
survey.title = surveyJSONObject["title"] as? String
survey.startDate = self.dateFormatter!.dateFromString(surveyJSONObject["start_date"] as! String)
survey.endDate = self.dateFormatter!.dateFromString(surveyJSONObject["end_date"] as! String)
survey.type = surveyJSONObject["type"] as? String
survey.questionGroups = Array()
if let questionGroupsJSONObjects = surveyJSONObject["question_groups"] as? Array<Dictionary<String, AnyObject>>
{
for questionGroupObj in questionGroupsJSONObjects
{
let questionGroup = ICQuestionGroup()
questionGroup.questions = Array()
questionGroup.text = questionGroupObj["text"] as? String
if let questionsArrayObj = questionGroupObj["questions"] as? Array<Dictionary<String, AnyObject>>
{
for questionObj in questionsArrayObj
{
var question = ICQuestion()
if let questionIdObj = questionObj["id"] as? Dictionary<String, String>
{
question.id = ICQuestionId(
questionId : questionIdObj["question_id"]!,
languageId : questionIdObj["language_id"]!
)
question.type = questionObj["type"] as? String
var requiredString = questionObj["required"]
as? String
question.required = (requiredString == "True" ? true : false)
question.setValue(questionObj["text"] as? String, forKey: "text")
if let questionResponseObj = questionObj["response"] as? Dictionary<String, AnyObject>
{
question.response = ICResponse(
type : questionResponseObj["type"] as! String,
value : questionResponseObj["value"] as! Int,
clientTimestamp : self.dateFormatter!.dateFromString(questionResponseObj["client_timestamp"] as! String)!
)
}
if let questionResponseObj = questionObj["options"] as? Array<Dictionary<String, AnyObject>>
{
question.options = questionResponseObj
}
}
questionGroup.questions!.append(question)
}
}
survey.questionGroups!.append(questionGroup)
}
}
}
self.currentSurvey = survey
completionHandler!(self.currentSurvey, error)
}
}
}
}
After doing some code review with my mentor, he told me everything is pretty good, except I need to "abstract out hard wired properties". My understanding is that he does not want to have code that looks like this:
survey.startDate = self.dateFormatter!.dateFromString(surveyJSONObject["start_date"] as! String)
, because "start_date" for instance is hard-coded.
Instead, I should find what objects the JSON file represent and map that data correspondingly. While I agree to that to some extent, as the app code will not need many changes if it do it that way, it seem to be an overhead for me because I need to map everything to Core Data, and if a property changes, many things may change or crash.
My question is: What is the best way to parse JSON files and create Model objects and then map them to the Core Data database?
How do we have to "abstract out hard wired properties"?
If anyone has more experience with Web Services integration, please give me some advice.
Thanks in advance!
I was using SwiftyJSON on previous projects but now I've converted everything to Swift 1.2 (if let,) syntax and I honestly like it more since it feels like I have more flexibility and it alleviates 3rd party framework dependency.
As far as your "hard-coded" values, simply utilize optionals for testing. For instance, the following line requires a value to be present. Test for that value prior to unwrapping it. His fear is that if that object doesn't exist for whatever reason your program will crash. (This isn't tested but you should get the idea)
surveyId : surveyIdObj["survey_id"]!,
if let surveyId = surveyIdObj["survey_id"] as? String,
let countryId = surveyIdObj["country_id"] as? String,
let languageId = surveyIdObj["language_id"] as? String {
survey.id = ICSurveyId(
// You could also test here for each and add the values to your survey object
surveyId : surveyIdObj["survey_id"]!,
countryId : surveyIdObj["country_id"]!,
languageId : surveyIdObj["language_id"]!
)
}
Personally I prefer just creating a new NSManagedObject and assigning the values directly.
var survey = NSEntityDescription.insertNewObjectForEntityForName("Survey", inManagedObjectContext: self.appDelegate.managedObjectContext!) as! Survey
// Then just assign the values to the object using dot notation
survey.surveyId = surveyIdObj["survey_id"]!
// and so on
// Then save the context
self.managedObjectContext?.save(nil)

Resources