Hi I am trying to learn RXSwift and First time I came across these concepts like Maps and Compact Maps.
I am able to get the response, but this line always returns empty.
objects.compactMap(DummyUser.init)
fileprivate let Users = Variable<[DummyUser]>([])
fileprivate let bag = DisposeBag()
response
.filter { response, _ in
return 200..<300 ~= response.statusCode
}
.map { _, data -> [[String: Any]] in
guard (try? JSONSerialization.jsonObject(with: data, options: [])) != nil else {
return []
}
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
// print(json!["results"])
return json!["results"] as! [[String : Any]]
}
.filter { objects in
return objects.count > 0
}
.map { objects in
// objects.forEach{print($0["name"]!)}
let names = objects.map { $0["name"]!}
print(names)
return objects.compactMap(DummyUser.init)
}
.subscribe(onNext: { [weak self] newEvents in
self?.processEvents(newEvents)
})
.disposed(by: bag)
func processEvents(_ newEvents: [DummyUser]) {
var updatedEvents = newEvents + Users.value
if updatedEvents.count > 50 {
updatedEvents = Array<DummyUser>(updatedEvents.prefix(upTo: 50))
}
Users.value = updatedEvents
DispatchQueue.main.async {
self.MianUsertable.reloadData()
}
// refreshControl?.endRefreshing()
let eventsArray = updatedEvents.map{ $0.dictionary } as NSArray
eventsArray.write(to: userFileURL, atomically: true)
}
My Json Response is Here
https://randomuser.me/api/?results=5
DummyUser Class
import Foundation
typealias AnyDict = [String: Any]
class DummyUser {
let gender: String
let name: AnyDict
let dob: String
let picture: AnyDict
init?(dictionary: AnyDict) {
guard let Dgender = dictionary["gender"] as? String,
let Dname = dictionary["name"] as? AnyDict,
let birthdata = dictionary["dob"] as? AnyDict,
let Ddob = birthdata["dob"] as? String,
let Dpicture = dictionary["picture"] as? AnyDict
else {
return nil
}
gender = Dgender
name = Dname
dob = Ddob
picture = Dpicture
}
var dictionary: AnyDict {
return [
"user": ["name" : name, "gender": gender, "dob": dob],
"picture" : ["userImage": picture]
]
}
}
In your DummyUser model you are using failable initializer, so in case of wrong dictionary provided to init method it will return nil.
compactMap automatically automatically filters nil's and that's the reason why your output is empty.
Looking at this piece of code:
let names = objects.map { $0["name"]!}
return objects.compactMap(DummyUser.init)
I would debug this variable called names because it probably has wrong input for the DummyUser initializer. It should be dictionary containing all of your DummyUser parameters. You can also debug your failable initializer to see which of the parameter is missing.
Related
I do not know how to access the 'duration' value within my nested Optional NSSingleObjectArrayI that is constructed from a JSON response. How do I access the nested values within this data structure?
When I call print(firstRow["elements"]), I get the following output:
Optional(<__NSSingleObjectArrayI 0x60000120f920>(
{
distance = {
text = "1.8 km";
value = 1754;
};
duration = {
text = "5 mins";
value = 271;
};
"duration_in_traffic" = {
text = "4 mins";
value = 254;
};
status = OK;
}
))
I have tried string indexing (firstRow['elements']['duration']) but am getting errors.
fetchData { (dict, error) in
if let rows = dict?["rows"] as? [[String:Any]]{
if let firstRow = rows[0] as? [String:Any]{
print("firstRow is")
print(firstRow["elements"])
// Trying to access duration within firstRow['elements'] here
}
}
}
For reference, this is the fetchData function:
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = getRequestURL(origin: "test", destination: "test")!;
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
A sample HTTP JSON request is here:
https://maps.googleapis.com/maps/api/distancematrix/json?destinations=77%20Massachusetts%20Ave,%20Cambridge,%20MA&departure_time=now&key=AIzaSyB65D4XHv6PkqvWJ7C-cFvT1QHi9OkqGCE&origins=428%20Memorial%20Dr,%20Cambridge,%20MA
Seeing your output, your firstRow["elements"] is Optional, so you need to unwrap it. And it actually is an NSArray with a single element, where the only element is a Dictionary, with 4 entries -- "distance", "duration", "duration_in_traffic" and "status". You may need to cast the element to a Dictionary to access each entry.
You may use Optional binding with as?-casting for this purpose:
fetchData { (dict, error) in
if let rows = dict?["rows"] as? [[String: Any]] {
if let firstRow = rows.first {
print("firstRow is")
print(firstRow["elements"])
//Unwrap and cast `firstRow["elements"]`.
if let elements = firstRow["elements"] as? [[String: Any]] {
//The value for "duration" is a Dictionary, you need to cast it again.
if let duration = elements.first?["duration"] as? [String: Any] {
print(duration["text"] as? String)
print(duration["value"] as? Int)
}
}
}
}
}
Or too deeply nested ifs are hard to read, so someone would like it as:
fetchData { (dict, error) in
if
let rows = dict?["rows"] as? [[String: Any]],
let firstRow = rows.first,
let elements = firstRow["elements"] as? [[String: Any]],
let duration = elements.first?["duration"] as? [String: Any]
{
print(duration["text"] as? String)
print(duration["value"] as? Int)
}
}
Or using guard may be a better solution.
Or else, if you can show us the whole JSON text in a readable format, someone would show you how to use Codable, which is a modern way to work with JSON in Swift.
I'm playing with Swift and JSONPlaceholder. I want to retrieve all the data contained in: https://jsonplaceholder.typicode.com/photos
I created a function that is acceding to the url, downloading the JSON but then I don't know how can I obtain the title and the thumbnailUrl to pass then for populate the tableView. In the past I used this code but now it's not working because on the JSONPlaceholder there are no array.
Any help for re-arrange the code for read and obtain the jsonplaceholder elements?
func loadList(){
let url = URL(string: urlReceived)
var myNews = NewInfo()
let task = URLSession.shared.dataTask(with: url!) {
(data, response, error) in
if error != nil{
print("ERROR")
}
else{
do {
if let content = data{
let myJson = try JSONSerialization.jsonObject(with: content, options: .mutableContainers)
//print(myJson)
if let jsonData = myJson as? [String : Any] {
if let myResults = jsonData["list"] as? [[String : Any]]{
//dump(myResults)
for value in myResults{
//Read time string from root
if let time = value ["dt_txt"] as? String{
myNews.time = time
}
//Read main container
if let main = value["main"]
as? [String : Any]{
if let temperature = main["temp"] as? Double {
myNews.temperature = String(temperature)
}
}
//Read from weather container
if let weather = value["weather"] as? [[String: Any]]{
for value in weather{
if let weatherContent = value["description"] as? String{
myNews.weatherDescription = weatherContent
}
}
}
self.myTableViewDataSource.append(myNews)
}
dump(self.myTableViewDataSource)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
catch{
}
}
}
task.resume()
}//End func
Okey, so with Alamofire + SwiftyJSON, you can do this:
func loadList(){
let url = "https://jsonplaceholder.typicode.com/photos"
AF.request(url).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
print(json)
for value in json.arrayValue {
let url = value.dictionaryValue["url"]!.stringValue
let albumId = value.dictionaryValue["albumId"]!.stringValue
let thumbnailUrl = value.dictionaryValue["thumbnailUrl"]!.stringValue
let id = value.dictionaryValue["id"]!.stringValue
let title = value.dictionaryValue["title"]!.stringValue
// Add this album to array.
let album = AlbumModel(id: id, albumId: albumId, title: title, thumbnailUrl: thumbnailUrl)
albums.append(album)
}
case .failure(let error):
print(error)
}
}
}
EDIT:
I made model for values
class AlbumModel {
var id: String?
var albumId: String?
var title: String?
var thumbnailUrl: String?
init(id: String?, albumId: String?, title: String?, thumbnailUrl: String?){
self.id = id
self.albumId = albumId
self.title = title
self.thumbnailUrl = thumbnailUrl
}
}
After that, just create an array like var albums = [AlbumModel]() and you can append all the albums to this. Easy to use after in tableViewcell (example: albums[indexPath.row].id)
I try to implement a simple shopping list swift application for iOS as a personal project. I did follow a guide for iOS on youtube.
My question is how do I parse the Item object from firebase to my ShoppingListItem swift object? If I execute the following code, it doesn't show any error message but it does not show any results either. If I uncomment all "items" lines, it shows the expected results without the item information.
Here is a screenshot from the firebase console of my firebase firestore structure / example object
Thanks in advance!
ShoppingListItem.swift
import Foundation
import FirebaseFirestore
protocol DocumentSerializable {
init?(dictionary: [String: Any])
}
struct ShoppingListItem {
var shoppingItemID: String
var priority: Int
var quantity: Int
var item: Item
var dictionary: [String: Any] {
return [
"shoppingItemID": shoppingItemID,
"priority": priority,
"quantity": quantity,
"item": item,
]
}
}
extension ShoppingListItem: DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let shoppingItemID = dictionary["shoppingItemID"] as? String,
let priority = dictionary["priority"] as? Int,
let quantity = dictionary["quantity"] as? Int,
let item = dictionary["item"] as? Item
else { return nil }
self.init(shoppingItemID: shoppingItemID, priority: priority, quantity: quantity, item: item)
}
}
struct Item {
var itemID: String
var lastPurchase: String
var name: String
var note: String
var picturePath: String
var dictionary: [String: Any] {
return [
"itemID": itemID,
"lastPurchase": lastPurchase,
"name": name,
"note": note,
"picturePath": picturePath,
]
}
}
extension Item: DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let itemID = dictionary["itemID"] as? String,
let lastPurchase = dictionary["lastPurchase"] as? String,
let name = dictionary["name"] as? String,
let note = dictionary["note"] as? String,
let picturePath = dictionary["picturePath"] as? String else { return nil }
self.init(itemID: itemID, lastPurchase: lastPurchase, name: name, note: note, picturePath: picturePath)
}
}
Get Data call in TableViewController.swift
db.collection("shoppingList").getDocuments(){
querySnapshot, error in
if let error = error {
print("error loading documents \(error.localizedDescription)")
} else{
self.shoppingArray = querySnapshot!.documents.flatMap({ShoppingListItem(dictionary: $0.data())})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
I used the Codable protocol.
I used this as an extension to the Encodable Protocol:
extension Encodable {
/// Returns a JSON dictionary, with choice of minimal information
func getDictionary() -> [String: Any]? {
let encoder = JSONEncoder()
guard let data = try? encoder.encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any]
}
}
}
Then I use this to decode:
extension Decodable {
/// Initialize from JSON Dictionary. Return nil on failure
init?(dictionary value: [String:Any]){
guard JSONSerialization.isValidJSONObject(value) else { return nil }
guard let jsonData = try? JSONSerialization.data(withJSONObject: value, options: []) else { return nil }
guard let newValue = try? JSONDecoder().decode(Self.self, from: jsonData) else { return nil }
self = newValue
}
}
Make your two structs conform to Codable (Item first, then ShoppingListItem). Of course, this may not work for the existing data stored in Firestore. I would first put data into Firestore via the getDictionary() (in a new collection), then try to read it back into your tableView.
You may also want to print the actual error when trying to Decode your data, this will greatly help you pinpoint the data error if there's any.
extension Decodable {
/// Initialize from JSON Dictionary. Return nil on failure
init?(dictionary value: [String:Any]) {
guard JSONSerialization.isValidJSONObject(value) else {
return nil
}
do {
let jsonData = try JSONSerialization.data(withJSONObject: value, options: [])
let newValue = try JSONDecoder().decode(Self.self, from: jsonData)
self = newValue
}
catch {
log.error("failed to serialize data: \(error)")
return nil
}
}
}
Here I tried to parse the data from my local server but unable to parse it and it returning empty data and below are my model classes from which the data I was passing to an table view which can anyone help me what's wrong in implementing it?
Here I had attached my image which follows the Json format:
Code:
var homePageModel = [HomeBanner]()
func HomeBannerDownloadJsonWithURL(){
let url = URL(string: homePageUrl)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil { print(error!); return }
do {
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
for item in jsonObj {
print(item)
for dict in item {
print(dict)
let dict = HomeBanner(json: item)
self.homePageModel.append(dict!)
print(self.homePageModel)
}
}
print(self.homePageModel)
DispatchQueue.main.async {
self.homeTableView.delegate = self
self.homeTableView.dataSource = self
self.homeTableView.reloadData()
}
}
} catch {
print(error)
}
}
task.resume()
}
struct HomeBanner {
let title : String?
let titleInArabic : String?
let showTitle : String?
var banner = [ChildrenBanners]()
init?(json : [String:Any]) {
if let customAttribute = json["childran_banners"] as? [[String: AnyObject]] {
var result = [ChildrenBanners]()
for obj in customAttribute {
result.append(ChildrenBanners(json: obj as! [String : String])!)
}
self.banner = result
} else {
self.banner = [ChildrenBanners]()
}
self.title = json["title"] as? String ?? ""
print(self.title)
self.titleInArabic = json["title_in_arabic"] as? String ?? ""
self.showTitle = json["show_title"] as? String ?? ""
}
}
struct ChildrenBanners {
let bannerId : String?
let name : String?
let status : String?
let sliderId : String?
let desktopImage : String?
let mobileImage : String?
let imageAlt : String?
let sortOrder : String?
let startTime : String?
let endTime : String?
init?(json : [String:Any]) {
self.bannerId = json["banner_id"] as? String ?? ""
print(self.bannerId)
self.name = json["name"] as? String ?? ""
self.status = json["status"] as? String ?? ""
self.sliderId = json["slider_id"] as? String ?? ""
self.desktopImage = json["desktop_image"] as? String ?? ""
self.mobileImage = json["mobile_image"] as? String ?? ""
self.imageAlt = json["image_alt"] as? String ?? ""
self.sortOrder = json["sort_order"] as? String ?? ""
self.startTime = json["start_time"] as? String ?? ""
self.endTime = json["end_time"] as? String ?? ""
}
}
Just try these lines of code
if let jsonObj = try JSONSerialization.jsonObject(with: data!) as? [[String:Any]] {
self.homePageModel = jsonObj.map{HomeBanner(json: $0)}
print(self.homePageModel)
DispatchQueue.main.async {
self.homeTableView.delegate = self
self.homeTableView.dataSource = self
self.homeTableView.reloadData()
}
}
} catch {
print(error)
}
and there is no necessity of making optional initializer for HomeBanner and ChildrenBanners just use init(json : [String : Any]){} for both struct
Root of json is an array and then second level is dictionary with keys list1, list2 etc. You are missing that in your code. Should be something like this (I haven't compiled it).
if let data = data, let jsonObj = try JSONSerialization.jsonObject(with: data) as? [[String:[String:Any]]] {
for item in jsonObj {
for (_, dict) in item {
if let obj = HomeBanner(json: dict) {
self.homePageModel.append(obj)
}
}
}
}
There are lot of other issues in your code. Like force unwrapping optional. Using same parameters again within a scope. For example.
for dict in item {
let dict = HomeBanner(json: item)
// ....
}
You shouldn't use same param names like you are using dict it hides the scope of the outer dict.
I am getting the following JSON from Foursquare API and I have been struggling with extracting the data:
{
"meta":{
"code":200,
"requestId":"58122e59498e5506a1b23580"
},
"response":{
"venues":[
{
"id":"4d56c381a747b60cd4a12c2b",
"name":"Sports Circle",
"contact":{},
"location":{
"lat":31.9,
"lng":35.9,
"labeledLatLngs":[
{
"label":"display",
"lat":31.9,
"lng":35.90
}
],
],
"confident":true
}
}
}
I want to get the name in venues in addition to the lat and lng values. I have tried this so far but it gets out of the second if statement at JVenues because it is nil:
func parseData (JSONData: Data){
do {
var readableJson = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! [String:AnyObject]
if let JResponse = readableJson ["response"] as? [String:AnyObject] {
if let JVenues = JResponse["venues"] as? [String:AnyObject]{
if let JName = JVenues["name"] as? String{
NSLog(JName)
}
}
}
} catch {
print(error)
}
}
This is what the other answers are getting at. Will probably make more sense if you can see it all laid out...
if let JResponse = readableJson ["response"] as? [String : AnyObject] {
if let JVenues = JResponse["venues"] as? [[String : AnyObject]] {
if let JName = JVenues.first?["name"] as? String {
NSLog(JName)
}
}
}
Note this only gets the FIRST name in the array of venues.
EDIT:
I prefer something like this. Define a struct and convert your dictionaries to the struct:
struct Venue {
var name: String?
var venueId: String?
init(_ venueDictionary: [String : AnyObject]) {
self.name = venueDictionary["name"] as? String
self.venueId = venueDictionary["id"] as? String
}
}
In your class create a property such as:
var venues = [Venue]()
From your JSON map the dictionaries to the venue array. I renamed variables that start with a capital for convention.
if let response = readableJson ["response"] as? [String : AnyObject] {
if let responseVenues = response["venues"] as? [[String : AnyObject]] {
self.venues = responseVenues.map({ Venue($0)) })
}
}
Use anywhere in your class like:
let venue = self.venues.first
print(venue?.name)
Or:
if let venue = self.venues.find({ $0.name == "Sports Circle" }) {
print("found venue with id \(venue.venueId)")
}