How to iterate through JSON - json

I have a method which performs a GET request to an API:
public func getApiData(completion: #escaping () -> (), fullUrl: String)
{
let session = URLSession.shared
let url = URL(string: fullUrl)!
let task = session.dataTask(with: url) { (data, _, _) -> Void in
if let data = data {
self.serializeToJSON(jsonData: data)
completion()
}
}
task.resume()
}
Using SwiftyJSON I then convert the data into JSON:
private func serializeToJSON(jsonData: Data) {
self.json = JSON(data: jsonData)
print(self.json)
for (index,item) in self.json {
print("hi")
}
}
Printing the full JSON gives:
[{"TenantID":1,"Tenant1":"RAC"},{"TenantID":2,"Tenant1":"VictorMillwell"},{"TenantID":3,"Tenant1":"Comfort"},{"TenantID":4,"Tenant1":"Greenlight"}]
However the JSON can't be iterated through as the print("hi") isn't executed, I'm not sure why, I've looked everywhere on the internet to understand why it doesn't iterate and I cant seem to understand why.
Does anyone know why?

There is a good tutorial here, but in the manual it says you can loop like this:
// If json is .Dictionary
for (key,subJson):(String, JSON) in self.json {
// Do something you want
}
// If json is .Array
// The `index` is 0..<json.count's string value
for (index,subJson):(String, JSON) in self.json {
// Do something you want
}
if you don't know if it's a dictionary or array, maybe you can do it like this:
switch self.json.type {
case .array:
for (index,subJson):(String, JSON) in self.json {
// Do something you want
}
case .dictionary:
for (key,subJson):(String, JSON) in self.json {
// Do something you want
}
default:
// Do some error handling
}

It isn't clear why you want to enumerate the JSON. It's trivial to decode this JSON in Swift 4:
struct Tenant:Decodable { let TenantID:Int; let Tenant1:String }
let arr = try! JSONDecoder().decode([Tenant].self, from: data)
Now arr is a Swift array of Tenant, where each Tenant has a TenantID property and a Tenant1 property. And now you can do whatever you like with that array, including cycling through it if you wish.

Related

I need help parsing some JSON data

I am fairly new to parsing json data and I am attempting to parse some json data from an rss feed generator and I am running into a problem where I can successfully print the data I am getting but I can't save the data to an object.
I have looked through tutorials that used decodables/codables mostly but I was able to use the urlSession and jsonSerialization objects for what I needed just fine.
class JSONSongs {
// initialize song array...
var songArray: [Song] = []
func getSongs() {
let jsonSongUrl = "https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/50/explicit.json"
let songUrl = URL(string: jsonSongUrl) // convert string to usable url
// start url session task with apple music api url...
// we get some data(hopefully), a response code and an error(hoepfully not)
let songTask = URLSession.shared.dataTask(with: songUrl!) { (data, response, error) in
// checking for an error
if error != nil {
print(Error.self)
print(error?.localizedDescription)
return
} else {
// lets store our data in a variable
if let content = data {
do {
// taking the json data and converting it so we can make objects
let json = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)
//print(json) // making sure data is present
// checking to see if our json data is there
guard let jsonOne = json as? [String: Any] else {
print("invalid operation!")
return
}
// accessing top root of the json file
if let feed = jsonOne["feed"] as? [String: Any] {
//print("it worked") // testing
// accessing the results array where the albums are stored
// there are arrays in the nested json data so we need the double brackets to access them
if let result = feed["results"] as? [[String: Any]]{
for item in result {
// attempting to store data in Song object, this is where problems appear
if let songName = (item["name"] as AnyObject? as? String),
let artistName = (item["artistName"] as AnyObject? as? String),
let coverArt = (item["artworkUrl100"] as AnyObject? as? String),
let artistPage = (item["artistUrl"] as AnyObject? as? String) {
self.songArray.append(Song(songName: songName, artistName: artistName, coverArt: coverArt, artistPage: artistPage))
// printing the data to the console works here but I can't save the data to an object
}
}
}
}
} catch {
print(error.localizedDescription)
print(Error.self)
return
}
}
}
}
songTask.resume()
}
}
All I get is either nil when I try and print a string value or 0 when I try and count the number of objects that are present in the songArray array
Basically your code is correct and should work, however this is a version using Decodable.
The songs property will contain the song data
struct Root : Decodable {
let feed : Feed
}
struct Feed : Decodable {
let results : [Song]
}
struct Song : Decodable {
let name, artistName : String
let artworkUrl100, artistUrl : URL
}
class JSONSongs {
var songs = [Song]()
func getSongs() {
let jsonSongUrl = "https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/50/explicit.json"
let songUrl = URL(string: jsonSongUrl) // convert string to usable url
// start url session task with apple music api url...
// we get some data(hopefully), a response code and an error(hoepfully not)
let songTask = URLSession.shared.dataTask(with: songUrl!) { [weak self] data, _, error in
// checking for an error
if let error = error { print(error); return }
do {
// taking the json data and converting it so we can make objects
let result = try JSONDecoder().decode(Root.self, from: data!)
self?.songs = result.feed.results
print(self?.songs)
} catch {
print(error)
}
}
songTask.resume()
}
}

Converting Firebase Datasnapshot to codable JSON data

I'm using Firebase Database and I'm attempting to retrieve and use data with NSObject. I'm receiving an NSUnknownKeyException error when running the app, causing it to crash.
NSObject:
class WatchList: NSObject {
var filmid: Int?
}
Firebase Code:
ref.child("users").child(uid!).child("watchlist").observe(DataEventType.childAdded, with: { (info) in
print(info)
if let dict = info.value as? [String: AnyObject] {
let list = WatchList()
list.setValuesForKeys(dict)
print(list)
}
}, withCancel: nil)
I'm not sure of what could cause this.
Also, to enhance this solution is their a way to take this data and, instead of using NSObject, use Codable and JSONDecoder with the Firebase data?
You can simply use JSONSerialization to convert the snapshot value property from Any to Data:
let data = try? JSONSerialization.data(withJSONObject: snapshot.value)
You can also extend Firebase DataSnapshot type and add a data and json string properties to it:
import Firebase
extension DataSnapshot {
var data: Data? {
guard let value = value, !(value is NSNull) else { return nil }
return try? JSONSerialization.data(withJSONObject: value)
}
var json: String? { data?.string }
}
extension Data {
var string: String? { String(data: self, encoding: .utf8) }
}
usage:
guard let data = snapshot.data else { return }
It's 2021 now.
Firebase finally added support for decoding Firestore documents. Just let your objects conform to Codable and decode like this:
let result = Result {
try document?.data(as: City.self)
}
switch result {
case .success(let city):
if let city = city {
print("City: \(city)")
} else {
print("Document does not exist")
}
case .failure(let error):
// A `City` value could not be initialized from the DocumentSnapshot.
print("Error decoding city: \(error)")
}
Just don't forget to add the 'FirebaseFirestoreSwift' pod and then import it into your file.
Read more:
https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
Original answer
A really nice library to use here is Codable Firebase which I am also using in my project. Just make your class / struct conform to Codable protocol and use FirebaseDecoder to decode your Firebase data into a Swift object.
Example:
Database.database().reference().child("model").observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value else { return }
do {
let model = try FirebaseDecoder().decode(Model.self, from: value)
print(model)
} catch let error {
print(error)
}
})

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)
// ...
}

Swift 3 - How can i extract elements from a json?

Hello i want to extract certain elements from a json .
CODE: .
var datas:[Usuario]?
struct Usuario : Codable {
let correo: String?
let contrasena: String?
}
let urlString = "http://localhost:8080/swiftdb/logear.php"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
//Implement JSON decoding and parsing
do {
//Decode retrived data with JSONDecoder and assing type of Article object
let articlesData = try JSONDecoder().decode([Usuario].self, from: data)
//Get back to the main queue
DispatchQueue.main.async {
}
} catch let jsonError {
print(jsonError)
}
}.resume()
What i want to do, is compare what it is in the json with a textfield ,so it can go to a different view.
Json Contains:
[{"correo":"unknown11#hotmail.com","contrasena":"12345"},{"correo":"lalo123#hotmail.com","contrasena":"12121"},{"correo":"kokunz#hotmail.com","contrasena":"11111"},{"correo":"a","contrasena":"a"}]
Textfield Contains: {Something which the user writes in in. txtEmail.text Ex.}
Before the answer, you can check the Apple Reference about JSON and parsing . https://developer.apple.com/swift/blog/?id=37
if let dictionary = netoxJsonVariable as? [String: Any] {
for (key, value) in dictionary {
// access all key / value pairs in dictionary
// I am not sure that you can reach key with string but I think you are searching for this.
//if(value == "searchedMailAddress"){//then}
//if(key == "correoLikeKey"){//then}
}
}
I used a for statement so
let {newVar} = self.{yourJson}
for item in {newVar}! {
let {anotherVar} = item.{field}
let {anotherVar2} = item.{field2}

How to convert a Swift object to a dictionary

I'm relatively new to iOS programming. However, I would have assumed that Swift would have an automated way of converting objects to JSON and vice versa. That being said, I have found several libraries that can do this.
HOWEVER...
It seems that no matter how you post data to a web service (even using something like AlamoFire), the requests must be a dictionary. All these forums show examples of how easy it is to convert the returned JSON string to objects. True. But the request needs to be manually coded. That is, go through all of the object properties and map them as a dictionary.
So my question is this: Am I missing something? Have I got this all wrong and there's a super-easy way to either (a) send JSON (instead of a dictionary) in the REQUEST or (b) convert an object automatically to a dictionary?
Again, I see how easy it is to deal with a JSON response. I'm just looking for an automatic way to convert the request object I want to post to a web service into a format that a library like AlamoFire (or whatever) requires. With other languages this is fairly trivial, so I'm hoping there's an equally easy and automated way with Swift.
I must disagree with #Darko.
In Swift 2,
use protocol oriented programming and the simple reflection offered by Mirror class :
protocol JSONAble {}
extension JSONAble {
func toDict() -> [String:Any] {
var dict = [String:Any]()
let otherSelf = Mirror(reflecting: self)
for child in otherSelf.children {
if let key = child.label {
dict[key] = child.value
}
}
return dict
}
}
then you can use this protocol with your request class and produce the desired dictionary :
class JsonRequest : JSONAble {
var param1 : String?
// ...
}
let request = JsonRequest()
// set params of the request
let dict = request.toDict()
// use your dict
My solution to this will be something like this:
extension Encodable {
var dict : [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { return nil }
return json
}
}
and usage will be something like this:
movies.compactMap { $0.dict }
Swift currently does not support advanced reflection like Java or C# so the answer is: no, there is not an equally easy and automated way with pure Swift.
[Update] Swift 4 has meanwhile the Codable protocol which allows serializing to/from JSON and PLIST.
typealias Codable = Decodable & Encodable
Without using reflection, and works for nested objects (Swift 4):
protocol Serializable {
var properties:Array<String> { get }
func valueForKey(key: String) -> Any?
func toDictionary() -> [String:Any]
}
extension Serializable {
func toDictionary() -> [String:Any] {
var dict:[String:Any] = [:]
for prop in self.properties {
if let val = self.valueForKey(key: prop) as? String {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Int {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Double {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Array<String> {
dict[prop] = val
} else if let val = self.valueForKey(key: prop) as? Serializable {
dict[prop] = val.toDictionary()
} else if let val = self.valueForKey(key: prop) as? Array<Serializable> {
var arr = Array<[String:Any]>()
for item in (val as Array<Serializable>) {
arr.append(item.toDictionary())
}
dict[prop] = arr
}
}
return dict
}
}
Just implement properties and valueForKey for the custom objects you want to convert. For example:
class Question {
let title:String
let answer:Int
init(title:String, answer:Int) {
self.title = title
self.answer = answer
}
}
extension Question : Serializable {
var properties: Array<String> {
return ["title", "answer"]
}
func valueForKey(key: String) -> Any? {
switch key {
case "title":
return title
case "answer":
return answer
default:
return nil
}
}
}
You can add more value types in the toDictionary function if you need.
The latest solution that I found after lots of digging throughout Stack Overflow is:
//This block of code used to convert object models to json string
let jsonData = try JSONEncoder().encode(requestData)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
//This method is used to convert jsonstring to dictionary [String:Any]
func jsonToDictionary(from text: String) -> [String: Any]? {
guard let data = text.data(using: .utf8) else { return nil }
let anyResult = try? JSONSerialization.jsonObject(with: data, options: [])
return anyResult as? [String: Any]
}
//Use above method something like this
let params = jsonToDictionary(from: jsonString) ?? [String : Any]()
//Use params to pass in paramters
Alamofire.request(completeUrl, method: .post, parameters: params, encoding:JSONEncoding.prettyPrinted, headers: myHeaders){
response in
//Do whatever you want with response of it.
}
Note:
I combine this solution from multiple answers.
This solution i used with alamofire because alamofire only accept parameter at this format "[String:Any]".
You can also use the ObjectMapper library. It has a "toJSON" method that converts your object to a dictionary.
in short
let dict = Mirror(reflecting: self).children.map({ $0 }).reduce(into: [:]) { $0[$1.label] = $1.value }
Example how to use Mirror with conversion to specific Dictionary type:
protocol DictionaryConvertible { }
extension DictionaryConvertible {
func toDictionary() -> [String: CustomStringConvertible] {
Dictionary(
uniqueKeysWithValues: Mirror(reflecting: self).children
.compactMap { child in
if let label = child.label,
let value = child.value as? CustomStringConvertible {
return (label, value)
} else {
return nil
}
}
)
}
}