I'm trying to use JSONDecoder with FirebaseDatabase. There are some examples online, but I haven't been able to find one with dynamic object keys like the ones that are being returned in the response I am getting. Is there a better way to do this?
JSON Example:
{
"-LUOhAM5W42t9YxqFkUQ" = {
author = {
name = testName;
};
commentCount = 0;
detail = "";
id = "-LUOhAM5W42t9YxqFkUQ";
likeCount = 0;
};
"-LUOhNU4wfwTlZQkrGFu" = {
author = {
name = otherName;
};
commentCount = 0;
detail = "";
id = "-LUOhNU4wfwTlZQkrGFu";
likeCount = 0;
};
My Struct looks like this (I'm just trying to decode successfully first):
struct Post: Decodable {
var id: String?
var title: String?
}
And my code in my controller looks like this:
func getPosts() {
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
guard let value = snapshot.value else { return }
print("VALUE: \(value)")
do {
let person = try FirebaseDecoder().decode(Post.self, from: value)
print(person)
} catch let error {
print(error)
}
})
}
I know I could loop through each object, decode and then add them to an array, but I'm looking for the most efficient approach and I really like the JSONDecoder. Again, the snag seems to be that Firebase puts the object key at the beginning of each object, which seems weird. Any help at all would be greatly appreciated!
Related
Hello I'm new to swift and this is giving me some problems.
I have a function from SDK that returns Any as a result, this is what the actual data looks like:
/*{
code = 1;
msg = success;
result = {
macAddress = "E6:1D:4D:71:64:9B";
};
}*/
I figured out a way to get the data I need, but it seems quite convoluted.
sucBlock: {
(mac) in
if let macAddress = ((mac as AnyObject)["result"] as AnyObject )["macAddress"] as! Optional<String>{
print("macAddress:" + macAddress)
}
}
Is there a better way to achieve this result? I tried to create a struct and type cast this object, but somehow it always failed.
You need to avoid using AnyObject and as! in such cases
if let macAddress = mac as? [String:Any] , let item = macAddress["result"] as? [String:String] , let str = item["macAddress"] {
print("str:" + str)
}
If you need a struct ( Don't think it really deserves for your simple json )
do {
let data = try JSONSerialization.data(withJSONObject:mac)
let res = try JSONDecoder().decode(Root.self, from:data)
print(res.result.macAddress)
}
catch {
print(error)
}
struct Root: Codable {
let code: Int
let msg: String
let result: Result
}
// MARK: - Result
struct Result: Codable {
let macAddress: String
}
I'm getting an array of dictionaries from the server. Then I'm trying to convert it to jsonDictionary it seems like I'm doing something wrong. How can I also init my Users model?
Here is the code:
func getSearchedUsers(key: String, completion: #escaping(SearchedUsers?) -> Void) {
if let url = URL(string: baseURL + "search?qerty=\(key)") {
Alamofire.request(url).responseJSON { (response) in
if let array = response.result.value as? [[String:Any]] {
var dictionary = [String:Any]()
for item in array {
for (key, value) in item {
dictionary.updateValue(value, forKey: key)
}
}
} else {
completion(nil)
}
}
}
}
And here is the model:
class SearchedUsers {
let id: Int
let username: String?
let fullName: String?
let profilePicture: URL?
let isPrivate: Bool
init(data: [String: Any]) {
id = data["id"] as! Int
username = data["username"] as? String
fullName = data["fullName"] as? String
isPrivate = data["isPrivate"] as! Bool
profilePicture = data["profilePicUrl"] as? URL
}
}
How can I get this to work?
Here is the response I get:
[Result]: SUCCESS: (
{
byline = "21.9k followers";
followerCount = 21911;
friendshipStatus = {
following = 0;
"incoming_request" = 0;
"is_bestie" = 0;
"is_private" = 0;
"outgoing_request" = 0;
};
fullName = "Undefined Variable";
hasAnonymousProfilePicture = 0;
id = 8513861541;
isPrivate = 0;
isVerified = 0;
mutualFollowersCount = 0;
picture = "https://scontent-ams3-1.cdninstagram.com/vp/885ac17fe17809de22790f0559f61877/5CD13A1C/t51.2885-19/s150x150/39312159_480582069091253_3011569611268161536_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
pk = 8513861541;
profilePicId = "1857507164564653723_8513861541";
profilePicUrl = "https://scontent-ams3-1.cdninstagram.com/vp/885ac17fe17809de22790f0559f61877/5CD13A1C/t51.2885-19/s150x150/39312159_480582069091253_3011569611268161536_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
reelAutoArchive = on;
username = "i_am_variable";
},
{
byline = "467 followers";
followerCount = 467;
friendshipStatus = {
following = 0;
"incoming_request" = 0;
"is_bestie" = 0;
"is_private" = 0;
"outgoing_request" = 0;
};
fullName = undefined;
hasAnonymousProfilePicture = 0;
id = 8657882817;
isPrivate = 0;
isVerified = 0;
latestReelMedia = 1547794887;
mutualFollowersCount = 0;
picture = "https://scontent-ams3-1.cdninstagram.com/vp/fb3c992c899aa269bdce2c4c1db8575b/5CD068BA/t51.2885-19/s150x150/46378106_2062632390480778_1266491662662631424_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
pk = 8657882817;
profilePicId = "1931972067016763185_8657882817";
profilePicUrl = "https://scontent-ams3-1.cdninstagram.com/vp/fb3c992c899aa269bdce2c4c1db8575b/5CD068BA/t51.2885-19/s150x150/46378106_2062632390480778_1266491662662631424_n.jpg?_nc_ht=scontent-ams3-1.cdninstagram.com";
reelAutoArchive = on;
username = "undefi.ned";
})
It's an array of dictionaries, I need to parse it in a proper way. That's my main issue.
If you know how to parse dictionary, then you should know how to make one ;) There are tools out there to make your own model class, like: http://www.jsoncafe.com/
EDIT: As suggested by Robert in the comment section below, you can learn Decodable.
You can use that to give yourself an idea how a model class could or should look like. Use it however you like. In a decent project, there could be tons of data, and you don't want to make a class model out of it especially if you're the only one handling the iOS project.
So we suppose, we have this json data, based on your post:
{
"id": 1,
"username": "dd",
"fullName": "dd",
"profilePicture": "ddd",
"isPrivate": true
}
We could make a model out of it like so:
//
// UserRootClass.swift
// Model Generated using http://www.jsoncafe.com/
// Created on January 18, 2019
import Foundation
class UserRootClass : NSObject {
var fullName : String!
var id : Int!
var isPrivate : Bool!
var profilePicture : String!
var username : String!
/**
* Instantiate the instance using the passed dictionary values to set the properties values
*/
init(fromDictionary dictionary: [String:Any]){
fullName = dictionary["fullName"] as? String
id = dictionary["id"] as? Int
isPrivate = dictionary["isPrivate"] as? Bool
profilePicture = dictionary["profilePicture"] as? String
username = dictionary["username"] as? String
}
/**
* Returns all the available property values in the form of [String:Any] object where the key is the approperiate json key and the value is the value of the corresponding property
*/
func toDictionary() -> [String:Any]
{
var dictionary = [String:Any]()
if fullName != nil{
dictionary["fullName"] = fullName
}
if id != nil{
dictionary["id"] = id
}
if isPrivate != nil{
dictionary["isPrivate"] = isPrivate
}
if profilePicture != nil{
dictionary["profilePicture"] = profilePicture
}
if username != nil{
dictionary["username"] = username
}
return dictionary
}
}
The model class above was made using the tool I gave above, but I removed the NSCoding protocol methods.
I hope this helps! Good luck and welcome to Stackoverflow.
You can use Decodable if you have Struct instead of Class for easy parsing. Here is the example in Alamofire 5.0
struct SearchedUsers: Decodable {
let id: Int
let username: String?
let fullName: String?
let profilePicture: URL?
let isPrivate: Bool
}
AF.request("http://url_endpoint/").responseData { response in
do {
// data we are getting from network request
let decoder = JSONDecoder()
let response = try decoder.decode([SearchedUsers].self, from: response.data!)
} catch { print(error) }
}
I've been trying to find out how to save a part of a JSON response from Alamofire as a variable to make an if statement and can't seem to find how to do it.
I made a piece of code to use as an example for the question which consists of the following:
import UIKit
import Alamofire
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request("http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=44143256ee15e9c3f521ca062463dd8d").responseJSON { response in
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
}
}
I get the following response from the API request:
JSON: {
base = stations;
clouds = {
all = 0;
};
cod = 200;
coord = {
lat = "51.51";
lon = "-0.13";
};
dt = 1485031800;
id = 2643743;
main = {
humidity = 83;
pressure = 1026;
temp = "270.54";
"temp_max" = "273.15";
"temp_min" = "267.15";
};
name = London;
sys = {
country = GB;
id = 5091;
message = "0.0038";
sunrise = 1484985141;
sunset = 1485016345;
type = 1;
};
visibility = 7000;
weather = (
{
description = haze;
icon = 50n;
id = 721;
main = Haze;
}
);
wind = {
speed = 1;
};
}
From this JSON response I would like to save the weather description so I could use the following statement:
if var currentWeather = sunny {
return "Nice day!"
} else {
return "Uh-Oh, keep warm!"
}
If anyone could help me with this I would greatly appreciate it! If I have to use SwiftyJSON to make it easier that's fine I've just been struggling with this for a while and need to figure it out!
You can parse your result following the printout of your JSON response:
guard let JSON = response.result.value as? [String:Any],
let weather = JSON["weather"] as? [[String:Any]] else {
print("Could not parse weather values")
return
}
for element in weather {
if let description = element["description"] as? String {
print(description)
}
}
If you wanted to save the result, or do something based on a certain result as you described, you could insert something else instead of just print(description) like:
if description == "sunny" {
//do something
}
Let me know if this makes sense. Keep in mind that ( and ) in the Xcode console means "Array".
I'm using Swift, Alamofire, and Alamofire ObjectMapper. I have a response JSON with an array of objects inside. I need to map each of the objects inside the array to an object and put it in an array of objects.
I'm pretty much sure that its's a dumb question, but I haven't found any solution online.
Thank you
One way to do it is to use SwiftyJSON https://github.com/SwiftyJSON/SwiftyJSON
Then you can do something like this.. response is the json reponse from alamofire
var categories = [Category]()
if let json = response["sports"].array {
for var i = 0; i < json.count; i++ {
let category : JSONObj = json[i]
if let catname = category["name"].string {
categories.append(Category(json: json[i]))
}
}
}
In the Category model i have a init method that take a json as parameter.. Like this
class Category {
var name : String?
var id : Int?
init(json : JSONObj) {
if let name = json["name"].string {
self.name = name
}
if let id = json["id"].int {
self.id = id
}
}
init() { }
}
I'm trying to access the url of an object stored in an array, but I'm getting errors no matters what methods I'm using.
let userPhotos = currentUser?.photos
for var i = 0; i < userPhotos!.count ; ++i {
let url = userPhotos[i].url
}
Here I get
Could not find member 'url'
and with a foreach:
for photo in userPhotos {
Utils.getImageAsync(photo.url , completion: { (img: UIImage?) -> () in
})
}
I get:
'[ModelAttachment]?' does not have a member named 'Generator'
My array is var photos: Array<ModelAttachment>? and my ModelAttachment looks like this:
class ModelAttachment :Model {
var id: String?
var url: String?
var thumb: String?
}
Any pointers to what I'm doing wrong would be great :)
Unwrap and downcast the objects to the right type, safely, with if let, before doing the iteration with a simple for in loop.
if let currentUser = currentUser,
let photos = currentUser.photos as? [ModelAttachment]
{
for object in photos {
let url = object.url
}
}
There's also guard let else instead of if let if you prefer having the result available in scope:
guard let currentUser = currentUser,
let photos = currentUser.photos as? [ModelAttachment] else
{
// break or return
}
// now 'photos' is available outside the guard
for object in photos {
let url = object.url
}
Your userPhotos array is option-typed, you should retrieve the actual underlying object with ! (if you want an error in case the object isn't there) or ? (if you want to receive nil in url):
let userPhotos = currentUser?.photos
for var i = 0; i < userPhotos!.count ; ++i {
let url = userPhotos![i].url
}
But to preserve safe nil handling, you better use functional approach, for instance, with map, like this:
let urls = userPhotos?.map{ $0.url }
You can try using the simple NSArray in syntax for iterating over the array in swift which makes for shorter code.
The following is working for me:
class ModelAttachment {
var id: String?
var url: String?
var thumb: String?
}
var modelAttachementObj = ModelAttachment()
modelAttachementObj.id = "1"
modelAttachementObj.url = "http://www.google.com"
modelAttachementObj.thumb = "thumb"
var imgs: Array<ModelAttachment> = [modelAttachementObj]
for img in imgs {
let url = img.url
NSLog(url!)
}
See docs here
The photos property is an optional array and must be unwrapped before accessing its elements (the same as you do to get the count property of the array):
for var i = 0; i < userPhotos!.count ; ++i {
let url = userPhotos![i].url
}