traverse data using SwiftyJSON - ios

I have a JSON file I call from the internet
{
"Editions": [
{
"Version": "November",
"Articles": [
{
"title": "hello",
"subheading": "Article 1",
"content": "stuff",
"author": "John Smith",
"authorDescription": "Author",
"image": "pic1.jpg"
},
{
"title": "article2",
"subheading": "Article 2",
"content": "stuff2",
"author": "first name last name",
"authorDescription": "Author",
"image": ""
},
{
"title": "article3",
"subheading": "Article 3",
"content": "stuff3",
"author": "Callum West",
"authorDescription": "Author",
"image": ""
},
{
"title": "Article 4",
"subheading": "Article 4",
"content": "stuff 4",
"author": "tom C",
"authorDescription": "Author",
"image": ""
}
]
},
{
"Version": "October",
"Articles": [
{
"title": "article 1",
"subheading": "Article1",
"content": "stuff1.1",
"author": "Tom",
"authorDescription": "Author",
"image": ""
},
{
"title": "article2",
"subheading": "Article 2",
"content": "stuff2.1",
"author": "Sam",
"authorDescription": "Author",
"image": ""
},
{
"title": "article3",
"subheading": "Article 3",
"content": "stuff3.1",
"author": "TomC",
"authorDescription": "Author and Editor",
"image": ""
},
{
"title": "article 4",
"subheading": "Article 4",
"content": "stuff4.1",
"author": "brad name",
"authorDescription": "Author",
"image": ""
},
{
"title": "article5",
"subheading": "Article 5",
"content": "stuff5.1",
"author": "chris evuan",
"authorDescription": "Author",
"image": ""
},
{
"title": "article6",
"subheading": "Article 6",
"content": "stuff6.1",
"author": "Jo",
"authorDescription": "Author",
"image": ""
},
{
"title": "article7",
"subheading": "Article7",
"content": "stuff7.1",
"author": "Tom Hall",
"authorDescription": "Author",
"image": ""
}
]
}
]
}
On my first view controller I pull out the editions version with the following code
func loaddata(){
Alamofire.request(.GET, "my JSON url")
.responseJSON { response in
//get json from response data
//print (response.data)
let json = JSON(data: response.data!)
//print(json)
//for loop over json and write all article titles articles array
for (key, subJson) in json["Editions"] {
let version = subJson["Version"].string
let stuff = Edition(Version: version!)
// print(stuff)
self.editions.append(stuff!)
}
// let x = self.editions[0].Version
// print ("\(x)")
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
I then use a segue to pass the version clicked into my second view controller
Once done this on my second view controller I can print out the string of the version, in the view did load
let worked = pleasework as String!
//print("\(worked)")
I then want to use this string to traverse the Json and pull out the correlating content
So I call the function and pass it through
loaddata("\(worked)")
Here is the load data function
func loaddata(verionTitle: String){
Alamofire.request(.GET, "my JSON url")
.responseJSON { response in
//get json from response data
let json = JSON(data: response.data!)
// print(json)
//for loop over json and write all article titles articles array
// print("\(verionTitle)")
for (key, subJson) in json["Editions"][0]["Articles"]{
// print(subJson)
//let versionmuted = version as String!
//print("\(version)")
//if verionTitle =
//if version == verionTitle{
//print("The month is \(version)")
let author = subJson["title"].string
//print("\(author)")
let subheading = subJson["subheading"].string
let content = subJson["content"].string
let Author = subJson["author"].string
//let image = subJson["image"].string
let stuff = Article(name: author!, subheading: subheading!, content: content!, author: Author!)
self.articles.append(stuff!)
}
//end iff
//if let content = subJson["content"].string {
// self.Content.append(content)
//}
//end for
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}
I am struggling with looping over the JSON and only pulling out the articles relevant to the version, I am using the swiftyjson library
The current function obviously prints out all the articles in Editions[0]
However can i loop through all of Editions and use the string to only print Articles under that version
For example
for (key, subJson) in json["Editions"][0]["Version"] = veriontitle??{
//do stuff
}
Hope I explained myself well, hoping you can help

func loaddata(verionTitle: String){
Alamofire.request(.GET, "your json")
.responseJSON { response in
//get json from response data
let json = JSON(data: response.data!)
//print(json)
//start looping function pass through identifer from month, i've set October to 1 and so forth
func looping(Number: Int){
for (key, subJson) in json["Editions"][Number]["Articles"]{
let author = subJson["title"].string
//print("\(author)")
let subheading = subJson["subheading"].string
let content = subJson["content"].string
let Author = subJson["author"].string
let Image = subJson["image"].string
let stuff = Article(name: author!, subheading: subheading!, content: content!, author: Author!, image: Image!)
self.articles.append(stuff!)
//end loop
}
//end looping
}
//Get Version Titles, here is where I assign months to a number releavnt to where they are in the json
switch verionTitle{
case "November":
looping(0)
case "October":
looping(1)
case "December":
looping(2)
default:
print("no month")
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
}

Related

I keep getting the "No value associated with key" error when using the JSONDecoder in Swift. Can someone please explain what is going wrong here?

I am trying to read from a local JSON file and populate my tableView with cells to be retrieved from the Decoder. Since my table view was still empty, I added a breakpoint on the JSONDecoder.decode line to see what is going on. I get this error, even though I made sure that my naming convention is the same in both my structs and JSON file. Is there something I am missing here.
Since my naming convention was consistent across the files, at first I did not try add CodingKeys enum inside my structs. After a few failed runs I added that in but it feels kind of obsolete.
Where I run the decoder:
let fileName = "settings"
...
if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let list = try JSONDecoder().decode(SettingsPayload.self, from: data)
completion(list.sections)
} catch {
completion(nil)
}
}
fileprivate struct SettingsPayload: Decodable {
let sections: [Section]
}
My structs that will be used to store the data when retrieved:
struct Item: Decodable {
let title: String
let type: String
let url: String
private enum CodingKeys: String, CodingKey {
case title = "title"
case type = "type"
case url = "url"
}
}
struct Section: Decodable {
let title: String
let items: [Item]
private enum CodingKeys: String, CodingKey {
case title = "title"
case items = "items"
}
}
and my .json file:
{
"sections": [{
"section": {
"title": "Main Settings",
"items": [{
"item": {
"title": "Notifications",
"type": "notification",
"url": ""
},
"item": {
"title": "Log Out",
"type": "",
"url": ""
}
}]
},
"section": {
"title": "Feedback",
"items": [{
"item": {
"title": "Contact Us",
"type": "email",
"url": ""
},
"item": {
"title": "Rate on App Store",
"type": "webView",
"url": "https://www.apple.com/uk/ios/app-store/"
}
}]
},
"section": {
"title": "About",
"items": [{
"item": {
"title": "Terms of Service",
"type": "webView",
"url": ""
},
"item": {
"title": "Privacy Policy",
"type": "webView",
"url": "https://www.apple.com/uk/ios/app-store/"
},
"item": {
"title": "Version Info",
"type": "webView",
"url": ""
}
}]
}
}]
}
This is the error message I get:
- debugDescription : "No value associated with key CodingKeys(stringValue: \"title\", intValue: nil) (\"title\")."
I think the problem is that the decoder expects an Array of the item Section, but your json has an Array of dictionaries with a key "section" and an item Section in there.
Maybe try to modify your json like this:
{
"sections": [{
"title": "Main Settings",
"items": [ {
"title": "Notifications",
"type": "notification",
"url": ""
}, {
"title": "Log Out",
"type": "",
"url": ""
}]}, {
"title": "Feedback",
"items": [{
"title": "Contact Us",
"type": "email",
"url": ""
},{
"title": "Rate on App Store",
"type": "webView",
"url": "https://www.apple.com/uk/ios/app-store/"
}]}, {
"title": "About",
"items": [{
"title": "Terms of Service",
"type": "webView",
"url": ""
}, {
"title": "Privacy Policy",
"type": "webView",
"url": "https://www.apple.com/uk/ios/app-store/"
}, {
"title": "Version Info",
"type": "webView",
"url": ""
}
]}]
}
Update
JSONDecoder doesn't look for the name of your decodable struct in the JSON, it only looks for the name of the properties.

How to send json results to an email when button is pressed in swift 4?

I am coming to a problem where I have a list of json question where when the user fills out the general questions, I show the a button to submit and all I want to do is make that submit button to send me the json results to my email. is it possible with my code? thank you so much! By the way, I am using SurveyNative for survey creation.
import UIKit
import SurveyNative
class MyViewController: SurveyViewController, SurveyAnswerDelegate, CustomConditionDelegate, ValidationFailedDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.setSurveyAnswerDelegate(self)
self.setCustomConditionDelegate(self)
self.setValidationFailedDelegate(self)
}
override func surveyJsonFile() -> String {
return "GeneralQuestions"
}
override func surveyTitle() -> String {
return "General Questions"
}
func question(for id: String, answer: Any) {
print("Question: \(id) has answer (maybe is complete): \(answer)")
if (surveyQuestions!.isQuestionFullyAnswered(id)) {
print("Question: \(id) is complete")
}
}
func isConditionMet(answers: [String: Any], extra: [String: Any]?) -> Bool {
let id = extra!["id"] as! String
if id == "check_age" {
if let birthYearStr = answers["birthyear"] as? String, let ageStr = answers["age"] as? String {
let birthYear = Int(birthYearStr)
let age = Int(ageStr)
let wiggleRoom = extra!["wiggle_room"] as? Int
let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.year], from: date)
let currentYear = components.year
return abs(birthYear! + age! - currentYear!) > wiggleRoom!
} else {
return false
}
} else {
Logger.log("Unknown custom condition check: \(id)")
return false
}
}
func validationFailed(message: String) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
My Json:
{
"questions": [
{
"id": "ice_cream",
"header": "Question 1",
"question": "Did you enjoy the discussion topic?",
"question_type": "single_select",
"options": [
"YES",
"NO",
"I DONT KNOW",
{
"title": "Other",
"type": "freeform"
}
]
},
{
"id": "perfect_day",
"header": "Question 2",
"question": "Did you enjoy the presenter? If yes, why?",
"question_type": "single_select",
"options": [
"YES",
"NO",
"I DONT KNOW",
{
"title": "Other",
"type": "freeform"
}
]
},
{
"id": "ice_cream",
"header": "Question 3",
"question": "Did you feel you engaged or connected to the topic discussed?",
"question_type": "single_select",
"options": [
"YES",
"NO",
{
"title": "Other",
"type": "freeform"
}
]
},
{
"id": "perfect_day",
"header": "Question 4",
"question": "Do you have any topics you would like to discuss?",
"question_type": "single_select",
"options": [
"YES",
"NO",
"I DONT KNOW",
]
},
{
"id": "felt_like_adult",
"header": "Question 5",
"question": "Do you feel we can change anything?",
"question_type": "single_select",
"options": [
"YES",
"NO",
"I DONT KNOW",
]
},
{
"id": "pets",
"header": "Question 6",
"question": "Do you prefer a specific presenter? If yes, why?",
"question_type": "single_select",
"options": [
"YES",
"NO",
"I DONT KNOW",
{
"title": "Other",
"type": "freeform"
}
]
},
],
"submit": {
"button_title": "Submit Answers",
"url": "https://www.example.com"
},
"auto_focus_text": true
}
Can I know what's the purpose of getting JSON Results in Email? These are a few option of sending email stated as below.
Option 1
You can use MFMailComposeViewController to send email from your app. Set your JSON into message body.
Here's a link about how to send an email with MFMailComposeViewController.
Option 2
Use API service to send email messages back to your email. For example: Gmail API. You can have a look on here
Option 3
Create your own API to post the JSON result to your server and receive your message using your own mail server.

Need to show TableView sections and rows according to json data received

I get JSON data as follows:
{
"upcoming": [
{
"id": "17",
"date": "2018/04/23 13:25",
"title": "Team A"
},
{
"id": "20",
"date": "2018/04/23 13:25",
"title": "Team B"
},
{
"id": "10",
"date": "2019/06/16 21:45",
"title": "Team c"
}
]
}
I need to show a table view with sections according to the date key. How can I populate a table view with sections and rows accordingly?
Swift comes with the amazing Codable protocol built in, you should read up on it. This will easily allow you to understand what is going on in this Playground:
import Cocoa
let jsonData = """
{
"upcoming": [
{
"id": "17",
"date": "2018/04/23 13:25",
"title": "Team A"
},
{
"id": "20",
"date": "2018/04/23 13:25",
"title": "Team B"
},
{
"id": "10",
"date": "2019/06/16 21:45",
"title": "Team c"
}
]
}
""".data(using: .utf8)!
struct Match : Codable {
let id: String
let date: String
let title: String
}
struct Matches : Codable {
let upcoming: [Match]
}
do {
let matches = try JSONDecoder().decode(Matches.self, from:jsonData)
print(matches.upcoming.count)
} catch {
print(error)
}
Now your matches.upcoming are your model array and using it as a TableView datasource is really straightforward.

Use Swifty JSON to parse array

This is my json to parse (example):
[
{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
},
{
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}
]
paste it this json viewer to view it easily.
as you can see, it is an array (of teams).
I need to get each team and do something with it.
After many attempts, I tried using SwiftyJSON, because I thought it will be easier. But, it did not worked for me.
This is what I tried:
let array = JSON(response)
// print each subJSON in array
for team in array.arrayValue {
print(team)
}
But the loop does not work. It does not go in to the loop at all.
Maybe it does not understand that my json is an array.
I can see the array object in the debugger. It looks like this:
How can I get these sub-JSONs?
Thanks.
I think you should use
let array = JSON(parseJSON: response)
instead of
let array = JSON(response)
SwiftyJSON contains methods to parse JSON string into a JSON object, check documentation
/**
Parses the JSON string into a JSON object
- parameter json: the JSON string
- returns: the created JSON object
*/
public init(parseJSON jsonString: String) {
if let data = jsonString.data(using: .utf8) {
self.init(data)
} else {
self.init(NSNull())
}
}
/**
Creates a JSON from JSON string
- parameter string: Normal json string like '{"a":"b"}'
- returns: The created JSON
*/
#available(*, deprecated: 3.2, message: "Use instead `init(parseJSON: )`")
public static func parse(json: String) -> JSON {
return json.data(using: String.Encoding.utf8)
.flatMap{ JSON(data: $0) } ?? JSON(NSNull())
}
or alternatively you can convert son string into son object like
Swift 3:
let dataFromString = response.data(using: .utf8)
let jsonArray = JSON(data: dataFromString!)
In the following example, I save team names in an array. I've tested it.
var names = [String]()
if let array = json.array {
for i in 0..<array.count {
let name = array[i]["name"]
names.append(name.stringValue)
}
}
print(names) // ["Team name", "Team name"]
Here is the answer for Swift 5. In My case data response is something like below :
[
{
"Name": "Some Type",
"Data": [
{
"ParentId": 111,
"Code": "Personal",
"SortOrder": 1,
"Name": "Personal",
"Id": 323
},
{
"ParentId": null,
"Code": "Work",
"SortOrder": 2,
"Name": "Work",
"Id": 324
}
],
"KeyType": "Integer"
},
{
"Name": "Phone Type",
"Data": [
{
"ParentId": null,
"Code": "Phone",
"SortOrder": 1,
"Name": "Phone",
"Id": 785
},
{
"ParentId": null,
"Code": "Cell",
"SortOrder": 2,
"Name": "Cell",
"Id": 786
},
{
"ParentId": null,
"Code": "Fax",
"SortOrder": 3,
"Name": "Fax",
"Id": 787
},
{
"ParentId": null,
"Code": "Home",
"SortOrder": 4,
"Name": "Home",
"Id": 788
},
{
"ParentId": null,
"Code": "Office",
"SortOrder": 5,
"Name": "Office",
"Id": 789
}
],
"KeyType": "Integer"
}
]
I was handled it with following code.
struct responseObjectClass:BaseModel {
var responsearray: [arrayData]? = nil
init(json: JSON) {
responsearray = json.arrayValue.map { arrayData(json: $0) }
}
}
struct arrayData:BaseModel {
let Name: String?
var DataValue: [DataLookup]? = nil
let KeyType: String?
init(json: JSON) {
Name = json["Name"].stringValue
DataValue = json["Data"].arrayValue.map { DataLookup(json: $0) }
KeyType = json["KeyType"].stringValue
}
}
struct DataLookup:BaseModel {
let ParentId: Any?
let Code: String?
let SortOrder: Int?
let Name: String?
let Id: Int?
init(json: JSON) {
ParentId = json["ParentId"]
Code = json["Code"].stringValue
SortOrder = json["SortOrder"].intValue
Name = json["Name"].stringValue
Id = json["Id"].intValue
}
}
BaseModel is Optional it's just used for init Json.
protocol BaseModel {
init(json: JSON)
}
Without SwiftyJSON
Below is the valid JSON
data.json File
[{
"id": 1,
"name": "Team name",
"shower": {
"id": 1,
"status": 1,
"startLocation": {
"id": 1,
"name": "abc 16"
}
}
}, {
"id": 2,
"name": "Team name",
"shower": {
"id": 2,
"status": 1,
"startLocation": {
"id": 1,
"name": "efg 16"
}
}
}]
Below is the code to read your json.
if let path = Bundle.main.path(forResource: "data", ofType: "json") {
let url = URL(fileURLWithPath: path)
do {
let data = try Data(contentsOf: url)
if let jsonArray = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSArray {
for (_, item) in jsonArray.enumerated() {
let itemDict = item as! NSDictionary
let id = itemDict["id"] as! Int
let name = itemDict["name"] as! String
let shower = itemDict["shower"] as! NSDictionary
let showerId = shower["id"] as! Int
let showerStatus = shower["status"] as! Int
let startLocation = shower["startLocation"] as! NSDictionary
let startLocationId = startLocation["id"] as! Int
let startLocationName = startLocation["name"] as! String
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}
this is what worked for me:
// Convert JSON to Array
func JSONToArray(_ json: String) -> Array<Any>? {
if let data = json.data(using: String.Encoding.utf8) {
do {
return try JSONSerialization.jsonObject(with: data, options: []) as? Array
} catch let error as NSError {
print(error)
}
}
return nil
}
After using this function i could loop trough the sub JSONs.
Thanks.

iOS swift tableView header with JSON array

I have a UITableViewlike this:
and a tableView data fetch from JSON with Alamofire and SwiftyJson:
JSON Data:
{
"timeline": {
"Milan": {
"place": [
{
"name": "Place 1",
"kind": "historic",
},
{
"name": "Place 2",
"kind": "historic",
},
{
"name": "Place 3",
"kind": "historic",
},
{
"name": "Place 4",
"kind": "historic",
}
]
},
"Paris": {
"place": [
{
"name": "Place 1",
"kind": "historic",
},
{
"name": "Place 2",
"kind": "historic",
},
{
"name": "Place 3",
"kind": "historic",
}
]
}
}
}
my question is how I separate city place in one array that inherit from class called Place and put data in tableView like above image.
struct Place {
var name = ""
var type = ""
}
I wrote some code in Alamofire .success but this is not enough:
if let jsonData = response.result.value {
let json = JSON(jsonData)
if let items = json["timeline"].dictionary {
for (key,subJson):(String, JSON) in items {
self.cityName.append(key)
...
}
}
}
https://gist.github.com/himanshu-benzatine/75fa44dc0e3f2752973a79984b182d70
Check this link and if any problem then tell me. i give you basic idea of how to create tableview with header and selection.
Happy coding

Resources