parse json, populate collection view Swift 4.2 - json

This is too simple but I am lost. I am still new to swift really.
I need to parse the downloaded json ( localized file in the Xcode project ) and populate the data to a CollectionView.
enum Response{
case success(Data)
case error(Error)
}
// struct follows the json
struct InformationFromJson: Decodable {
let id: Int
let name: String
}
class MYJSON {
public func downloadMYJSON(_ completion: #escaping (Response) -> ()) {
guard let bundle = Bundle(identifier: MYJSON.bundleId), let path = bundle.path(forResource: "data", ofType: "json"), let data = try? Data(contentsOf: URL(fileURLWithPath: path)) else {
completion(Response.error(NSError(domain: MYJSON.bundleId, code: MYJSON.bundleErrorCode, userInfo: [NSLocalizedDescriptionKey : MYJSON.bundleError])))
return
}
completion(Response.success(data))
}
}
So, without totally changing the function call, how do I parse the json? It's downloaded so far from the function, but I don't see how to even add a print statement to test, without getting errors because of the guard statement , the way it is.
I need to simple populate a cellForRowAt:
I never saw nested guard like this, so it got me. I am used to seeing the let statements separated so you can put print statements to at least see if things are getting downloaded or parsed.

You can decode your json by passing data, whatever you get from
let data = try? Data(contentsOf: URL(fileURLWithPath: path))
guard let decoded = try? JSONDecoder().decode(InformationFromJson.self, from: data) else {
return
}

Related

How to keep certain changed properties unchanged in Realm migration - Swift

When you have a Realm model that looks something like:
class Thing: Object, Decodable {
#objc dynamic var id: String = ""
#objc dynamic var propertyOne: String = ""
#objc dynamic var propertyTwo: String? = nil
override static func primaryKey() -> String? {
return "id"
}
}
All of the data is from a JSON file, using a JSON Serializer:
func serialize(input sourceName: String) {
let path = Bundle.main.path(forResource: sourceName, ofType: nil)
let url = URL(fileURLWithPath: path!)
let jsonDecoder = JSONDecoder()
do {
let data = try Data(contentsOf: url)
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
guard json is [AnyObject] else {
assert(false, "failed to parse")
return
}
do {
let things = try jsonDecoder.decode([Thing].self, from: data)
let realm = try! Realm()
for thing in things {
try! realm.write {
realm.add(thing)
// realm.add(thing, update: .modified) wipes out all changes
}
}
} catch let error {
print("failed to convert data: \(error)")
}
} catch let error {
print(error)
}
}
propertyOne's info is gotten from the JSON. propertyTwo's value is meant to be inputted from within the app. Once I serialize the JSON again, all changes to propertyTwo are wiped out. How do I make a migration without restoring it to the original JSON file? I want to keep some properties unchanged during a migration/new serialization.
In my AppDelegate, this is my code in didFinishLaunchingWithOptions:
let serializer = JSONSerializer()
serializer.serialize(input: "Nafliah.json")
This works if I have realm.add(thing, update: .modified) in my serializer. But this wipes out all changes made within the app. If I change it to this:
if save.bool(forKey: "serilized") == false {
let serializer = JSONSerializer()
serializer.serialize(input: "Nafliah.json")
save.set(true, forKey: "serilized")
}
And make the serializer part realm.add(thing), it works and does not wipe out changed data every time the app is opened.
The issue is, once I add a new object to the JSON file, it obviously does not get added to the realm. I must serialize the JSON again. This, however, deletes all changes done in the 2 properties that are only meant to be changed by the user in the app.
I figured it out. You have to do a partial serialization.
Changed this:
realm.add(thing, update: .modified)
To:
realm.create(Thing.self, value: [
"id": thing.id,
"propertyOne": thing.propertyOne
// Leave out propertyTwo to leave it unchanged
], update: .modified)
Please check below link for Migrating Realm for existing properties
https://docs.realm.io/sync/v/3.x/using-synced-realms/migrating-your-data

Parsing a local JSON file in Swift to display in table (Type ANY does not conform to protocol sequence)

I know an array of similar questions have been asked and I have looked at, but they don't seem to directly answer what I'm trying to achieve. I have a local JSON file in my directory with a structure similar to this: (it's more like an array than json)
"["countrycode1", "country1", "countrycode2", "country2"]" // and so forth
I am trying to achieve some sort of collection where the structure can be either be a multi array with the country and codes paired up, or a key value pair collection with the country code as the key and country as the value. The end goal is populating a table with these values with each countrycode and country occupying one row of the table.
Now, I have loaded the json file into my directory and run the following code:
func parseJSONData(filename: String){
if let path = Bundle.main.path(forResource: filename, ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
for elements in jsonResult{
print(elements)
}
} catch {
print(error)
}
}
}
When trying to loop through the array, I get the error Type 'Any' does not conform to protocol 'Sequence. How can I loop through the jsonResult to restructure the collection properly or is there a better way to achieve what I'm trying? Any help appreciated! Thanks
The result of jsonObject(with: is Any, you have to conditionally downcast the type
func parseJSONData(filename: String) {
if let url = Bundle.main.url(forResource: filename, withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let jsonResult = try JSONSerialization.jsonObject(with: data)
if let jsonArray = jsonResult as? [String] { // object is an array of strings
for element in jsonArray {
print(element)
}
} else if let jsonDictionary = jsonResult as? [String:String] { // object is a dictionary with String keys and values
for (key, value) in jsonDictionary {
print(key, value)
}
}
} catch {
print(error)
}
}
}
Notes:
There are APIs in Bundle to get URLs
In Swift don't use any .mutable... option in jsonObject(with
I hope it helps you:
let array = jsonResult as? [Any] ?? []
for elements in array {
print(elements)
}

Swift: How to use response from SocketIO emitWithAck method

I'm using the SocketIO library to connect my iOS app to my server.
I want to emit some data to the server and get a json dictionary back in the acknowledgment. I currently have something like this:
SocketHandler.mySocket.emitWithAck("my_event", [session, someInput]).timingOut(after: 3) {data in
let myData = try? JSONDecoder().decode(myStruct.self, from: data)
MyStruct is defined as Class inheriting from Decodable and resembles the structure of the json I expect.
I get the following error: Cannot convert value of type 'Any' to expected argument type 'Data'
Any idea how I can tackle that type casting? Or would I need to go a totally other route?
(Swift 4.1 for iOS 11.3)
Cheers!
If anyone else is wondering how to use SocketIO with Decodable, I created a little extension for the client to accept Decodable in the callback, based on Dan Karbayev's answer.
import Foundation
import SocketIO
extension Decodable {
init(from any: Any) throws {
let data = try JSONSerialization.data(withJSONObject: any)
self = try JSONDecoder().decode(Self.self, from: data)
}
}
extension SocketIOClient {
func on<T: Decodable>(_ event: String, callback: #escaping (T)-> Void) {
self.on(event) { (data, _) in
guard !data.isEmpty else {
print("[SocketIO] \(event) data empty")
return
}
guard let decoded = try? T(from: data[0]) else {
print("[SocketIO] \(event) data \(data) cannot be decoded to \(T.self)")
return
}
callback(decoded)
}
}
}
Usage:
socket.on("location") { (data: LocationEventData) in
// ...
}
socket.on("success") { (name: String) in
// ...
}
Where LocationEventData and String are Decodable.
There're two things:
decode(_:from:) accepts a Data as a second parameter. To be able to decode from Any you'll need to add an extension to first serialize the data and then pass it to JSONDecoder, like this:
extension Decodable {
init(from any: Any) throws {
let data = try JSONSerialization.data(withJSONObject: any)
self = try JSONDecoder().decode(Self.self, from: data)
}
}
AckCallback's parameter is of an array type (i.e. [Any]), so you should get the first element of that array.
To make sure that you have indeed a decodable data (a dictionary or a JSON object) you can write something like this:
SocketHandler.mySocket.emitWithAck("my_event", [session, someInput]).timingOut(after: 3) { data in
guard let dict = data.first as? [String: Any] else { return }
let myData = try? myStruct(from: dict)
// ...
}

JSONSerialization with URLSession.shared.dataTask errors

As a part of teaching myself Swift, I am working on a Weather App. I am currently attempting to integrate weather alerts. I use a struct called AlertData to initialize data returned from the API call to weather.gov after serializing the returned data from an API call. Or, at least that is the plan. I have modeled my classes off of other classes that request data from weather.gov, but to get an alert, I need to be able to send variable parameters in my dataTask. I use the URL extension from Apple's App Development with Swift (code below) and have the code set to issue the parameters with the users current location to get alerts where the user is currently.
My problem comes when I attempt to construct the API call to weather.gov in my AlertDataController class(code below). Xcode keeps throwing different errors and I am not sure why. I would like to use a guard statement as I have in my code below, but that throws an error of "Cannot force unwrap value of non-optional type '[[String : Any]]'" in my code where shown. It also throws the same error when I make it a simple constant assignment after unwrapping as the extension returns an optional URL.
The same code works flawlessly when I construct the URL from a string in the guard statement directly as in:
guard let url = URL(string: (baseURL + locationString + stations)) else {
What am I missing? Where my error is thrown is inside the dataTask, and regardless of how it got there, the variable url is an unwrapped URL. Thanks in advance.
Controller class:
import Foundation
import CoreLocation
struct AlertDataController {
func checkWxAlert(location: CLLocation, completion: #escaping (AlertData?) -> Void) {
let baseURL = URL(string: "https://api.weather.gov/alert")!
let locationString = "\(location.coordinate.latitude),\(location.coordinate.longitude)"
var query = [
"active": "1",
"point": locationString
]
guard let url = baseURL.withQueries(query) else {
completion(nil)
print("Unable to build URL in AlertDataController.checkWxAlert with supplied queries.")
return
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data,
let rawJSON = try? JSONSerialization.jsonObject(with: data),
let json = rawJSON as? [String: Any],
let featuresDict = json["features"] as? [[String: Any]],
let propertiesArray = featuresDict!["properties"] as? [String: Any] {
Error: Cannot force unwrap value of non-optional type '[[String : Any]]'
let alertData = AlertData(json: propertiesArray)
completion(alertData)
} else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
}
task.resume()
}
}
URL extension:
import Foundation
extension URL {
func withQueries(_ queries: [String: String]) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.queryItems = queries.flatMap { URLQueryItem(name: $0.0, value: $0.1) }
return components?.url
}
func withHTTPS() -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: true)
components?.scheme = "https"
return components?.url
}
}
If featuresDict is really an array, you cannot use featuresDict["properties"] syntax. That subscript with string syntax is only for dictionaries. But you've apparently got an array of dictionaries.
You could iterate through the featuresDict array (which I'll rename to featuresArray to avoid confusion), you could do that after you finish unwrapping it. Or, if just want an array of the values associated with the properties key for each of those dictionaries, then flatMap is probably a good choice.
For example:
let task = URLSession.shared.dataTask(with: url) { data, _, error in
guard let data = data,
error == nil,
let json = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
let featuresArray = json["features"] as? [[String: Any]] else {
print("Either no data was returned in AlertDataController.checkWxAlert, or data was not serialized.")
completion(nil)
return
}
let propertiesArray = featuresArray.flatMap { $0["properties"] }
let alertData = AlertData(json: propertiesArray)
completion(alertData)
}
Or, if AlertData is expecting each of those properties to be, themselves, a dictionary, you might do:
let propertiesArray = featuresArray.flatMap { $0["properties"] as? [String: Any] }
Just replace that cast with whatever type your AlertData is expecting in its array, json.
Or, if you're only interested in the first property, you'd use first rather than flatMap.
The error
Cannot force unwrap value of non-optional type '[[String : Any]]'
is very clear. It occurs because in the optional binding expression featuresDict is already unwrapped when the next condition is evaluated.
Just remove the exclamation mark
... let propertiesArray = featuresDict["properties"] as? [String: Any] {
The error is not related at all to the way the URL is created.

Swift - Reading JSON File

I'm new to Swift - trying to read a JSON file from a URL. My attempt below.
The JSON looks valid - I tested it with JSONLint but it keeps crashing.
Thoughts?
func getRemoteJsonFile() -> NSDictionary {
//Create a new url
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
//check if its nil
if let actualRemoteUrl = remoteUrl {
//try to get the data
let filedata:NSData? = NSData(contentsOfURL: actualRemoteUrl)
//check if its nil
if let actualFileData = filedata {
//parse out the dictionaries
let jsonDict = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.AllowFragments, error: nil) as NSDictionary
return jsonDict
}
}
return NSDictionary()
}
This took me a second to figure out, so I don't blame you for missing it.
The JSON you linked to is minified, so it's difficult to see the structure. Let's take a look at (a fragment of) it after piping it through a prettifier:
[
{
"PlayerId":2501863,
"PlayerName":"Peyton Manning",
"PlayerTeam":"DEN",
"PlayerPosition":"QB",
"PlayerPassingYards":4727,
"PlayerPassingTDs":39,
"PlayerInterceptions":15,
"PlayerRushingYards":-24,
"PlayerRushingTDs":0,
"PlayerReceivingYards":0,
"PlayerReceivingTDs":0,
"PlayerReturnYards":0,
"PlayerReturnTDs":0,
"PlayerFumbleTDs":0,
"PlayerTwoPointConversions":2,
"PlayerFumblesLost":2,
"PlayerTeamLogo":"http://i.nflcdn.com/static/site/7.0/img/logos/teams-gloss-81x54/den.png"
}
]
Huh. It's encased in brackets, which means that it's an array.
It's an array, so you can't cast it as an NSDictionary. Instead, you could cast it as an NSArray, but why not use native Swift types?
Well, if you don't like types, you're about to find out, but I still think that this is a better way, because it forces you to think about the data you're parsing.
So we have the first part of our type definition for this function; it's an array ([]). What components is our array made up of? We could go with a simple NSDictionary, but we're doing full native types here, so let's use a native Swift dictionary.
To do that, we have to know the types of the dictionary (the syntax for a native dictionary type is [KeyType: ValueType]). Examining the JSON shows that all of the keys are Strings, but the values are of varying types, so we can use AnyObject.
That gives us a dictionary type of [String: AnyObject], and our entire JSON is an array of that, so the final type is [[String: AnyObject]] (wow).
Now that we have the proper type, we can modify the function you're using to parse the JSON a bit.
First of all, let's use our new type for the return and cast values. Then, let's make the return type optional in case something goes wrong and add an error variable to document that.
A cleaned up function would look something like this:
func getData() -> [[String: AnyObject]]? {
let data: NSData? = NSData(contentsOfURL: NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")!)
if let req: NSData = data {
var error: NSError?
if let JSON: [[String: AnyObject]] = NSJSONSerialization.JSONObjectWithData(req, options: NSJSONReadingOptions.AllowFragments, error: &error) as? [[String: AnyObject]] {
return JSON
}
}
return nil
}
That's it!
We can now call the function and extract values from our [[String: AnyObject]] (again, wow) like this:
if let data: [[String: AnyObject]] = getData() {
println(data[0]["PlayerName"]!) // Peyton Manning
}
Update your code with this:
func getRemoteJsonFile() -> [NSDictionary] {
// Create a new URL
let remoteUrl:NSURL? = NSURL(string: "http://nfl-api.azurewebsites.net/myplayers.json")
let urlString:String = "\(remoteUrl)"
// Check if it's nil
if let actualRemoteUrl = remoteUrl {
// Try to get the data
let fileData:NSData? = NSData(contentsOfURL: actualRemoteUrl)
// Check if it's nil
if let actualFileData = fileData {
// Parse out the dictionaries
let arrayOfDictionaries:[NSDictionary]? = NSJSONSerialization.JSONObjectWithData(actualFileData, options: NSJSONReadingOptions.MutableContainers, error: nil) as [NSDictionary]?
if let actualArrayOfDictionaries = arrayOfDictionaries {
// Successfully parsed out array of dictionaries
return actualArrayOfDictionaries
}
}
}
return [NSDictionary]()
}
This is working fine for me.