My function to parse an array of properties to a json object looks like this:
var jsonObject = "[{"
for i in 1...table.count {
var str = ""
var insideStr = String()
for property in properties {
let filteredTable = table.value(forKey: property) as! [Any]
insideStr += "\"\(property)\": \"\(filteredTable[i - 2])\","
}
let index = insideStr.characters.index(insideStr.startIndex, offsetBy: (insideStr.count - 2))
insideStr = String(insideStr[...index])
str += "\(insideStr)}],"
jsonObject.append(str)
}
let index = jsonObject.characters.index(jsonObject.startIndex, offsetBy: (jsonObject.count - 2))
jsonObject = "\(String(jsonObject[...index]))"
return jsonObject
}catch let error { print("\(error)") }
My question is, is it possible to skip the first property because it always returns nil data?
You could delete the first property:
delete obj[Object.keys(obj)[0]];
otherwise slice like this:
Object.entries(obj).slice(1); //array of arrays [key,value], without the first.
Replace your for loop with a for...in style loop, and then add a where clause to exclude nils since you don't want to risk dealing with them.
for tableItem in table where tableItem != nil {
// Handle json here
}
Or, you can use higher order functions to remove all the nils, and then iterate through it like this:
table.filter({ $0 != nil }).forEach({ tableItem in // Do stuff with tableItem })
Related
I know how to make a synchronous request with completions. But I don't know how to make synchronous requests in a loop.
Here is my code:
var marks = [JSON]()
let vnCount = studentVnCodes.count
var i: Int = 0
marks = [JSON](repeating: JSON.null, count: vnCount)
for vn in studentVnCodes {
let url = "https://example.com/Student/Grade/GetFinalGrades?&vn=\(vn)&academic_year=All"
Alamofire.request(url).responseString { response in
var dataString: String = (response.result.value)!
dataString = cleanMarksJSON(string: dataString)
if let dict = convertToDictionary(text: dataString) {
marks[i] = (JSON(dict as Any))
i += 1
if (vnCount == marks.count) {
completionHandler(marks)
}
}
}
}
Here I'm trying to make x requests with the number of vn codes (vnCount).
The issue is that I get all the JSON in a wrong order in my array of JSON marks. Certainly because it appends responses in the array when it's finished and don't wait the previous request to be ended.
So I tried to create a variable i to force the function to append responses in the right order. That's not working. Any idea? Thanks!
You can run your requests sequentially in a serial queue, in which case they will be executed in the order you call them, which ensures they will be added to the array in order. However, this seems like a suboptimal solution, since you lose execution time by running your requests sequentially instead of concurrently.
If you still want to implement it like this, see the code below:
var marks = [JSON]()
let vnCount = studentVnCodes.count
marks = [JSON](repeating: JSON.null, count: vnCount)
let serialQueue = DispatchQueue(label: "serialQueue")
for vn in studentVnCodes {
serialQueue.async{
let url = "https://example.com/Student/Grade/GetFinalGrades?&vn=\(vn)&academic_year=All"
Alamofire.request(url).responseString { response in
var dataString: String = (response.result.value)!
dataString = cleanMarksJSON(string: dataString)
if let dict = convertToDictionary(text: dataString) {
marks.append(JSON(dict as Any))
if (vnCount == marks.count) {
completionHandler(marks)
}
}
}
}
}
A better solution would be to store the response in a data structure, where ordering doesn't matter, for example in a dictionary, where your keys are the indexes (which you would use for an array) and your values are the JSON response values. This way you can run the requests concurrently and access the responses in order.
I am getting data from a URL and it is coming back in Json . What I am trying to do is color a certain button Blue if a particular Json column does not contain null or Nil . This is My Json
{"votes":"0","vote_status":null},{"votes":"1","vote_status":"11"}
as you can see the field vote_status returns as a String however if the value is null then it doesn't have any quotation marks around it . How can I check for Null values in my code
// This will store all the vote_status values
var VoteStatus = [String]()
// captures the value
if var vote_Status = Stream["vote_status"] as? String {
self.VoteStatus.append(vote_Status)
}
However I get an error fatal error: Index out of range
Which I am positive it is because the NuLL values does not have any strings . Is there a way I can check for NULL values and change them to something like "null" ? I have tried doing it this way
if var voteStatus = Stream["vote_status"] as? String {
if vote_Status == nil {
vote_Status = "null"
}
self.VoteStatus.append(vote_Status)
}
and it states that comparing non-optional value of type String to nil is always false . The code above compiles but gives an error on Runtime . I am new to Swift but any suggestions would be great..
The reason you're getting that compiletime error is that if this passes: if var voteStatus = Stream["vote_status"] as? String { then that is a guarantee that Stream["vote_status"] is a non-nil String value. If you want to do something different if that IS a nil, then just put an else statement:
if var voteStatus = Stream["vote_status"] as? String {
//Do whatever you want with a guaranteed, non-nil String
} else {
//It's nil
}
If you also want to treat the string "null" as a nil value, you can add one little bit:
if var voteStatus = Stream["vote_status"] as? String, voteStatus != "null" {
//Do whatever you want with a guaranteed, non-nil, non-"null" String
} else {
//It's nil or "null"
}
The index out of range error is likely caused by something that we're not seeing in your code. Is Stream itself an optional? In your second example are you forgetting to initialize your voteStatus array?
Here I'm trying to get currency values of INR for last 30 dates.
I'm fetching last 30 dates values of INR currency using Alamofire.
//strDates contains all 30 days dates
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
}
}
print(sValues) //Print nil
setChart(dataPoints: strDates, values: sValues)
How do I use this sValues array outside the Alamofire block.
Here, Actually I'm sending dates & INR values as a parameter to below method.
func setChart(dataPoints: [String], values: [Double]) {
barChartView.noDataText = "You need to provide data for the chart."
for i in 0..<dataPoints.count {
let dataEntry = BarChartDataEntry(x: Double(i), yValues: [values[i]])
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(values: dataEntries, label: "INR Rates(₹)/$")
let chartData = BarChartData(dataSet: chartDataSet)
barChartView.data = chartData
barChartView.xAxis.labelPosition = .bottom
barChartView.rightAxis.enabled = false
barChartView.leftAxis.enabled = true
barChartView.data?.setDrawValues(false)
barChartView.leftAxis.granularityEnabled = true
barChartView.leftAxis.granularity = 1.0
barChartView.xAxis.granularityEnabled = true
barChartView.xAxis.granularity = 1.0
barChartView.leftAxis.axisMinimum = 70//65
barChartView.leftAxis.axisMaximum = 60//70
//chartDataSet.colors = [UIColor.cyan, UIColor.green]
}
Are you looking for the chart to refresh when only when ALL of the calls are done? If so you use a pattern like this:
var completedCalls = 0
for i in 0..<strDates.count {
Alamofire.request("http://api.fixer.io/\(strDates[i])?base=USD").responseJSON { response in
if let arr = response.result.value as? [String:AnyObject]
{
completedCalls += 1
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
if completedCalls = strDates.count {
DispatchQueue.main {
setChart(dataPoints: strDates, values: sValues)
}
}
}
}
}
The idea is to count how many API requests come back and only take action when all of the requests are done (you should check that all of them actually succeeded as well and show an error if any fail).
Your codes cause sValues to print nil because your print(sValues) ran before your request response. This block
if let arr = response.result.value as? [String:AnyObject]
{
let inrc = (arr["rates"]?["INR"] as? Double)!
print(inrc)
self.sValues.append(inc)
print(sValues)
//It prints values here.
}
runs only when your request returns with a value or an error. So any usage of values from your request should also be done here. If you want to use the value to update certain UI element or database, you should also call the method from within this block to ensure that you already retrieved the value from its source.
I do suggest you adopt this mentality when programming any asynchronous codes as you can never expect when your codes running in another thread or the server to return. It is always safer to run you methods that requires the return value in a completion block that runs only when the asynchronous codes have completed.
If you're displaying them in a table, create a strong variable of type Array, or some other data provider (e.g. CoreData, SQLite) and store the results there. In the completion block for AF, set the value of the Array variable (or update your local data provider) to the API result data, then call reloadData() on your table. You'll also need to configure the UITableView delegate and datasource methods.
We fetch some JSON data using a REST protocol like this.
jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
Which looks like this:
jsonResult: (
{
board = "[[\"1:\",\"Y\",\"U\",\"P\"]]";
})
From this we get a game board like so:
if let boardContentArray = jsonResult[0]["board"] as NSArray?{
print("boardContentArray: \(boardContentArray)" )
} else {
print("board element is not an NSArray")
}
The boardContentArray looks like this: It i supposed to be a 2D array with only one row and four columns at the moment, but it should should work for any given size.
[["1:","Y","U","P"]]
How can you retrieve the individual values of boardFromRemote. I imagine to get the element at 0,0 in the 2D array some way like this:
boardContentArray[0][0]
This should then return "1:", which is not the case. This exact syntax is incorrect and won't compile. What is the correct way to retrieve an element from the boardContentArray variable?
The content of jsonResult[0]["board"] is a JSON String which can be decoded as an array with NSJSONSerialization. You have to first transform the String to NSData, then decode it like this for example:
do {
let boardContentArray = "[[\"1:\",\"Y\",\"U\",\"P\"]]" // the String from jsonResult[0]["board"]
if let boardData = boardContentArray.dataUsingEncoding(NSUTF8StringEncoding),
let boardArray = try NSJSONSerialization.JSONObjectWithData(boardData, options: []) as? [[String]] {
print(boardArray[0]) // ["1:", "Y", "U", "P"]
}
} catch let error as NSError {
print(error)
}
My JScript is:
var t={'color':'red'}; // dynamic json data.
for(n in t)
{
alert(n)
}
here, alert gives the json key color. but how to get its value?
Note: the json is dynamic.
var t={'color':'red'}; // dynamic json data.
for(n in t)
{
alert(n);// n = key
var val =t[n];// value where key is n
}
Here is a simple example to get dynamic keys from json response - Get dynamic keys from JSON Data
public void getData(String data){
// Load json data and display
JSONObject jsonData = new JSONObject(data);
// Use loop to get keys from your response
Iterator itr = jsonData.keys();
while(itr.hasNext()){
String keys = (String)itr.next();
Log.e("Keys", "----"+keys);
JSONArray dynamicValue = jsonData.getJSONArray(keys);
// Your stuff here
} }
var t={'color':'red'}; // dynamic json data.
for(n in t)
{
alert(t[n])
}
instead of putting the n in al alert put it in an external variable or something...
Edited, try sometnihg like this:
var ex_n;
var t={'color':'red'};
for(var i=0; i<t.length; i++) ex_n = t[i]["color"];