XCTest for test model objects - ios

Bellow i have mentioned my model object.
class HVConnection: NSObject {
//private var _data: NSMutableDictionary
private var _data: NSMutableDictionary
// MARK:- Init
init(data: NSDictionary)
{
_data = NSMutableDictionary(dictionary: data)
}
// MARK:- Properties
var last_name: String? {
if let lastNameObject = _data.objectForKey("last_name") {
return (idObject as! String)
} else {
return nil
}
}
}
then i implemented a test case to check variables.
Bellow i have mentioned the test case.
func testNetworkModelObject() {
let connectionObject = ["network": ["first_name": "Dimuth", "last_name": "Lasantha", "business_email": "example#gmail.com", "currency": "USD", "language": "en-us", "category": "individual"]]
let modelObject = HVConnection(data: connectionObject)
XCTAssertEqual(modelObject.last_name, "Lasantha")
}
bellow i have mentioned the error
XCTAssertEqual failed: ("nil") is not equal to ("Optional("Lasantha")")
please help me to fix the issue

Your problem is that you cannot use
_data.objectForKey("last_name")
in your HVConnection because it is nested within another dictionary key called network.
So instead use:
// MARK:- Properties
var last_name: String? {
if let lastNameObject = _data.objectForKey("network")?.objectForKey("last_name") {
return (lastNameObject as! String)
} else {
return nil
}
}
This is to demonstrate the dictionaries in use:
["network": // your dictionary is a dictionary within a dictionary
["first_name": "Dimuth",
"last_name": "Lasantha",
"business_email": "example#gmail.com",
"currency": "USD",
"language": "en-us",
"category": "individual"]
]
In order to get your last_name, you have to get the dictionary for key network to get the actual dictionary, and once you have that, you can check that dictionary you get back from the network key for the key last_name.

Related

How to parse JSON Array Objects with SwiftyJSON?

I have a JSON file which contains list of array objects. I'm unable to parse that json file
I have tried this code but it didn't work
if let tempResult = json[0]["sno"].string{
print("Temp Result is \(tempResult)")
}
else {
print(json[0]["sno"].error!)
print("Temp Result didn't worked")
}
Here is my JSON File
[
{
"sno": "21",
"title": "title 1",
"tableid": "table 1"
},
{
"sno": "19",
"title": "title 222",
"tableid": "table 222"
},
{
"sno": "3",
"title": "title 333",
"tableid": "table 333"
}
]
Actually it would be better to define a struct for object in Array.
public struct Item {
// MARK: Declaration for string constants to be used to decode and also serialize.
private struct SerializationKeys {
static let sno = "sno"
static let title = "title"
static let tableid = "tableid"
}
// MARK: Properties
public var sno: String?
public var title: String?
public var tableid: String?
// MARK: SwiftyJSON Initializers
/// Initiates the instance based on the object.
///
/// - parameter object: The object of either Dictionary or Array kind that was passed.
/// - returns: An initialized instance of the class.
public init(object: Any) {
self.init(json: JSON(object))
}
/// Initiates the instance based on the JSON that was passed.
///
/// - parameter json: JSON object from SwiftyJSON.
public init(json: JSON) {
sno = json[SerializationKeys.sno].string
title = json[SerializationKeys.title].string
tableid = json[SerializationKeys.tableid].string
}
}
And you need to map your array of JSON to Item objects.
var items = [Item]()
if let arrayJSON = json.array
items = arrayJSON.map({return Item(json: $0)})
}
Ditch SwiftyJSON and use Swift's built-in Codable with model objects instead:
typealias Response = [ResponseElement]
struct ResponseElement: Codable {
let sno, title, tableid: String
}
do {
let response = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print(error)
}
where data is the raw JSON data you got from your API.

Nested repeatative loop in swift

I am trying to parse a nested iterative loop in swift
I am getting the response from web service in the following format
{
"categories": [{
"name": "Default Category",
"id": "default_category",
"children": [{
"uuid": "783f491fef5041438fb7a2c3bf6a3650",
"name": "Accessories",
"children": [{
"uuid": "d21b4491ff784a9bae88de279b99fac3",
"name": "All Accessories",
"children": [{
"uuid": "2b1a23c4107844ad8a7afc1b324d0ffd",
"name": "Belts",
"children": [{
"uuid": "2b1a23c4107844ad8a7afc1b324d0ffd",
"name": "Belts",
"children": []
},
{
"uuid": "2b1a23c4107844ad8a7afc1b324d0ffd",
"name": "Belts",
"children": []
}
]
},
{
"uuid": "a1c2a64c36c2461cad3d5f850e4fd0f5",
"name": "Hats",
"children": []
},
{
"uuid": "8f26bc764b8342feaa0cb7f3b96adcae",
"name": "Scarves",
"children": []
},
{
"uuid": "aa1116d1a0254ecea836cc6b32eeb9e0",
"name": "Sunglasses",
"children": []
},
{
"uuid": "9d7033233e8f47eaa69eb1aaf2e98cdd",
"name": "Watches",
"children": []
}
]
}]
}],
"uuid": "6a23415771064e7aaad59f84f8113561"
}]
}
Inside, the categories, there is 'children' key which in turn can contain another children and so on.
I want to continuously loop inside the children key until the children key is empty and insert the last child into database.
Following is the code which i have done
for currentCategory in mainCategories {
// guard against if there are child categories
guard var children = currentCategory.children, children.count > 0 else {
// Save the context
self.coreData.saveStore()
continue
}
for thisChildCategory in children {
if thisChildCategory.children?.count > 0 {
for innerChildCategory in thisChildCategory.children! {
print("innerChildCategory name \(String(describing: innerChildCategory.name))")
}
}
if let child = thisChildCategory.children {
children = child
}
// Create new object
if let currentChildCategory = self.coreData.insertNewObject(CoreDataEntities.BijouCategories.rawValue,
keyValues: ["id" : thisChildCategory.id! as Optional<AnyObject>,
"uuid" : thisChildCategory.uuid as Optional<AnyObject>,
"name" : thisChildCategory.name! as Optional<AnyObject>,
"gender" : thisChildCategory.gender as Optional<AnyObject>!,
"active" : NSNumber(value: false)]) as? BijouCategories {
// Set as parent category
currentChildCategory.parentCategory = parentCategory
// Save the context
self.coreData.saveStore()
}
}
}
But this is not saving all the last child category in database.
Swift 4
You should let Swift 4's codable do the work for you.
You can use the following class as a struct but I find using a class is better if you plan on editing the data.
class Categories: Codable {
var categories: [CategoryItems]
}
class CategoryItems: Codable {
var name: String?
var id: String?
var uuid: String?
var children: [CategoryItems]?
required init(from decoder: Decoder) throws {
var container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decodeIfPresent(String.self, forKey: CodingKeys.name)
id = try container.decodeIfPresent(String.self, forKey: CodingKeys.id)
uuid = try container.decodeIfPresent(String.self, forKey: CodingKeys.uuid)
children = try container.decodeIfPresent([CategoryItems].self, forKey: CodingKeys.children)
if children != nil, children!.count == 0 {
children = nil
}
}
You can see here we add create the root level class "Categories" that has an array of CategoryItems. CategoryItems has all the possible values within it, but each item in the array may or may not have all of the possible values, hence they are optional. The important one is the children which is optional. Then in the required init we only se the optional values if the key value pair is available when decoding. I also set the children to nil if there are zero items, this is optional but helps when doing if statements later.
Then to decode your json using these codable classes you use the following code.
func decode(jsonData data: Data) {
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(Categories.self, from: data)
}
catch let error as NSError {
print("JSON Decode error = ", error)
}
}
If you want to do a quick test to see if you got the deeping children level which I did you can simply run the following on the decoded variable.
for i in decoded.categories.first!.children!.first!.children!.first!.children!.first!.children! {
print(i.name)
print(i.uuid)
}
With more than 2 nested levels a recursive function is recommended. recursive means the function calls itself.
Here is an simple example assuming jsonString is the given JSON in the question.
The function parseCategory passes the children array and the UUID string as parent identifier. The print line is the place to save the object in Core Data and of course you can pass the created Core Data object as parent as well to set the relationship.
func parseCategory(children: [[String:Any]], parent: String) {
for child in children {
print("Save in Core Data", child["name"] as! String, parent)
let descendants = child["children"] as! [[String:Any]]
parseCategory(children:descendants, parent: child["uuid"] as! String)
}
}
let data = Data(jsonString.utf8)
do {
let json = try JSONSerialization.jsonObject(with: data) as! [String:Any]
parseCategory(children: json["categories"] as! [[String:Any]], parent: "")
} catch { print(error)}
The output is
"Save in Core Data Default Category
Save in Core Data Accessories 6a23415771064e7aaad59f84f8113561
Save in Core Data All Accessories 783f491fef5041438fb7a2c3bf6a3650
Save in Core Data Belts d21b4491ff784a9bae88de279b99fac3
Save in Core Data Belts 2b1a23c4107844ad8a7afc1b324d0ffd
Save in Core Data Belts 2b1a23c4107844ad8a7afc1b324d0ffd
Save in Core Data Hats d21b4491ff784a9bae88de279b99fac3
Save in Core Data Scarves d21b4491ff784a9bae88de279b99fac3
Save in Core Data Sunglasses d21b4491ff784a9bae88de279b99fac3
Save in Core Data Watches d21b4491ff784a9bae88de279b99fac3"
Created an model class to hold your nested children in the form of a tree.
class Children {
var uuid: String?
var name: String?
var children: [Children] = [Children(array: [])]
init(array: NSArray) {
let childrenDic = array[0] as! NSDictionary
uuid = childrenDic["uuid"] as? String
name = childrenDic["name"] as? String
children[0] = Children.init(array: childrenDic["children"] as! NSArray)
}
}
Use like
var childrenModel = Children.init(array: yourArray)
I would suggest you to use ObjectMapper instead of unwrapping the json manually.
https://github.com/Hearst-DD/ObjectMapper
then everything should be much cleaner
class Child: Mappable {
var uuid: String?
var name: String?
var childern: [Child]?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
uuid <- map["uuid"]
name <- map["name"]
childern <- map["childern"]
}
}
class Category: Mappable {
var _id: String? //id is the reserved word
var name: String?
var childern: [Child]?
required init?(map: Map) {
}
// Mappable
func mapping(map: Map) {
_id <- map["id"]
name <- map["name"]
childern <- map["childern"]
}
}

Codable object mapping array element to string

I have a (annoying) situation where my back-end returns an object like this:
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
where each property is an array that holds a string as its first element. In my data model struct I could declare each property as an array but that really would be ugly. I would like to have my model as such:
struct User: Codable {
var user: String
var familyName: String
}
But this of course would fail the encoding/decoding as the types don't match. Until now I've used ObjectMapper library which provided a Map object and currentValue property, with that I could declare my properties as String type and in my model init method assig each value through this function:
extension Map {
public func firstFromArray<T>(key: String) -> T? {
if let array = self[key].currentValue as? [T] {
return array.first
}
return self[key].currentValue as? T
}
}
But now that I am converting to Codable approach, I don't know how to do such mapping. Any ideas?
You can override init(from decoder: Decoder):
let json = """
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
"""
struct User: Codable {
var name: String
var familyName: String
init(from decoder: Decoder) throws {
let container:KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
let nameArray = try container.decode([String].self, forKey: .name)
let familyNameArray = try container.decode([String].self, forKey: .familyName)
self.name = nameArray.first!
self.familyName = familyNameArray.first!
}
enum CodingKeys: String, CodingKey {
case name
case familyName
}
}
let data = json.data(using: .utf8)!
let decodedDictionary = try JSONDecoder().decode(Dictionary<String, User>.self, from: data)
print(decodedDictionary) // ["user": __lldb_expr_48.User(name: "John", familyName: "Johnson")]
let encodedData = try JSONEncoder().encode(decodedDictionary["user"]!)
let encodedStr = String(data: encodedData, encoding: .utf8)
print(encodedStr!) // {"name":"John","familyName":"Johnson"}
My tendency would be to adapt your model to the data coming in and create computed properties for use in the application, e.g.
struct User: Codable {
var user: [String]
var familyName: [String]
var userFirstName: String? {
return user.first
}
var userFamilyName: String? {
return familyName.first
}
}
This allows you to easily maintain parody with the data structure coming in without the maintenance cost of overriding the coding/decoding.
If it goes well with your design, you could also have a UI wrapper Type or ViewModel to more clearly differentiate the underlying Model from it's display.

Correctly parsing through a JSON array with multiple objects using Alamofire and SwiftyJSON

I am able to print out the response. However I am unable to loop through the array and initialise it. This is how I am requesting the data:
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default).responseJSON { (response) in
print(response.value!)
guard let data = response.data else { return }
let json = JSON(data: data)
for r in json.arrayValue {
let person = Person(json: r)
}
}
I have added breakpoints but I am unable to understand as to why the loop does not happen?
Update:
response.value! example:
{
"pagination": {
"object_count": 1,
"page_number": 1
},
"attendees": [
{
"id": "818060297",
"quantity": 1,
"profile": {
"first_name": "John",
"last_name": "Doe",
"company": "Apple",
"name": "John Doe",
"email": "john_doe#testmail.com",
"job_title": "CEO"
},
"status": "Attending"
}
]
}
Update 2
Here is my AttendeesProfile class:
class AttendeesProfile {
var name: String?
init(json: JSON) {
self.name = json["name"].stringValue
}
}
Now I am not sure how to get the Attendee class to work properly:
class Attendees {
var attendees = [String]()
var attendeesProfile: AttendeesProfile!
init(json: JSON) {
// Something here
}
}
I suspect your guard statement stopping your code flow before you loop through your array : guard let data = response.data else { return }
With this line, you are saying if data has something then the code flow can continue. If not, please stop it and return. So, you never reach your loop statement.
Are you sure that your response.data has something and is different from nil ? Your print(response.value!) shows something ?
You have to cast your response data. You can not walk on an unknown array
I suggest you to use ObjectMapper library. It can parse your data to your willing model and if received data is nil or is not your wanted model you can find out easily. Do not forget to print response.data to ensure what data exactly is.
https://github.com/Hearst-DD/ObjectMapper
You could implement a couple of structs conforming to Decodable, which among other things would give you the benefit of not relying on SwiftyJSON for your code to work.
Going off from the JSON data that you have provided, consider the following three simple structs:
struct AttendeeArray: Decodable {
let attendees: [Attendee]
}
struct Attendee: Decodable {
let status: String
let profile: AttendeeProfile
}
struct AttendeeProfile: Decodable {
let name: String
let age: Int
}
Each struct simply contains the variables that you have defined in your JSON object.
Using the JSONDecoder you will now be able to decode your JSON data as simple as calling:
do {
let array = try JSONDecoder().decode(AttendeeArray.self, from: data)
// do whatever with your attendees array
} catch {
// handle error if parsing fails
print(error)
}
I created a simple Playground where you can test this by adding the code bolow along with the Decodable structs above:
import Foundation
func decodeAttendees(json: String) -> AttendeeArray? {
guard let data = json.data(using: .utf8) else { return nil }
do {
return try JSONDecoder().decode(AttendeeArray.self, from: data)
} catch {
print("Error: \(error)")
return nil
}
}
let json = """
{
"attendees": [
{
"status": "attending",
"profile": {
"name": "Joe",
"age": 22
}
},
{
"status": "not attending",
"profile": {
"name": "Bob",
"age": 44
}
}
],
"servlet": {
"servlet-name": "cofaxCDS",
"servlet-class": "org.cofax.cds.CDSServlet"
}
}
"""
let arr = decodeAttendees(json: json)
arr?.attendees[0].profile.name //"Joe"
arr?.attendees[1].status //"not attending"
Now, for your current Alamofire completion handler, I'm guessing that it would be as simple to modify it to be something along the lines of:
Alamofire.request(url, method: .get, parameters: nil, encoding: URLEncoding.default).responseJSON { (response) in
guard let data = response.data else { return //remember to error if data is nil }
do {
let array = JSONDecoder().decode(AttendeesArray.self, from: data)
//update your UI to show the array, or pass it on to a completion hanldler
} catch {
//handle errors
}
}

I can't seem to access sub arrays in swiftyJSON

So here is the JSON
{
"city": {
"id": 4930956,
"name": "Boston",
"coord": {
"lon": -71.059769,
"lat": 42.358429
},
"country": "US",
"population": 0,
"sys": {
"population": 0
}
},
"cod": "200",
"message": 0.0424,
"cnt": 39,
"list": [
{
"dt": 1473476400,
"main": {
"temp": 76.33,
"temp_min": 73.11,
"temp_max": 76.33,
"pressure": 1026.47,
"sea_level": 1027.96,
"grnd_level": 1026.47,
"humidity": 73,
"temp_kf": 1.79
},
"weather": [
{
"id": 500,
"main": "Rain",
"description": "light rain",
"icon": "10n"
}
],
"clouds": {
"all": 8
},
"wind": {
"speed": 7.29,
"deg": 300.501
},
Here is my Controller where I go grab the data......
class ViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var amConnected: UILabel!
#IBOutlet weak var weatherTable: UITableView!
var arrRes = [[String:AnyObject]]()
var swiftyJsonVar: JSON?
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.doSomethingNotification(_:)), name: "ReachabilityChangedNotification", object: nil)
let openWMAPI = "http://api.openweathermap.org/data/2.5/forecast/city?q=Boston,Ma&APPID=XXXXXXXXXXXXXXX&units=imperial"
Alamofire.request(.GET,openWMAPI).responseJSON{
(responseData) -> Void in
print(responseData)
let swiftyJsonVar = JSON(responseData.result.value!)
self.weatherTable.reloadData()
}
.responseString{ response in
//print(response.data.value)
// print(response.result.value)
//print(response.result.error)
//eprint("inhere");
}
weatherTable.rowHeight = UITableViewAutomaticDimension
weatherTable.estimatedRowHeight = 140
// Do any additional setup after loading the view, typically from a nib.
}
In my table loop it is now saying that the jsonArray is nil and failing.
I'm unsure as to what I'm doing wrong at this point.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = weatherTable.dequeueReusableCellWithIdentifier("theCell", forIndexPath: indexPath)
let label1 = cell.viewWithTag(101) as! UILabel
print("inhere")
if((swiftyJsonVar) != nil){
if let jsonArray = self.swiftyJsonVar["list"].array {
var temp = jsonArray[indexPath.row]["main"]["temp"].float
var rdate = jsonArray[indexPath.row]["dt_txt"].string
print(temp)
}else{
print("test")
}
}
label1.text = "TEST"
return cell
}
OVerall I'm just not sure how to dig down into the next level of the JSON.
If you are not averse to it, try using AlamofireObjectMapper instead of SwiftyJson
1) If you are going to change the name of the json keys very often, and are going to do a lot of enum transformations try :
AlamofireObjectMapper
2) If the names are going to be the same, with minimal transformations, directly use :
AlamofireJsonToObjects
Both of these cases, create model classes for your json object. If you have an array - you can define a var as an array
if it is an object or an array of objects - you can then create another model class which is again Mappable and then define such an object var in the original model.
The above libraries will make your code extremely clean while extracting objects to json.
You can access to the elements inside an JSON array in SwiftyJSON using consecutive subscripts, if we have the following JSON for example:
var json: JSON = ["name": "Jack", "age": 25,
"list": ["a", "b", "c", ["what": "this"]]]
Then you can access to the four element of the subarray listcontained in the main array in the following way for example:
json["list"][3]["what"] // this
Or you can define a path like this let path = ["list",3,"what"] and then call it in this way:
json[path] // this
With the above explained let's introduce it with your JSON file to list the elements inside the array weather:
if let jsonArray = json["list"].array {
// get the weather array
if let weatherArray = jsonArray[0]["weather"].array {
// iterate over the elements of the weather array
for index in 0..<weatherArray.count {
// and then access to the elements inside the weather array using optional getters.
if let id = weatherArray[index]["id"].int, let main = weatherArray[index]["main"].string {
print("Id: \(id)")
print("Main: \(main)")
}
}
}
}
And you should see in the console:
Id: 800
Main: Clear
I hope this help you.

Resources