Store JSON data in NSUserDefaults Swift 2 - json

I'm new to coding. I have JSON request which returns different parameters, for example "unitid" and "buldingid".
i want to store the "buldingid" by NSUserDfaults. I use code below for storing it, but every time I get nil response for "buldingid".
reading:
NSUserDefaults.standardUserDefaults().setObject(buildingid, forKey: "buildingidKey")
NSUserDefaults.standardUserDefaults().synchronize()
writing:
let buildingid: [NSString]? = NSUserDefaults.standardUserDefaults().objectForKey("buildingidKey") as? [NSString]
i saved it in a label then stored in NSUserDfaults but it doesn't work (error: Attempt to insert non-property list object)
what should I do to get the correct response for "buldingid"?
Thanks!

I suppose from your code that buildingid is a String, so something like this should work:
NSUserDefaults.standardUserDefaults().setObject(String(buildingid), forKey: "buildingidKey")
NSUserDefaults.standardUserDefaults().synchronize()
Retrieving it should be done like this:
let buildingid = NSUserDefaults.standardUserDefaults().stringForKey("buildingidKey")

Related

Swift Decodable - Is it possible to read part of a tree as a string rather than a data structure?

Here's my problem. Let's say I have a JSON structure that I'm reading using Swift's Codable API. What I want to do is not decode part of the JSON but read it as a string even though it's valid JSON.
In a playground I'm messing about with this code:
import Foundation
let json = #"""
{
"abc": 123,
"def": {
"xyz": "hello world!"
}
}
"""#
struct X: Decodable {
let abc: Int
let def: String
enum CodingKeys: String, CodingKey {
case abc
case def
}
init(decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
abc = try container.decode(Int.self, forKey: .abc)
var defContainer = try container.nestedUnkeyedContainer(forKey: .def)
def = try defContainer.decode(String.self)
// def = try container.decode(String.self, forKey: .def)
}
}
let x = try JSONDecoder().decode(X.self, from: json.data(using: .utf8)!)
Essentially I'm trying to read the def structure as a string instead of a dictionary.
Any clues?
If the resulting string doesn't need to be identical to the corresponding text in the JSON file (i.e. preserve whatever white space is there, etc.), just decode the entire JSON, and then encode the part that you want as a string, and construct a string from the resulting data.
If you do want to preserve exactly the text in the original JSON, including white space, then you'll do better to get that string some other way. Foundation's Scanner class makes it pretty easy to look for some starting token (e.g. "def:") and then read as much data as you want. So consider decoding the JSON in one step, and then separately using a Scanner to dig through the same input data to get the string you need.
Definitely not using JSONDecoder. By the time init(from:) is called, the underlying data has already been thrown away. However you do it, you'll need to parse the JSON yourself. This isn't as hard as it sounds. For example, to extract this string, you could use JSONScanner, which is a few hundred lines of code that you can adjust as you like. With that, you can do things like:
let scanner = JSONScanner()
let string = try scanner.extractData(from: Data(json.utf8), forPath: ["def"])
print(String(data: string, encoding: .utf8)!)
And that will print out:
{
"xyz": "hello world!"
}
(Note that RNAJSON is a sandbox framework of mine. It's not a production-ready framework, but it does a lot of interesting JSON things.)
Integrating this into a system that decodes this in a "Decoder-like" way is definitely buildable along these lines, but there's no simple answer down that path. Caleb's suggestion of re-encoding the data into a JSON string is definitely the easiest way.
Using RNAJSON again, there's a type called JSONValue that you can use like this:
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
abc = try container.decode(Int.self, forKey: .abc)
let defJSON = try container.decode(JSONValue.self, forKey: .def)
def = String(data: try JSONEncoder().encode(defJSON), encoding: .utf8)!
}
This will make def be a JSON string, but it doesn't promise that key order is maintained or that whitespace is preserved.
Thanks everyone. It's an interesting problem. Not so much in the rather simple example I gave, but in the actual problem I'm thinking about I want to include mustache templating inside the text I'm decoding (which by the way could be JSON or YAML). The top level parts of the data are fixed in that they have pre-defined keys so reading them is fine. but there is a point in the data where the developer generating it can pretty much include any data structure they like, and that data structure can include mustache keys. ie. abc: {{some-value}} which of course, makes no sense to a decoder.
There's lots of ways this could go wrong but I'm thinking that really I would need to run mustache across the entire data structure before decoding it, where as currently I'm decoding then running mustache and to do that I'm already setting the unknown part as a string and reading it as such. I'm contemplating the idea that it would be much nicer if it was just JSON/YAML rather than a string containing JSON/YAML. But it might not be possible :-)

JSON cannot get data into SQLite3 table

I have seen quite a few posts on this question and none seem to answer my question. They are usually far more complicated than what I am asking. I have a list of one or more stocks that I would like to get the latest price for from the Internet. I found a nice API for doing that and was excited to create a simple structure and do the basics of a URLSession. I was able to pull the data into my structured format and print it in the console no problem. For example:
Quote(symbol: "C", companyName: "Citigroup Inc.", latestPrice: 68.86)
I want to update my SQLite3 database records with the current price. I cannot get the data out.
So I have my opentrades array based on a model:
class OpenTradeDur: NSObject {
#objc dynamic var trNo: Int
#objc dynamic var trPDate: String
#objc dynamic var trTicker: String
#objc dynamic var trDur: String
#objc dynamic var trCurPrice: Double
and the Quote structure is simple:
struct Quote: Decodable {
let symbol: String
let companyName: String
let latestPrice: Double
}
I have tried multiple variations of functions and actions and variables but my latest attempt that is still no closer to working is running in my viewDidLoad of my ViewController where I do some initial setup:
for _ in opentrades {
rn = rn + 1
let url = URL(string: "https://api.iextrading.com/1.0/stock/\(opentrades[rn].trTicker)/quote")
let session = URLSession.shared
let task = session.dataTask(with: url!) {(data, response, error) in
guard let data = data else { return }
do {
let quote = try JSONDecoder.decode(Quote.self,from: data)
print(quote)
self.opentrades[rn].trCurPrice = quote.latestPrice
} catch {}
}
task.resume()
}
I found a few posts that talk about completion handlers but even in those examples only give an answer that ends up printing to console. I just need a simple implementation. What I really need is a function that I can just call and say:
price = getPrice(stockTicker)
eg. price = getPrice("C")
and what that is doing behind the scenes if getting the JSON data from
https://api.iextrading.com/1.0/stock/C/quote
Can anyone point me in the right direction?
[2018-10-25]
I have managed to get this working in a fashion by creating a delegate protocol in the class I have performing the URLSession and then extending my ViewController with the delegate. And this works for a single quote at a time. I can initiate the process with a button and when the data comes back through the delegate's didLoad function I have it update the screen. But I was looking for a backend process to make multiple calls to the quote api and update multiple records. But I could not get that to synch up. But I had a thought and went back to the API documentation found a batch call where I can return all of the data for multiple stocks in one call. That is exactly what I need however I am stuck once again. The returned data is JSON data but not jsonapi.org compliant. I am going to post this as a new question and try hard to make sense in my asking of the question.
Edit: I received a great response from my new question and it has solved the problem I was experiencing here so I will provide an update.
As I tried to explain previously, my application runs through a list of open stock trades at startup and wants to update the latest price for each of them. I have an array I run through to set the price and then update my database to save those changes. Then my app opens with the new data in place.
I now have a function that takes a stock symbol as an input and returns the price immediately...
func getPrice(ticker: String) -> Double {
var price = 0.0
let urlString = "https://api.iextrading.com/1.0/stock/\(ticker)/quote"
let data = try! Data(contentsOf: URL(string: urlString)!)
do {
let response = try JSONDecoder().decode(Quote.self,from: data)
price = response.latestPrice
} catch let jsonErr { print("Error decoding JSON:",jsonErr)}
return price
}
I simply run through my array of trades and set the price...
opentrades[rn].trCurPrice = getPrice(ticker: opentrades[rn].trTicker)
I will be testing this to see how it works out without using a URLSession especially during the trading day and times of high activity and potential network latency.

Swift 3 Dictionary JSON

in school we're creating a program to track Sailing Races.
I'm currently developing the GPS Tracker App for the sailing teams.
The JSON to send to our Api must look like this:
{"hash":"asdh832","positions":[{"longitude":13.340532999999999,"latitude":52.4965431,"time":1488182463461},{"longitude":13.3175489,"latitude":52.4927039,"time":1488195535705},{"longitude":13.3175489,"latitude":52.4927039,"time":1488195536657}]}
First the Hash for the Team and positions in a Array(if the smartphone doesn't have a internet connection atm to send them later)
I have a "positions" Array Dictionary:
var positions = Array<Dictionary<String,String>>()
positions.append( ["longitude":String(location.coordinate.longitude),
"latitude":String(location.coordinate.latitude),
"time":String(Int64(location.timestamp.timeIntervalSince1970 * 1000.0))])
// Times 2 to test
positions.append( ["longitude":String(location.coordinate.longitude),
"latitude":String(location.coordinate.latitude),
"time":String(Int64(location.timestamp.timeIntervalSince1970 * 1000.0))])
let data2 = try JSONSerialization.data(withJSONObject: positions, options: [])
let dataString2 = String(data: data2,encoding: String.Encoding.utf8)!
print(dataString2)
The Result of print(dataString2) is:
[{"latitude":"52.4965211222075","longitude":"13.3405919673345","time":"1488194768467"},{"latitude":"52.4965211222075","longitude":"13.3405919673345","time":"1488194768467"}]
which is correct. Now I want to combine it with a the Hash:
let params: Dictionary<String, String> = ["hash":"asdh832","positions": dataString2]
let data = try JSONSerialization.data(withJSONObject: params , options: [])
let dataString = String(data: data,encoding: String.Encoding.utf8)!
but the now the result after "positions:" looks kinda weird:
{"hash":"asdh832","positions":"[{\"latitude\":\"52.4966040328522\",\"longitude\":\"13.3402242104124\",\"time\":\"1488195406482\"},{\"latitude\":\"52.4966040328522\",\"longitude\":\"13.3402242104124\",\"time\":\"1488195406482\"}]"}
without these extra " and \ it would be correct but I just don't know how to build it like this.
I'm using Swift 3 with Xcode 8.2.1
Make JSON string at last means don't create JSON string from Array positions instead of that set that Array with your params dictionary with key positions instead of setting string.
var positions = Array<Dictionary<String,String>>()
positions.append(["longitude":String(location.coordinate.longitude),
"latitude":String(location.coordinate.latitude),
"time":String(Int64(location.timestamp.timeIntervalSince1970 * 1000.0))])
positions.append(["longitude":String(location.coordinate.longitude),
"latitude":String(location.coordinate.latitude),
"time":String(Int64(location.timestamp.timeIntervalSince1970 * 1000.0))])
//Now set this positions array with positions key in params dictionary
let params: Dictionary<String, Any> = ["hash":"asdh832","positions": positions]
let data = try JSONSerialization.data(withJSONObject: params , options: [])
let dataString = String(data: data,encoding: String.Encoding.utf8)!
The Result of print(dataString2) is: [...] which is correct.
No.
You're serialising latitude, longitude and timestamp as strings, while they should be numbers (notice the quotes).
[...] but the now the result after "positions:" looks kinda weird
That's because you double serialise dataString2.
Do not call JSONSerialization.data twice but create one big Dictionary<String, Any> that contains all structured data and then perform the serialisation in one call.

having trouble drilling down into response json using alamofire. Swift

Ok so im having trouble drilling down into the response json that is retrieved in the below code.
Snapshot 1:
Snapshot 2:
I get the json above as a response, yet when I try to drill down into the json I cannot access the key "Address" or any other key that is held under bookings. it finds bookings but then assigns the variables i created for each key's value (address, state, zipcode, and city) and assigns these variables as null. My only theory is that the response json has "()" right after bookings instead of the normal "{}". Those brakets do appear after the parentheses, so how would i drill into that with out a key? I am still new to the app dev world so i could be completely wrong, just this request method has worked for me in the past when i've received "{}" after the first key.
1) You are using method valueForKeyPath() to get value from dictionary.
Change it to valueForKey().
2) Value for key "bookings" will give you array.
Try this code.
let data = JSON as! NSDictionary;
//value for key "bookings" will give you array
if let bookings = data.valueForKey("bookings") as? NSArray {
let bookingObj = bookings[0] as! NSDictionary;
self.address = bookingObj.valueForKey("address");
self.state = bookingObj.valueForKey("state");
self.zip = bookingObj.valueForKey("zip");
self.city = bookingObj.valueForKey("city");
}
FYI Please Note that, method valueForKeyPath() returns array of value for particular key from the array of dictionaries.
e.g. suppose you have multiple objects of bookings array, and you use
let arrayAddresses = bookings.valueForKeyPath("address") as! NSArray
this will return string array containing only address of all object.
Another point, '()' means Array Value, '{}' means Dictionary value.
I'd suggest using Swift types (like [String: AnyObject]) instead of Foundation types (like NSDictionary), and on top of that, you should print out when something fails so you know what's wrong. So starting from your let data = ... line, try this instead:
if let data = JSON as? [String: AnyObject],
bookings = data["bookings"] as? [[String: AnyObject]],
booking = bookings[0] {
self.address = booking["address"] as? String
self.state = booking["state"] as? String
self.zipCode = booking["zip"] as? String
self.city = booking["city"] as? String
print(self.address)
} else {
print("Couldn't parse booking from JSON")
}
More on why typing is important (and why you should use [[String: AnyObject]] instead of NSArray) in this article. You can be more specific and more concise if you use proper types.

Parsing JSON with SwiftyJSON

I'm having trouble parsing the following JSON file with SwiftyJSON. I've looked around the web and tried different suggested solutions with no luck.
Here is the JSON:
{'info-leag':{'Status':1,'Name':'Testing Name','url-lig':'test.testing.com','uid':'12345'}}
And my relevant code:
//initializes request
let request = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.currentQueue()) { response, maybeData, error in
if let data = maybeData {
let json = JSON(data: data)
//stores data as UTF8 String
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
The first part seems to work fine, I am able to get the JSON and save it as data, at the bottom I converted it to a string to make sure that I was getting the right information, I then later print it to make sure.
I tried different things like:
let name = json["info-league"]["Name"] //can't seem to get the context
I'm trying to get the Name and uid to be saved as 2 strings as well as the Status as an int.
Thanks!
Once you've made your JSON valid like this:
{"info-league":{"Status":1,"Name":"Testing Name","url-lig":"test.testing.com","uid":"12345"}}
you will be able to use your example, it works (I just tested):
let name = json["info-league"]["Name"]
but it's better to use SwiftyJSON types:
let name = json["info-league"]["Name"].string
let status = json["info-league"]["Status"].int
so your variables are of known types for later use.
If you don't do this they will be of type JSON, a type created by SwiftyJSON, and you will have to cast them later (not a problem, depends how you're organised in your code).
Try:
let name = json["info-league"]["Name"].string