serializing array to object in swift - arrays

i have the following object class:
class NewsItem: NSObject {
var storyCategory: String?
var titleText: String?
var paragraph1: String?
var paragraph2: String?
var featureImage: String?
var secondImage: String?
var storyDate: String?
var majorReference: String?
var fact: String?
var actualFeatureImage: UIImage? // Using these two to pass the image from home to story view
var actualSecondImage: UIImage?
var referencesArray = [AnyObject]()
...
init?(dictionary: [String: AnyObject]) {
guard
let storyCategory = dictionary["category"] as? String,
let titleText = dictionary["title"] as? String,
let paragraph1 = dictionary["paragraph1"] as? String,
let paragraph2 = dictionary["paragraph2"] as? String,
let featureImage = dictionary["headerImage"] as? String,
let storyDate = dictionary["date"] as? String,
let majorReference = dictionary["majorReference"] as? String,
let secondImage = dictionary["secondImage"] as? String
else {
return nil
}
self.storyCategory = storyCategory
self.titleText = titleText
self.paragraph2 = paragraph2
self.paragraph1 = paragraph1
self.featureImage = featureImage
self.storyDate = storyDate
self.majorReference = majorReference
self.fact = dictionary["fact"] as? String //if there's a fact in the dict, it will be assigned and if not there'll be nil
self.secondImage = secondImage
let referenceObject = dictionary["reference"] as? [[String: AnyObject]]
for object in referenceObject! {
self.referencesArray.append(object)
//print(object)
}
//bellow is a snippet from the method that's serializing the JSON data
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments)
if let newsArticles = json["stories"] as? [[String: AnyObject]] {
for article in newsArticles {
let newsArticle = NewsItem(dictionary: article)
newsItems.append(newsArticle!)
}
}
} catch {
print("error in the news items delivery: \(error)")
let displayError = EHPlainAlert(title: "hmmm...", message: "\(error)", type: ViewAlertError)
displayError.show()
}
}
in the backend of the app, i have JSON objects and one of the values of each of the objects is as follows:
... "reference" : [
{
"refName": "CNN",
"refURL": "http://edition.cnn.com/2016/07/19/football/brexit-effect-on-english-premier-league/index.html"
},
{
"refName": "Telegraph",
"refURL": "http://www.telegraph.co.uk/football/2016/06/21/what-would-brexit-mean-for-the-premier-league/"
}
], ...
i don't know if i have the JSON wrong o' something but what i intend to have is an array of references, each reference having a reference name(refName) and the url of the reference (refURL).
my question is, what would be the best method to have the JSON object above be added to the referencesArray in my object class. Would it be better to use a dictionary instead of an array? if so, how should i re-write my code?
if you need any more details about the code, ask and ye shall receive an update. Thanks!
EDIT
Here's the JSON in full :
{
"stories" : [
{
"title" : "English Premier League vs BREXIT: Who will win?",
"headerImage" : "http://i2.cdn.turner.com/cnnnext/dam/assets/160117112140-rooney-goal-exlarge-169.jpg",
"category" : "Sports",
"paragraph1" : "Paragraph text",
"paragraph2" : "More text goes here",
"date" : "21st July",
"majorReference" : "CNN",
"reference" : [
{
"refName": "CNN",
"refURL": "http://edition.cnn.com/2016/07/19/football/brexit-effect-on-english-premier-league/index.html"
},
{
"refName": "Telegraph",
"refURL": "http://www.telegraph.co.uk/football/2016/06/21/what-would-brexit-mean-for-the-premier-league/"
}
],
"secondImage" : "http://www.telegraph.co.uk/content/dam/football/2016/06/14/02-leicester-afp-xlarge_trans++Wy_u4a9GUNQgLIY2EGV3qvLCN329DeTLuwi-bwi35Bo.jpg",
"fact" : "Talent will go missing"
}, ...
Thats just one story... after the comma, is another story.

First, define a data model for your reference:
struct NewsItemReference {
var refName: String
var refURL: NSURL
}
Then modify your NewsItem class as below:
class NewsItem: NSObject {
var referencesArray = [NewsItemReference]()
init?(dictionary: [String: AnyObject]) {
...
if let references = dictionary["reference"] as? [[String: AnyObject]] {
for object in references {
guard let refName = object["refName"] as? String,
let refURLString = object["refURL"] as? String,
let refURL = NSURL(string: refURLString) else {
continue
}
self.referencesArray.append(NewsItemReference(refName: refName, refURL: refURL))
}
}
}
If you see yourself quickly outgrowing this model (the code is too verbose), take a look at some JSON-to-object mapping framework like ObjectMapper.

Related

Map function explanation

Someone can explain me that piece of code because I can't understand well. I find this code and I can't understand notably this line : Room(dict: $0)
var rooms: [Room] = [] // The globale variable
func refresh() {
let request = URLRequest(url: URL(string: "\(Config.serverUrl)/rooms")!)
NSURLConnection.sendAsynchronousRequest(request, queue: OperationQueue.main, completionHandler: { resp, data, err in
guard err == nil else {
return
}
let rooms = try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions()) as! [[String: AnyObject]]
self.rooms = rooms.map {
Room(dict: $0) // I can't understand this line
}
self.tableView.reloadData()
})
}
My Room struct:
struct Room {
var key: String
var title: String
var cat: String!
init(dict: [String: AnyObject]) {
title = dict["title"] as! String
key = dict["key"] as! String
cat = dict["cat"] as! String
}
init(key: String, title: String, cat: String) {
self.key = key
self.title = title
self.cat = cat
}
func toDict() -> [String: AnyObject] {
return [
"title": title as AnyObject,
"key": key as AnyObject,
"cat": cat as AnyObject
]
}
}
If someone can help me to understand and explain it, thank you
The map function loops over every item in a collection, and applies an operation to each element in the collection.
This piece of code
self.rooms = rooms.map {
Room(dict: $0)
}
is a short form of this.
// `dict` paramater is `$0` in shorter form
self.rooms = rooms.map { (dict : [String: AnyObject]) -> Room in
return Room(dict: dict)
}

Swift 3 JSON Array to Dictionary

I have a JSON result from Alamofire and SwiftyJSON and am trying to create a dictionary from it to create an array
JSON Result
JSON: [
{
"p_589b6a49a0bfd" : {
"path" : "\/uploads\/588fa43eba3c9\/588fa43eba3c9_1486580297.jpg",
"likes" : "0",
"userid" : "588fa43eba3c9",
"caption" : "Bae",
"comments" : "0",
"date" : "1486580297"
}
},
{
"p_589b7f1c540f1" : {
"path" : "\/uploads\/588fa43eba3c9\/588fa43eba3c9_1486585628.jpg",
"likes" : "0",
"userid" : "588fa43eba3c9",
"caption" : "Hot stuff bitch ",
"comments" : "0",
"date" : "1486585628"
}
}
]
Request/Response
Alamofire.request(BASE_URL + "index.php/feed/build", method: .get, headers: headers).responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
print("JSON: \(json)")
case .failure(let error):
print(error)
}
}
I have then set up a simple class called 'FeedPost' which will store each of the elements in JSON response (this is the function in the FeedPost class)
init(postid: String, postData: Dictionary<String, AnyObject>) {
self._postid = postid
if let caption = postData["caption"] as? String {
self._caption = caption
}
if let path = postData["path"] as? String {
self._path = path
}
if let likes = postData["likes"] as? Int {
self._likes = likes
}
if let comments = postData["comments"] as? Int {
self._comments = comments
}
if let userid = postData["userid"] as? String {
self._userid = userid
}
if let date = postData["date"] as? String {
self._date = date
}
}
I need to somehow cycle through the JSON to create a dictionary to pass to FeedPost then add each FeedPost to another array called Posts during the request. The string that starts with 'p_' I want to use as the postid
With Alamofire response you have used SwiftyJSON and with your FeedPost init you are using swift native dictionary. So I'm suggesting you to either work with SwiftyJSON or with swift's native type. Since you have already added init with dictionary I'm answering your answer with native type.
Alamofire.request(BASE_URL + "index.php/feed/build", method: .get, headers: headers).responseJSON { response in
switch response.result {
case .success(let value):
If let dic = value as? [String: Any],
let array = DIC["JSON"] as? [[String: Any]] {
for item in array {
for (key, value) in item {
If let subDic = value as? [String: Any] {
let obj = FeedPost(postid: key, postData: subDic)
}
}
}
}
print("JSON: \(json)")
case .failure(let error):
print(error)
}
}
Note: Correct Dictionary notation for JSON in swift 3 is [String: Any] not [String: AnyObject] so change your init parameter postData's type to [String: Any].
Below is the code which can be used for your case, This code is copied from playgrounds.
import UIKit
typealias JSONDictionary = [String: AnyObject]
class Post {
let id: String
let userId: String?
let date: Double?
let caption: String?
let comments: Double?
let likes: Double?
let path: String?
init?(with dictionary: JSONDictionary) {
guard let postId = dictionary.keys.first, let postInfo = dictionary[postId] as? JSONDictionary else { return nil }
self.id = postId
self.userId = postInfo["userid"] as? String
self.date = postInfo["date"] as? Double
self.caption = postInfo["caption"] as? String
self.comments = postInfo["comments"] as? Double
self.likes = postInfo["likes"] as? Double
self.path = postInfo["path"] as? String
}
}
Parsing JSON array will be like this.
case .success(let value):
let jsonArray = value["JSON"] as? [JSONDictionary]
let posts = jsonArray?.flatMap(Post.init(with:))
print("Posts \(posts)"
case .failure: break
I have tried this using a local JSON file in a Playgrounds & code was something like this.
let url = Bundle.main.url(forResource: "data", withExtension: "json")
let data = try! Data(contentsOf: url!)
let jsonArray = try! JSONSerialization.jsonObject(with: data , options: .allowFragments) as? [JSONDictionary]
let posts = jsonArray?.flatMap(Post.init(with:))

How to Save complex Arrayobjects to device using NSKeyedArchiver swift

I want to save an array of any class (e.g let array = SymptomsModel) type into device using NSKeyedArchiver in swift .
I Know how to save an array if SymptomsModel class contains all variables with primitive data types , but don't know how to save it if also contains an array of any other class as its property
Below I have explained my problem with the help of example , please go through it and provide solution.
I have a class
class SymptomsModel: NSObject, NSCoding ,ResponseJSONObjectSerializable {
var slug:String?
var name:String?
var images:[Sym_images]?
var videos:[Sym_videos]?
struct Keys {
static let Name = "name"
static let Slug = "slug"
static let Images = "images"
static let Videos = "videos"
}
required init(json:SwiftyJSON.JSON) {
self.slug = json["slug"].string
self.name = json["name"].string
self.images = [Sym_images]()
if let imagesJSON = json["images"].array {
for(imagesJSON) in imagesJSON {
if let newImages = Sym_images(json: imagesJSON){
self.images?.append(newImages)
}
}
}
self.videos = [Sym_videos]()
if let videosJSONArray = json["videos"].array {
for(videosJSON) in videosJSONArray {
if let newVideos = Sym_videos(json: videosJSON){
self.videos?.append(newVideos)
}
}
}
}
init(dictionary: [String : AnyObject]) {
self.name = dictionary[Keys.Name] as? String
self.slug = dictionary[Keys.Slug] as? String
self.images = dictionary[Keys.Images] as? [Sym_acc_images_objects]
self.videos = dictionary[Keys.Videos] as? [Sym_acc_videos_objects]
}
func encodeWithCoder(archiver: NSCoder) {
archiver.encodeObject(name, forKey: Keys.Name)
archiver.encodeObject(slug, forKey: Keys.Slug)
archiver.encodeObject(images, forKey: Keys.Images)
archiver.encodeObject(videos, forKey: Keys.Videos)
}
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_images_objects]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_acc_videos_objects]
}
and a PersistanceManager class to save the data with NskeyedArchiver as
class PersistenceManager {
class private func documentsDirectory() -> NSString {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentDirectory = paths[0] as String
return documentDirectory
}
class func saveNSArray(arrayToSave: NSArray, key: String) {
print(" saveNSArray key : \(key)")
let file = documentsDirectory().stringByAppendingPathComponent(key)
NSKeyedArchiver.archiveRootObject(arrayToSave, toFile: file)
}
class func loadNSArray(path: String) -> NSArray? {
print(" loadNSArray key : \(path)")
let file = documentsDirectory().stringByAppendingPathComponent(path)
let result = NSKeyedUnarchiver.unarchiveObjectWithFile(file)
return result as? NSArray
}
}
And Here is my implimentation of saving and then retreiving the array
class ViewController: UIViewController{
var ArraySymptom = [SymptomsModel]()
override func viewDidLoad() {
super.viewDidLoad()
ArraySymptom = loadArray()
//saving data in device
PersistenceManager.saveNSArray(ArraySymptom, key: "Symptom")
//loading data from device
if let value = PersistenceManager.loadNSArray("Symptom") as? [SymptomsModel] {
let images = value[0].images
print("images : \(images)")
let slug = value[0].slug
print("slug : \(slug)")
}
}
Here am able to get the value of slug but not able to fetch images value.
It might be happening because slug is of String type and Images is of Custom Class type .
Please suggest me the way i can get it done .
Is is possible to save these type of arrays with NSKeyedArchiver , so that i can access images value just by retreiving ArraySymptom from device.
Silly mistake it was
I was getting nil in Images because it was decoded with wrong key , it was copy paste mistake
The Error was in this function..
required init(coder unarchiver: NSCoder) {
super.init()
name = unarchiver.decodeObjectForKey(Keys.Name) as? String
slug = unarchiver.decodeObjectForKey(Keys.Slug) as? String
self.images = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Slug) as? [Sym_videos]
}
And the correct decoding must be
self.images = unarchiver.decodeObjectForKey(Keys.Images) as?
[Sym_images]
self.videos = unarchiver.decodeObjectForKey(Keys.Videos) as?
[Sym_videos]

JSON on Swift: Array to String/TextField Text

I have problem with JSON Array that I want to be display on textfield. JSON is taken from URL. This is JSON structure:
{
description = „This is short decripton”;
);
more-description = (
„this is first line”,
„this is second line”,
„third line”,
„etc”,
„etc”
);
one-more-description = (
„this is first line”,
„this is second line”,
„third line”,
„etc”,
„etc”
);
And this is my code:
import UIKit
class RecipeViewController: UIViewController {
#IBOutlet weak var descriptionTextField: UITextView!
#IBOutlet weak var more-descriptionTextField: UITextView!
#IBOutlet weak var one-more-descriptionTextField: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
let urlAsString = "http://JSON-Address.com"
let url = NSURL(string: urlAsString)!
let urlSession = NSURLSession.sharedSession()
let jsonQuery = urlSession.dataTaskWithURL(url, completionHandler: { data, response, error -> Void in
do {
if let jsonDate = data, let jsonResult = try NSJSONSerialization.JSONObjectWithData(jsonDate, options: []) as? NSDictionary {
print(jsonResult)
let jsonDescription = jsonResult["description"] as? String
print("result: \(jsonDescription)")
let jsonMoreDescrp: AnyObject? = jsonResult["more-description"] as? Array<AnyObject>
print("result: \(jsonMoreDescrp)")
let jsonOneMoreDescrp: AnyObject? = jsonResult["one-more-description"] as? Array<AnyObject>
print("result: \(jsonOneMoreDescrp)")
dispatch_async(dispatch_get_main_queue(),{
self.descriptionTextField.text = jsonDescription
self.more-descriptionTextField.text = jsonMoreDescrp as? String
self.one-more-descriptionTextField.text = jsonOneMoreDescrp as? String
});
}
} catch let error as NSError {
print(error)
}
})
jsonQuery.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
The problem is with jsonMoreDescrp & jsonOneMoreDescrp. Althought I've changed it to String, after running Xcode the result is empty. jsonDescription of course works, but this is just simple string.
I know I'm doing something wrong with Array, but - can you tell me what?
jsonMoreDescrp & jsonOneMoreDescrp are array. so, you can call like this
self.more-descriptionTextField.text = jsonMoreDescrp[indexValue] as? String
self.one-more-descriptionTextField.text = jsonOneMoreDescrp[indexValue] as? String
Hope this will help you.
Try this way, if it not works comment me the error you come across.
self.more-descriptionTextField.text = jsonMoreDescrp[indexValue].stringValue
self.one-more-descriptionTextField.text = jsonOneMoreDescrp[indexValue].stringValue
indexValue is the key of the value you want in the json.
[Updated] As Sudhir noticed there is also error in code, try this to show comma separated strings:
dispatch_async(dispatch_get_main_queue(),{
self.descriptionTextField.text = jsonDescription
self.more-descriptionTextField.text = (jsonMoreDescrp as? [String])?.joinWithSeparator(",") ?? ""
self.one-more-descriptionTextField.text = (jsonOneMoreDescrp as? [String])?.joinWithSeparator(",") ?? ""
});
Validate JSON structure online before using http://jsonlint.com/
Valid JSON:
{
"description": "This is short decription",
"more-description": [
"this is first line",
"this is second line",
"third line",
"etc",
"etc"
],
"one-more-description": [
"this is first line",
"this is second line",
"third line",
"etc",
"etc"
]
}
try this code, it is much easy to use.
First of all parse all elements of map to the variables and than do what you want with knowing structure of just created variables.
let task = session.dataTask(with: url!) {
(data, response, error) in
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let desc = json["description"] as? String,
let moreDesc = json["more-description"] as? [String],
let oneMoreDesc = json["one-more-description"] as? [String] {
dispatch_async(dispatch_get_main_queue(),{
self.descriptionTextField.text = moreDesc
self.more-descriptionTextField.text = moreDesc.joinWithSeparator("/n")
self.one-more-descriptionTextField.text = oneMoreDesc.joinWithSeparator("/n")
});
}
} catch let error {
print (error)
}
}
task.resume()
I have not tested it, but it should work. Feel free to ask.

struggling with JSON parsing in swift

I am trying to load data in JSON format from my server into IOS application.
Here is my JSON:
[
{
"BankName": "bank1",
"CurrencyName": "cur1",
"SellRate": "0.65",
"BuyRate": "0.55",
"OfficialRate": "0.6"
},
{
"BankName": "bank1",
"CurrencyName": "cur2",
"SellRate": "1.65",
"BuyRate": "1.55",
"OfficialRate": "1.6"
}
]
There are 2 files in my project:
1:
import Foundation
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
2:
import Foundation
class JsonTest {
func loadJson(completion: ((AnyObject) -> Void)!) {
var urlString = "http://a.com/g.php"
let session = NSURLSession.sharedSession()
let sourceUrl = NSURL(string: urlString)
var task = session.dataTaskWithURL(sourceUrl!){
(data, response, error) -> Void in
if error != nil {
println(error.localizedDescription)
} else {
var error: NSError?
var jsonData = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &error) as NSArray
var shots = [Shot]()
println(jsonData)
for shot in jsonData{
let shot = Shot(data: shot as NSDictionary)
shots.append(shot)
}
println(shots) //[jsontest.Shot, jsontest.Shot]
}
}
task.resume()
}
}
I am trying to populate array automatically when my app starts. To do it I have a code in my mainViewController class.
override func viewDidLoad() {
super.viewDidLoad()
let api = JsonTest()
api.loadJson(nil)
}
The problem occurs when I try to print shots variable in the second file.
it returns [jsontest.Shot, jsontest.Shot] when I was expecting the array of dictionaries.
println(jsonData) works fine and shows JSON data from URL.
Can anybody advise what is wrong in my program?
"shots" is an array of instances of Shot, not a dictionary:
class Shot {
var bankName: String!
var currencyName: String!
var sellRate: String!
var buyRate: String!
var offRate: String!
init (data: NSDictionary) {
self.bankName = getStringFromJSON(data, key:"BankName")
self.currencyName = getStringFromJSON(data, key:"CurrencyName")
self.sellRate = getStringFromJSON(data, key:"SellRate")
self.buyRate = getStringFromJSON(data, key:"BuyRate")
self.offRate = getStringFromJSON(data, key: "OfficialRate")
}
func getStringFromJSON(data: NSDictionary, key: String) -> String {
if let info = data[key] as? String{
return info
}
return ""
}
}
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
let sourceUrl = NSURL(string: urlString)
// Using NSData instead of NSURLSession for experimenting in Playground
let data = NSData(contentsOfURL: sourceUrl!)
var error: NSError?
// As I'm using Swift 1.2 I had to change "as" with "as!"
let jsonData = NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers, error: &error) as! NSArray
for shot in jsonData{
let shot = Shot(data: shot as! NSDictionary)
shots.append(shot)
}
println(shots[0].bankName)
Update for Swift 2
var shots = [Shot]()
let urlString = "http://almazini.lt/getrates.php"
// Using NSData instead of NSURLSession for experimenting in Playground
if let sourceUrl = NSURL(string: urlString) {
NSURLSession.sharedSession().dataTaskWithURL(sourceUrl, completionHandler: { (data, response, error) in
if error == nil {
if let data = data, jsonData = try? NSJSONSerialization.JSONObjectWithData(data, options: []), jsonArray = jsonData as? [NSDictionary] {
for item in jsonArray {
let shot = Shot(data: item)
shots.append(shot)
}
print(shots[0].bankName)
} else {
print("no JSON data")
}
} else {
print(error!.localizedDescription)
}
}).resume()
}
Seems like there are two problems:
You're trying to use println to debug instead of setting a breakpoint and checking your objects values.
You have not created a description or debugDescription property for your object, so println on your object is just using some default implementation.
shots is an array of your custom object, so when you call println, it's using the description for Array, which prints out the objects in the array, comma separated, and within square brackets.
The default description property for classes in Swift just prints the class name.
Ideally, you should just use a break point to check the values of your object to be certain it initialized correctly, but if it's actually important to get them to print right, it's only a matter of implementing the description property:
override var description: String {
get {
// build and return some string that represents your Shot object
}
}

Resources