How to instantiate a mapped class? (swift - alamofireObjectMapper) - swift

I have this mapped class caled Movie and I make an API request that returns me this type. How can I instantiate this class with the values of my API response?
Movie mapped class:
class Movie: Mappable {
var posterURL : String?
var title : String?
var runtime : String?
var director : String?
var actors : String?
var genre : String?
var plot : String?
var production : String?
var released : String?
var year : String?
var imdbID : String?
var imdbRating : String?
required init?(map: Map) {
}
func mapping(map: Map) {
posterURL <- map["Poster"]
title <- map["Title"]
runtime <- map["Runtime"]
director <- map["Director"]
actors <- map["Actors"]
genre <- map["Genre"]
plot <- map["Plot"]
production <- map["Production"]
released <- map["Released"]
year <- map["Year"]
imdbID <- map["imdbID"]
imdbRating <- map["imdbRating"]
}
}
And in my MovieViewController I'm making the API call and passing the values for my outlet label.
But I would like to instantiate this class by assigning the values ​​obtained in my API call.
func getMovieById() {
let requestURL = "https://www.omdbapi.com/?i=\(String(describing: imdbID!))"
print("URL: \(requestURL)")
Alamofire.request(requestURL).responseObject{ (response: DataResponse<Movie>) in
print("|MovieController| Response is: \(response)")
DispatchQueue.main.async {
let spinnerActivity = MBProgressHUD.showAdded(to: self.view, animated: true)
spinnerActivity.label.text = "Loading";
spinnerActivity.isUserInteractionEnabled = false;
}
let movie = response.result.value
if let posterURL = movie?.posterURL {
print("Poster URL: \(posterURL)")
let imgStg: String = posterURL
print("---> Image string: \(imgStg)")
let imgURL: URL? = URL(string: imgStg)
let imgSrc = ImageResource(downloadURL: imgURL!, cacheKey: imgStg)
self.movPosterImageView.layer.cornerRadius = self.movPosterImageView.frame.size.width/2
self.movPosterImageView.clipsToBounds = true
//image cache with KingFisher
self.movPosterImageView.kf.setImage(with: imgSrc)
}
if let title = movie?.title {
print("Title: \(title)")
self.movTitleLabel.text = title
}
if let runtime = movie?.runtime {
print("Runtime: \(runtime)")
self.movRuntimeLabel.text = runtime
}
if let genre = movie?.genre {
print("Genre: \(genre)")
self.movGenreLabel.text = genre
}
if let plot = movie?.plot {
print("Plot: \(plot)")
self.movPlotTextView.text = plot
}
if let rating = movie?.imdbRating {
print("Rating: \(rating)")
self.movRatingLabel.text = rating
}
if let director = movie?.director {
print("Director: \(director)")
self.movDirectorLabel.text = director
}
if let production = movie?.production {
print("Production: \(production)")
self.movProductionLabel.text = production
}
if let actors = movie?.actors {
print("Actors: \(actors)")
self.movActorsLabel.text = actors
}
if let released = movie?.released {
print("Released in: \(released)")
self.movReleasedLabel.text = released
}
DispatchQueue.main.async {
MBProgressHUD.hide(for: self.view, animated: true)
}
}//Alamofire.request
}//getMovieByID()
It would be something like
let movieDetails: Movie = Movie(plot = movie?.plot, title = movie?.title, ...)
How can I do this with a mappable class?
Update
I'm trying to organize this things and also I'll have to reuse code, so did this inside functions seems better for me. So, I started separating the API call putting like this:
file: OMDB.swift
import Foundation
import Alamofire
import AlamofireObjectMapper
func getMovieIdFromAPI(imdbID: String, completionHandler: #escaping (Movie) -> () ) {
let requestURL = "https://www.omdbapi.com/?i=\(imdbID)"
print("|getMovieIdFromAPI| URL: \(requestURL)")
Alamofire.request(requestURL).responseObject{ (response: DataResponse<Movie>) in
print("|Alamofire request| Response is: \(response)")
if let movieResult = response.result.value{
completionHandler(movieResult)
}
}
}
Next step, I'm trying to create a MovieDAO, and here I'll have to instantiate my object, right? So, in the same file as my Movie class is, I've created a MovieDAO class with this function:
class MovieDAO {
func getMovieDetailed<Movie: Mappable>(imdbID: String, completionHandler: #escaping (Movie) -> ()) {
getMovieIdFromAPI(imdbID: imdbID, completionHandler: {
(movieResult) in
let mapper = Mapper<Movie>()
let movieDetailed = mapper.map(movieResult)!
completionHandler(movieDetailed)
})
}
}
But I didn't understood very well the answer and the xcode gives me an error in
let movieDetailed = mapper.map(movieResult)!
^Error: Argument labels '(_:)' do not match any available overloads
Could you explain how can I use the answer given in this case?

ObjectMapper is what helps you get an instance of the model class, with the property values set as per your API response. You will need to do the last step where in you tell ObjectMapper to do the 'mapping' procedure with the json you provide it.You can use this generic method to parse response for any Mappable class
static func parseModel<Model: Mappable>(modelResponse modelResponse: AnyObject, modelClass: Model.Type) -> Model? {
let mapper = Mapper<Model>()
let modelObject = mapper.map(modelResponse)!
return modelObject
}

Related

how to get single variable name from struct

I have a core data framework to handle everything you can do with coredata to make it more cooperateable with codable protocol. Only thing i have left is to update the data. I store and fetch data by mirroring the models i send as a param in their functions. Hence i need the variable names in the models if i wish to only update 1 specific value in the model that i request.
public func updateObject(entityKey: Entities, primKey: String, newInformation: [String: Any]) {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: entityKey.rawValue)
do {
request.predicate = NSPredicate.init(format: "\(entityKey.getPrimaryKey())==%#", primKey)
let fetchedResult = try delegate.context.fetch(request)
print(fetchedResult)
guard let results = fetchedResult as? [NSManagedObject],
results.count > 0 else {
return
}
let key = newInformation.keys.first!
results[0].setValue(newInformation[key],
forKey: key)
try delegate.context.save()
} catch let error {
print(error.localizedDescription)
}
}
As you can see the newInformation param contains the key and new value for the value that should be updated. However, i dont want to pass ("first": "newValue") i want to pass spots.first : "newValue"
So if i have a struct like this:
struct spots {
let first: String
let second: Int
}
How do i only get 1 name from this?
i've tried:
extension Int {
var name: String {
return String.init(describing: self)
let mirror = Mirror.init(reflecting: self)
return mirror.children.first!.label!
}
}
I wan to be able to say something similar to:
spots.first.name
But can't figure out how
Not sure that I understood question, but...what about this?
class Spots: NSObject {
#objc dynamic var first: String = ""
#objc dynamic var second: Int = 0
}
let object = Spots()
let dictionary: [String: Any] = [
#keyPath(Spots.first): "qwerty",
#keyPath(Spots.second): 123,
]
dictionary.forEach { key, value in
object.setValue(value, forKeyPath: key)
}
print(object.first)
print(object.second)
or you can try swift keypath:
struct Spots {
var first: String = ""
var second: Int = 0
}
var spots = Spots()
let second = \Spots.second
let first = \Spots.first
spots[keyPath: first] = "qwerty"
spots[keyPath: second] = 123
print(spots)
however there will be complex (or impossible) problem to solve if you will use dictionary:
let dictionary: [AnyKeyPath: Any] = [
first: "qwerty",
second: 123
]
you will need to cast AnyKeyPath back to WritableKeyPath<Root, Value> and this seems pretty complex (if possible at all).
for path in dictionary.keys {
print(type(of: path).rootType)
print(type(of: path).valueType)
if let writableKeyPath = path as? WritableKeyPath<Root, Value>, let value = value as? Value { //no idea how to cast this for all cases
spots[keyPath: writableKeyPath] = value
}
}

how to pass the API parameter and parameter is in array

How to pass array parameter
Parameter
[
{
"id": 0,
"followerId": 1030,
"followingId": 1033,
"followerName": "string",
"followingName": "string",
"createdDate": "string",
"message": "string"
}
] //how to solve this array
API Function
class func postFollowers(params:[String: Any],success:#escaping([FollowingDataProvider]) -> Void, failure:#escaping (String) -> Void){
var request = RequestObject()
request = Services.servicePostForFollower(param: params)
APIManager.Singleton.sharedInstance.callWebServiceWithRequest(rqst: request, withResponse: { (response) in
if (response?.isValid)!
{
//success()
print(response?.object as! JSON)
success(self.followingJSONarser(responseObject: response?.object as! JSON));
//followingJSONarser(responseObject: response?.object as! JSON)
}
else
{
failure((response?.error?.description)!)
}
}, withError: {
(error) in
failure((error?.description)!)
})
}
Parsing
static func followingJSONarser(responseObject:JSON) -> [FollowingDataProvider]{
var dataProvider = [FollowingDataProvider]()
let jsonDataa = responseObject["data"]
print(jsonDataa)
let newJSON = jsonDataa["data"].arrayValue
print(newJSON)
for item in newJSON{
print(item)
dataProvider.append(FollowingDataProvider(id: item["userId"].intValue, followerId: item["userId"].intValue, followingId: item["followingId"].intValue, followerName: item["userName"].stringValue, followingName: item["followingName"].stringValue, createdDate: item["createdDate"].stringValue, message: item["message"].stringValue))
}
return dataProvider
}`
You can try to combine SwiftyJson with Codable
struct Root: Codable {
let id, followerID, followingID: Int
let followerName, followingName, createdDate, message: String
enum CodingKeys: String, CodingKey {
case id
case followerID = "followerId"
case followingID = "followingId"
case followerName, followingName, createdDate, message
}
}
if let con = response?.object as? JSON {
do {
let itemData = try con.rawData()
let res = try JSONDecoder().decode([Root].self, from: itemData)
print(res)
catch {
print(error)
}
}
Also avoid force-unwraps with json
response?.object as! JSON
Following your code you're trying to parse data from API, you can use SwiftyJSON with Alamofire to create an HTTP request (post,get,put,delete,etc)
You should use arrayObject instead of arrayValue
Your code missing the right definition to data parsing
static func followingJSONarser(responseObject:JSON) -> [FollowingDataProvider]{
var dataProvider = [FollowingDataProvider]()
var itemClass = [ItemClass]()
let jsonDataa = responseObject["data"] as! Dictionary
let newJSON = jsonDataa["data"].arrayObject as! Array
Now create a dataModel class to cast data to it
class ItemsClass:NSObject{
var id:Int = 0
var followerId:Int = 0
var followingId:Int = 0
var followerName:String = ""
var followingName:String = ""
var createdDate:String = ""
var message:String = ""
init(data:JSON) {
self.id = data["userId"].intValue
self.followerId = data["userId"].intValue
self.followingId = data["followingId"].intValue
self.followerName = data["userName"].stringValue
self.followingName = data["followingName"].stringValue
self.createdDate = data["createdDate"].stringValue
self.message = data["message"].stringValue
}
}
for item in newJSON{
dataProvider.append(itemClass)
}
return dataProvider
}`

What's the best way to return a collection of response representable objects in Swift Vapor?

Context:
Recently, I've decided to take up Swift server side development because I think the Vapor framework is extremely cool. I've gotten a bit stuck while experimenting and would like some advice on templating with leaf and vapor.
I've reviewed the documentation several times when it comes to rendering views. Rendering a templated view with variables requires the name of the leaf template and a Response Representable node object containing the variables.
Trying to work out a scenario with templating and the framework itself (because that's how I learn best), I tried to mock a blog format. This is my class/get request:
// MARK: Blog Post Object
final class BlogPost: NodeRepresentable {
var postId: Int
var postTitle: String
var postContent: String
var postPreview: String
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"postId":self.postId,
"postTitle":self.postTitle,
"postContent":self.postContent,
"postPreview":self.postPreview
])
}
init(_ postId: Int, _ postTitle: String, _ postContent: String) {
self.postId = postId
self.postTitle = postTitle
self.postContent = postContent
self.postPreview = postContent.trunc(100)
}
}
// MARK: Blog view request; iterate over blog objects
drop.get("blog") { request in
let result = try drop.database?.driver.raw("SELECT * FROM Posts;")
guard let posts = result?.nodeArray else {
throw Abort.serverError
}
var postCollection = [BlogPost]()
for post in posts {
guard let postId = post["postId"]?.int,
let postTitle = post["postTitle"]?.string,
let postContent = post["postPreview"]?.string else {
throw Abort.serverError
}
let post = BlogPost(postId, postTitle, postContent)
postCollection.append(post)
}
// Pass posts to be tokenized
/* THIS CODE DOESN'T WORK BECAUSE "CANNOT CONVERT VALUE OF TYPE
* '[BLOGPOST]' TO EXPECTED DICTIONARY VALUE OF TYPE "NODE"
* LOOKING FOR THE BEST METHOD TO PASS THIS LIST OF OBJECTS
*/
drop.view.make("blog", [
"posts":postCollection
])
}
and this is my blog.leaf file:
#extend("base")
#export("head") {
<title>Blog</title>
}
#export("body") {
<h1 class="page-header">Blog Posts</h1>
<div class="page-content-container">
#loop(posts, "posts") {
<div class="post-container">
<h3 style="post-title">#(posts["postTitle"])</h3>
<p style="post-preview">#(posts["postPreview"])</h3>
</div>
}
</div>
}
Problem:
As you can see, I'm a bit stuck on finding the best method for iterating over objects and templating their properties into the leaf file. Anyone have any suggestions? Sorry for the bad programming conventions, by the way. I'm fairly new in Object/Protocol Oriented Programming.
What I ended up doing is, making the Post model conform to the Model protocol.
import Foundation
import HTTP
import Vapor
// MARK: Post Class
final class Post: Model {
var id: Node?
var title: String
var content: String
var date: Date
var isVisible: Bool
// TODO: Implement truncate extension for String and set preview
// to content truncated to 100 characters
var preview = "placeholder"
var exists: Bool = false
init(title: String, content: String, isVisible: Bool = true) {
self.title = title
self.content = content
self.date = Date()
self.isVisible = isVisible
}
init(node: Node, in context: Context) throws {
let dateInt: Int = try node.extract("date")
let isVisibleInt: Int = try node.extract("isVisible")
id = try node.extract("id")
title = try node.extract("title")
content = try node.extract("content")
date = Date(timeIntervalSinceNow: TimeInterval(dateInt))
isVisible = Bool(isVisibleInt as NSNumber)
exists = false
}
func makeNode(context: Context) throws -> Node {
return try Node(node: [
"id": id,
"title": title,
"content": content,
"date": Int(date.timeIntervalSince1970),
"isVisible": Int(isVisible as NSNumber)
])
}
static func prepare(_ database: Database) throws {
try database.create("Posts") { posts in
posts.id()
posts.string("title", optional: false)
posts.string("content", optional: false)
posts.int("date", optional: false)
posts.int("isVisible", optional: false)
}
}
static func revert(_ database: Database) throws {
try database.delete("posts")
}
}
Then to return/create instances of the Post object:
import Vapor
import Foundation
import HTTP
final class BlogController {
func addRoutes(_ drop: Droplet) {
let blogRouter = drop.grouped("blog")
let blogAPIRouter = drop.grouped("api","blog")
blogRouter.get("posts", handler: getPostsView)
blogAPIRouter.get("posts", handler: getPosts)
blogAPIRouter.post("newPost", handler: newPost)
}
// MARK: Get Posts
func getPosts(_ request: Request) throws -> ResponseRepresentable {
let posts = try Post.all().makeNode()
return try JSON(node: [
"Posts":posts
])
}
// Mark: New Post
func newPost(_ request: Request) throws -> ResponseRepresentable {
guard let title = request.data["title"]?.string,
let content = request.data["content"]?.string else {
throw Abort.badRequest
}
var post = Post(title: title, content: content)
try post.save()
return "success"
}
// Mark: Get Posts Rendered
func getPostsView(_ request: Request) throws -> ResponseRepresentable {
return try getPosts(request)
}
}
I'm not an expert on Vapor yet, but I think you need to use .makeNode() so your postCollection object get converted to something you can later use on the template.
Something like this:
drop.view.make("blog", ["posts":postCollection.makeNode()])
func list(_ req: Request) throws -> ResponseRepresentable {
let list = try User.all()
let node = try list.makeNode(in: nil)
let json = try JSON(node: [ "list":node ])
return json
}

How can I do array mapping with objectmapper?

I have a response model that looks like this:
class ResponseModel: Mappable {
var data: T?
var code: Int = 0
required init?(map: Map) {}
func mapping(map: Map) {
data <- map["data"]
code <- map["code"]
}
}
If the json-data is not an array it works:
{"code":0,"data":{"id":"2","name":"XXX"}}
but if it is an array, it does not work
{"code":0,"data":[{"id":"2","name":"XXX"},{"id":"3","name":"YYY"}]}
My mapping code;
let apiResponse = Mapper<ResponseModel>().map(JSONObject: response.result.value)
For details;
I tried this code using this article : http://oramind.com/rest-client-in-swift-with-promises/
you need to use mapArray method instead of map :
let apiResponse = Mapper<ResponseModel>().mapArray(JSONObject: response.result.value)
What I do is something like this:
func mapping(map: Map) {
if let _ = try? map.value("data") as [Data] {
dataArray <- map["data"]
} else {
data <- map["data"]
}
code <- map["code"]
}
where:
var data: T?
var dataArray: [T]?
var code: Int = 0
The problem with this is that you need to check both data and dataArray for nil values.
You need to change your declaration of data to an array, since that's how it is in the JSON:
var data: [T]?
let apiResponse = Mapper<ResponseModel>().mapArray(JSONObject: response.result.value)
works for me
Anyone using SwiftyJSON and if you want an object from JSON directly without having a parent class, for example, you want the "data" from it. You can do something like this,
if let data = response.result.value {
let json = JSON(data)
let dataResponse = json["data"].object
let responseObject = Mapper<DataClassName>().mapArray(JSONObject: dataResponse)
}
This will return you [DataClassName]? as response.
Based on Abrahanfer's answer. I share my solution. I wrote a BaseResult for Alamofire.
class BaseResult<T: Mappable> : Mappable {
var Result : Bool = false
var Error : ErrorResult?
var Index : Int = 0
var Size : Int = 0
var Count : Int = 0
var Data : T?
var DataArray: [T]?
required init?(map: Map){
}
func mapping(map: Map) {
Result <- map["Result"]
Error <- map["Error"]
Index <- map["Index"]
Size <- map["Size"]
Count <- map["Count"]
if let _ = try? map.value("Data") as [T] {
DataArray <- map["Data"]
} else {
Data <- map["Data"]
}
}}
The usage for Alamofire :
WebService.shared.request(url, params, encoding: URLEncoding.default, success: { (response : BaseResult<TypeData>) in
if let arr = response.DataArray
{
for year in arr
{
self.years.append(year)
}
}
}, failure: {
})
The request method is :
func request<T: Mappable>(_ url: String,_ parameters: [String : Any] = [:], _ method: HTTPMethod = .post,_ httpHeaders: HTTPHeaders? = nil, encoding: ParameterEncoding = JSONEncoding.default, success: #escaping (T) -> Void, failure: #escaping () -> () ) {
AF.request(newUrl, method:method, parameters:parameters, encoding:encoding, headers: httpHeaders)
.responseJSON { response in
if let res = response.value {
let json = res as! [String: Any]
if let object = Mapper<T>().map(JSON: json) {
success(object)
return
}
}else if let _ = response.error {
failure()
}
}
}
And TypeData class is :
class TypeData : Mappable
{
var Id : String = ""
var Title: String = ""
required init(map: Map){
}
func mapping(map: Map) {
Id <- map["ID"]
Title <- map["YEAR"]
}}

Swift error when trying to access Dictionary: `Could not find member 'subscript'`

This won't compile:
I've tried a couple different things; different ways of declaring the Dictionary, changing its type to match the nested-ness of the data. I also tried explicitly saying my Any was a collection so it could be subscripted. No dice.
import UIKit
import Foundation
class CurrencyManager {
var response = Dictionary<String,Any>()
var symbols = []
struct Static {
static var token : dispatch_once_t = 0
static var instance : CurrencyManager?
}
class var shared: CurrencyManager {
dispatch_once(&Static.token) { Static.instance = CurrencyManager() }
return Static.instance!
}
init(){
assert(Static.instance == nil, "Singleton already initialized!")
getRates()
}
func defaultCurrency() -> String {
let countryCode = NSLocale.currentLocale().objectForKey(NSLocaleCountryCode) as String
let codesToCountries :Dictionary = [ "US":"USD" ]
if let localCurrency = codesToCountries[countryCode]{
return localCurrency
}
return "USD"
}
func updateBadgeCurrency() {
let chanCurr = defaultCurrency()
var currVal :Float = valueForCurrency(chanCurr, exchange: "Coinbase")!
UIApplication.sharedApplication().applicationIconBadgeNumber = Int(currVal)
}
func getRates() {
//Network code here
valueForCurrency("", exchange: "")
}
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"][exchange][currency] as Float
}
}
Let's take a look at
response["current_rates"][exchange][currency]
response is declared as Dictionary<String,Any>(), so after the first subscript you try to call another two subscripts on an object of type Any.
Solution 1. Change the type of response to be a nested dictionary. Note that I added the question marks because anytime you access a dictionary item you get back an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String, Float>>>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
return response["current_rates"]?[exchange]?[currency]
}
Solution 2. Cast each level to a Dictionary as you parse. Make sure to still check if optional values exist.
var response = Dictionary<String,Any>()
func valueForCurrency(currency :String, exchange :String) -> Float? {
let exchanges = response["current_rates"] as? Dictionary<String,Any>
let currencies = exchanges?[exchange] as? Dictionary<String,Any>
return currencies?[currency] as? Float
}
You can get nested dictionary data by following these steps:
let imageData: NSDictionary = userInfo["picture"]?["data"]? as NSDictionary
let profilePic = imageData["url"] as? String
func valueForCurrency(currency :String, exchange :String) -> Float? {
if let exchanges = response["current_rates"] as? Dictionary<String,Any> {
if let currencies = exchanges[exchange] as? Dictionary<String,Any> {
return currencies[currency] as? Float
}
}
return nil
}
response is declared as such:
var response = Dictionary<String,Any>()
So the compiler thinks response["current_rates"] will return an Any. Which may or may not be something that is subscript indexable.
You should be able to define you type with nested Dictionaries, 3 levels and eventually you get to a float. You also need to drill in with optional chaining since the dictionary may or may not have a value for that key, so it's subscript accessor returns an optional.
var response = Dictionary<String,Dictionary<String,Dictionary<String,Float>>>()
// ... populate dictionaries
println(response["current_rates"]?["a"]?["b"]) // The float