Parse JSON in Swift 24hours of problems [closed] - ios

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 4 years ago.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Improve this question
I am running low on energy and time complete this project now, I have done everything I can to learn and understand and implement a solution with no success, I would really like some help with this. Anyone got any ideas for implementing a solution?
I am really struggling to parse a json file into my iOS app in Swift.
I have a JSON output from an SQL db. I think the formatting is wrong and the data headers are a bit ambiguous but my developer who I hired to create the process is happy with the output. Link below.
So in the last 3 days I have read all of the Apple Developer materials on parsing JSON, also watched numerous tutorials which all cover pretty much the same data, and I don't know how much I have read and converted into actual projects which have all failed. I am really needing some guidance here. I have even in a desperate attempt to understand my problem from a different perspective I have looked at how it is done in other languages.
This is the latest iteration:
import UIKit
import Foundation
struct ScheduleList: Codable {
let data: [Datum]
enum CodingKeys: String, CodingKey {
case data = "data"
}
}
struct Datum: Codable {
let date: String
let message: String
let notice: String
enum CodingKeys: String, CodingKey {
case date = "date"
case message = "message"
case notice = "notice"
}
}
class VCMainViewController: UIViewController {
#IBOutlet weak var flightTable: UITableView!
#IBOutlet weak var date: UILabel!
#IBOutlet weak var route: UILabel!
#IBOutlet weak var std: UILabel!
#IBOutlet weak var sta: UILabel!
#IBOutlet weak var pax: UILabel!
let flight = [Datum]()
func parse() {
let jsonUrlString = "http://35.237.114.234/api/index.php?uid=Bx7faf08A9fYJ7bZCNMUX9EzxYN2"
guard let url = URL(string: jsonUrlString) else { return }
let task = URLSession.shared.scheduleListTask(with: url) { (scheduleList, response, error) in
guard let scheduleList = scheduleList, error == nil, response != nil else {
print("Something Wrong")
return
}
print("downlaoded")
do {
let decoder = JSONDecoder()
//Error Here: Cannot convert value of type 'ScheduleList' to expected argument type 'Data'
let downloadedFlights = try decoder.decode([scheduleList], from: scheduleList)
self.flight = downloadedFlights.date
} catch {
print("Something wrong after loading")
}
}
//I find I can print data / scheduleList here and it still returns correct data. So something above I guess is the problem.
task.resume()
/*
if scheduleList != nil {
print(scheduleList!)
} else {
print(error)
}
}
*/
}
override func viewDidLoad() {
super.viewDidLoad()
parse()
}
}
**Added full JSON file now due to issue with original post.
I think the structure is the problem.
Any advice where I am going wrong? I can't cast that as Data, the dataTask data is called scheduleList so I don't know why this won't accept it.
Thanks

Just change your last two characters from ], to ]}, and you'll have valid JSON.
It might even work :)
If it doesn't, please post back the exact error message, and the line it's failing on.
And again, having a JSON formatter/browser/validator handy is Good. I generally use a Notepad++ plugin. Here's a good on-line browser:
JSON formatter and validator
See also:
Stack Overflow: How to create a Minimal, Complete, and Verifiable example

The new way to handle JSON in Swift 4.1 is to use the Codable protocol (or its subvariants Encodable and Decodable). To go from a JSON string to actual objects, you should use Decodable.
//: Playground - noun: a place where people can play
import UIKit
import Foundation
struct Message: Decodable {
let date: String
let message: String
let notice: String
}
struct Flight: Decodable {
let flight: String
let aircraft: String
let operating: String
let type: String
let route: String
let std: String
let sta: String
let pax: String
let prm: String
let check: String
}
struct Response: Decodable {
let data: [Message]
let flights: [Flight]
let state: String
private enum CodingKeys: String, CodingKey {
case data, state
case flights = "flight" // Parses "flight" from the response to the flights property
}
}
// Get a String from your JSON and convert it to Data
let jsonString = "..."
let jsonData = jsonString.data(using .utf8)!
// Initialize the decoder
let decoder = JSONDecoder()
// Try to convert your JSON to objects
do {
let response = try decoder.decode(Response.self, from: jsonData)
print("Decoded successfully: \(response)")
// Access values as usual with a struct... well, this IS a Swift struct already
print("State: \(response.state)")
// etc...
} catch {
print("Error decoding: \(error)")
}
Check out Apple documentation here:
1) Encoding and Decoding Custom Types
2) JSONDecoder

Related

How to set an array's value from a JSON file?

I am building a SwiftUI app for iOS to help me and my classmates keep up with assignments.
The way I am syncing homework across devices is by pushing some JSON with the assignments into GitHub Pages and then the app parses the JSON code into a sleek and simple Section within a Form.
I would like to have the following JSON...
[
{
"subject": "Maths",
"content": "Page 142, exercises 4, 5, 6.",
"dueDate": "15/01/2022"
},
{
"subject": "English",
"content": "Write an essay about your favorite subject. 2 pages at least.",
"dueDate": "18/01/2022"
},
{
"subject": "Chemistry",
"content": "Learn every element from the Periodic Table.",
"dueDate": "16/01/2022"
}
]
... turn into something that looks like this:
The easiest way would be to create about 5 Sections and, if there aren't 5 assignments, leave them empty. This solution didn't work because not having 5 assignments in the JSON file means the function that handles the file would abort because JSONDecoder would return nil when unwrapping the 4th assignment.
I've been struggling for quite a while to find a solution. I tried this:
struct Assignments: Decodable {
let subject: [String]
let content: [String]
let dueDate: [String]
}
struct AssignmentView: View {
#State private var currentNumber = 0
#State private var numberOfAssignments = 0
#State private var subject = [""]
#State private var dueDate = [""]
#State private var content = [""]
func downloadAssignments() {
let assignemtURL = URL(string: "https://example.com/assignments.json")!
var assignmentRequest = URLRequest(url: assignmentURL)
assignmentRequest.httpMethod = "GET"
assignmentRequest.attribution = .developer
NSURLConnection.sendAsynchronousRequest(assignmentRequest, queue: OperationQueue.main) {(response, data, error) in
if error != nil {
print("Could not get assignments. :/")
} else {
guard let data = data else { return }
let assignmentJSON = "\(data)"
let jsonData = assignmentJSON.data(using: .utf8)!
let decodedAssignments: Assignments = try! JSONDecoder().decode(Assignments.self, from: jsonData)
let numberOfAssignments: Int = decodedAssignments.count
// Here comes the problem. How do I create an array / modify an existing array with each individual subject taken in order?
}
}
}
var body: some View {
let _ = downloadAssignments()
VStack {
Form {
Section {
ForEach(0..<numberOfAssignments) {
Text("\(subject[$0]) - Due \(dueDate[$0])")
.font(.system(size: 20, weight: .semibold))
Text("\(content[$0])")
}
}
}
}
}
}
}
How can I get the value of each variable from the JSON file and combine it into an array (example: var subjects = ["Maths", "English", "Chemistry"])?
I've been looking for an answer for weeks with no solution. Some help on this would be highly appreciated!
There are quite a few things to point out in your code. I'm afraid I can't present you a full solution, but these pointers might help you go into the right direction:
The structure of your Assignments model doesn't match the JSON (and is also not really easy to work with in the list). Instead of having a single model which has an array for each property, there should one model object per assignment
struct Assignment: Decodable {
let subject: String
let content: String
let dueDate: String
}
and then you can decode/store an array of those:
let decodedAssignments: [Assignment] = try! JSONDecoder().decode([Assignment].self, from: jsonData)
Triggering the download as a side-effect of the body being evaluated is not a good idea, because this does not happen only once. In fact, whenever you modify any state of the view it would re-trigger the download, basically leaving you in an infinite download-loop. Instead, a better place to start the download would be when the view appears:
var body: some View {
VStack {
// ...
}
.onAppear(perform: downloadAssignments)
}
NSURLConnection is an extreeeemly old API that you don't want to use nowadays (it has been officially deprecated for 6 years now!). Have a look at URLSession instead.

How to fill a single Model Object with multiple chained URL requests Request Dependency?

I have a data model object that I have to fill with multiple network requests.
My model looks like this
struct CO2Data: Identifiable, Codable {
let id = UUID()
let totalCO2: Double
let dailyAverage: Int
let calendar = Calendar.current
let dateString: String
let emissionStats: EmissionStats
let driveLog: String
// It is an ID to get the trip details
let trip : Trip
// This is what needs to be filled from the next URL request using driveLog
enum CodingKeys: String, CodingKey {
case id
case totalCO2 = "totCO2"
case dateString = "stDt"
case dailyAverage = "avg"
case emissionStats = "emisStats"
case drLogRefId = "drLogRefId"
//Not having case trip here breaks the conformance to codable
// and results in error
}
}
struct Trip: Codable {
let startTime: String
let endTime: String
let startLat: String
let startLong: String
let endLat: String
let endLong: String
}
This is the flow of the network request and responses
request to get the CO2Data and decode it using JSONDecoder()
use the driveLog Property in the fetched CO2Data to make another network request to get the Trip object.
The result of the second network request should be added to the CO2Data object in the trip property.
How should I go about this. Is it even possible to store the Trip in CO2 data?
I am using the Combine & URLSession.shared.dataTaskPublisher(for: URLRequest) for this, but I can totally use plain old simple data task.
Are there known ways to handle request dependencies like this?
I am not using any third party libraries and wish to stick with URLSession.
JSON Objects look like this
// for CO2Data
[
{
"stDt": "2019-11-17T16:00:00.000+0000",
"totCO2": 50,
"avg": 0,
"emisStats": {
"wea": 10,
"aggDr": 10,
"tfc": 10,
"tirePress": 0,
"ac": 10,
"seatHeater": 10
},
"drLogRefId": "5dc20204199f752c7726a8f0"
}
]
//for Trip
[
{
"id": "5dc20204199f752c7726a8f0",
"startTime": "...",
"startLat: "...",
"startLong: "...",
"endLat: "...",
"endLong": "...",
}
]
Here's how you can achieve your goal. You need to make Trip optional during the first network call as you don't have data to set there. Make sure handle this properly during json decoding.
Then use flatMap to make the second network call.
Use zip to wait for both requests to complete and pass result data to extension CO2Data.init.
Full Code where fetchCO2TripData will return CO2Data with Trip :
struct CO2Data: Identifiable, Codable {
let id = UUID()
/// TODO: add other properties
let driveLog: String
let trip : Trip?
}
struct Trip: Codable {
let startTime: String
let endTime: String
let startLat: String
let startLong: String
let endLat: String
let endLong: String
}
func fetchCO2Data() -> AnyPublisher<CO2Data, Error> {
/// TODO: Replace with actual network request
Empty().eraseToAnyPublisher()
}
func fetchTripData(by driveLog: String) -> AnyPublisher<Trip, Error> {
/// TODO: Replace with actual network request
Empty().eraseToAnyPublisher()
}
func fetchCO2TripData() -> AnyPublisher<CO2Data, Error> {
let co2Publisher = fetchCO2Data().share()
let tripPublisher = co2Publisher.flatMap { fetchTripData(by: $0.driveLog) }
return co2Publisher
.zip(tripPublisher)
.map { CO2Data(co2data: $0, trip: $1) }
.eraseToAnyPublisher()
}
extension CO2Data {
init(co2data: CO2Data, trip: Trip) {
self.driveLog = co2data.driveLog
self.trip = trip
}
}
P.S. I would rather recommend creating high-level model that will be composed of both network calls, to eliminate optional trip:
struct Model {
let co2Data: CO2Data
let trip: Trip
}
Depending on the complexity of the surrounding data look either into
Operations: You can specify other operations this operation is dependent on.
DispatchGroup: With DispatchGroup.enter(), DispatchGroup.leave() and finally DispatchGroup.notify() you can also model dependencies of dispatched tasks you perform.
Good luck.

How to remove optional text from json Result In swift

I am using newsApi to get list of news from it. I created model based on news's property, all property are optional in model and as i parse it printed to console getting result but all fields have data with optional text
I have created three struct based on news api fields, They are like
struct GoogleNews: Codable {
var status: String?
var totalResults: Int?
var articles: [Article]
}
struct Article: Codable {
var source: Source
var author: String?
var title: String?
var description: String?
var url: String?
var urlToImage: String?
var publishedAt: String?
var content: String?
}
struct Source: Codable {
var id: String?
var name: String?
}
Calling the appi
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let allNews = try JSONDecoder().decode(GoogleNews.self, from: data)
print(allNews.articles[0])
} catch let error {
print(error.localizedDescription)
}
}.resume()
After calling the api, in result all fields are having result with optional text
name: Optional("Venturebeat.com")), author: Optional("Dean Takahashi"), title: Optional("How Paymentwall’s Terminal3 lets game developers create their own online shops"), description: Optional("Paymentwall built a business as a global payments platform, with much of its focus on games. Last year, the company spun out its Terminal3 as a platform for monetizing and distributing games. Now it is making it easier for indie, small, and medium-size game c…")...ect
What should be the solution to remove the optional text from the results..
For the values in your struct that are optional, be sure they're optionals because you know for sure that there are cases where a value won't be returned. If you'd like to unwrap them, you have 2 ways of doing so.
The first way is to use an if-let statement, which would look something like this:
if let name = allNews.articles[0].name {
}
Within the curly braces is where you would use the variable name, which wouldn't be the optional value you're asking about because it's been unwrapped.
The second method you could use is a guard statement, which looks like this:
guard let name = allNews.articles[0].name else { return }
In this instance, the name variable would be unwrapped and can be used anywhere in the scope of your code. However, it's only valid if it can be successfully unwrapped. If it cannot, then the return statement is called and breaks out of whatever scope it's in.
The thing is you're trying to print an object which all properties are optionals. You have to unwrap the properties even if you unwrap the article . You can unwrap your optionals like this:
let article = allNews.articles[0]
if let source = article.source {
print(source)
}
Also you can unwrap more than one property at once:
if let source = article.source, let author = article.author, let title = article.title {
// do things
}
Just remove the sign of ? from the end of the Struct members declaration to become like this:
let author: String

Making sense of Realm, Moya, and ObjectMapper

So I'm trying to make sense of how to use Realm, Moya, and ObjectMapper.
I use Moya to make requests to my API. I use Realm to keep the returned data in a local database. And I use ObjectMapper to map the JSON objects to correct Realm variable.
However, I've come to an issue now where I'm not sure how to decode the JSON response in order to put it through the mapper.
Here is my Moya code:
provider.request(.signIn(email: email, password: password)) { result in
switch result {
case let .success(response):
do {
// Get the response data
let data = try JSONDecoder().decode(MyResponse.self, from: response.data)
// Get the response status code
let statusCode = response.statusCode
// Check the status code
if (statusCode == 200) {
// Do stuff
}
} catch {
print(error)
}
case let .failure(error):
print(error)
break
}
}
The error happens on this line:
In argument type 'MyResponse.Type', 'MyResponse' does not conform to expected type 'Decodable'
The MyResponse class looks like this:
class MyResponse: Object, Mappable {
#objc dynamic var success = false
#objc dynamic var data: MyResponseData? = nil
required convenience init?(map: Map) {
self.init()
}
func mapping(map: Map) {
}
}
I understand why I'm getting that error, I just don't know the correct way to solving it. Am I missing something in the documentation of one of the above frameworks? Am I doing this totally wrong? How should I fix my line of code?
I've tried #Kamran's solution, but I got the error:
Argument labels '(JSON:)' do not match any available overloads
On the line:
let myResponse = MyResponse(JSON: json)
You are getting that error because you are decoding using Swift JSONDecoder which requires you to implement Codable which wraps Encodable and Decodable (JSON <-> YourObject).
If you're using Swift 4 you can use Codable instead of relying on 3rd party libraries.
MyResponse would become:
class MyResponse: Codable {
let success: Bool
let data: MyResponseData?
}
And MyResponseData should implement Codable too.
After this you should be able to do:
do {
let data = try JSONDecoder().decode(MyResponse.self, from: response.data)
} catch let error {
// handle error
}

How can I fix this error " AnyObject? is not convertible to 'string'"? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
When I try to connect the database by using external databases with an API on my application, I get an error in loadPostsfunction.
Error:
AnyObject is not convertible to String
Code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
service = PostService()
service.getPosts(){
(response) in
self.loadPosts(response["posts"]! as! NSArray)
}
}
/////////////////// In here ///////////////////////
func loadPosts(posts:NSArray) {
for post in posts {
var id = (post["Post"]!["id"]! as! String).toInt()!
var title = post["Post"]!["title"]! as String
var author = post["Post"]!["author"]! as String
var content = post["Post"]!["content"]! as String
}
}
////////////////////////////////////////////////////
//Link DB
var postsCollection = [Post]()
var service:PostService!
}
Any ideas please?
AnyObject is not convertible to String.
You should let it knows post["Post"] is NSDictionary or what type you define it.
func loadPosts(posts:NSArray) {
for post in posts {
var id = ((post["Post"]! as? NSDictionary)!["id"]! as! String).toInt()!
var title = (post["Post"]! as? NSDictionary)!["title"]! as String
var author = (post["Post"]! as? NSDictionary)!["author"]! as String
var content = (post["Post"]! as? NSDictionary)!["content"]! as String
}
}
There are multiple issues with your code, I will try to teach you how you can make it better so that you no longer receive the compile error, and you better handler various points of failure.
First things first, let's Swiftify your code and make use of the type system:
self.loadPosts(response["posts"]! as! NSArray)
can be better written as
if let postsDicts = response["posts"] as? [[String:AnyObject]] {
loadPosts(postsDicts)
} else {
// the server response didn't send the proper "posts"
}
, the optional binding allowing you to handle the scenario where the server sent an invalid response.
Secondly, let's specify loadPosts to accept an array of dictionaries (exactly the type to which we casted above):
func loadPosts(posts: [[String:AnyObject]])
And not lastly, all that forced unwraps (!) can make your app crash without thinking twice. The problem is that you can't control which data will the server send - a bug in a server can cause your application to crash if you blindly trust it's data, so you need to add safeguards to avoid app crashes due to invalid data:
func loadPosts(posts: [[String:AnyObject]]) {
for post in posts {
guard let postDetail = post["Post"] as? [String:AnyObject],
id = postDetail["id"] as? String,
title = postDetail["title"] as? String,
author = postDetail["author"] as? String,
content = postDetail["content"] as? String else {
continue
}
// I assume here you would construct a Post object
}
}
Keep in mind that NSArray can be safely caster to a Swift array (e.g [String], or as in your case [[String:AnyObject]], similarly a NSDictionary can be safely casted to a Swift dictionary. Working with Swift's data types give you more flexibility and can help you write less and more robust code.

Resources