Mapping allHeaderFields in AlamofireObjectMapper response - ios

I am using AlamofireObjectMapper
and response from my server is in JSON but it also include some key in the headers
Alamofire.request(URL).responseObject { (response: DataResponse<MyMappable>) in
let weatherResponse = response.result.value
print(weatherResponse?.location)
}
and my map able class is like this
class MyMappable: Mappable {
var location: String?
required init?(map: Map){
}
func mapping(map: Map) {
location <- map["location"]
}
}
all of the above code is working but my problem is I want to map some value is header, I can get these header in the response only
let allHeaders = response.response?.allHeaderFields
if let headers = allHeaders as? [String: String], let someValue = headers["key"]
{
print("my value is: \(someValue)")
}
but I want to map it in the MyMappable class, is it possible ?

I could not find a way to map header Fields with payload
what I could find was, we can get all header fields from response and store it in our class in two ways.
first one is like this
Alamofire.request(URL).responseObject { (response: DataResponse<MyMappable>) in
let myMappable = response.result.value
myMappable.allHeaderFields = response.response?.allHeaderFields
print(myMappable?.location)
}
and the second way (which one used by me) if you are using generic class as parent then you can cast your response like blow
Alamofire.request(URL).responseObject { (response: DataResponse<MyMappable>) in
let myMappable = response.result.value
if let genericResp = myMappable as? GenericResponse<AnyGeneric> {
genericResp.mHeaders = response.response?.allHeaderFields
}
}

Related

Google Books API parsing

So I'm trying to parse Google Books API respond. I want to get title, description, thumbnailUrl, authors and published data. Here is the problem :
func getBooksFrom(completion: #escaping (Result<[[String: AnyObject]]>) -> Void) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else { return completion(.Error(error!.localizedDescription)) }
guard let data = data else { return
completion(.Error(error!.localizedDescription)) }
do {
if let json = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers]) as? [String: AnyObject] {
if let items = json["items"] as? [[String: AnyObject]] {
DispatchQueue.main.async {
completion(.Succes(items))
}
}
}
} catch let error {
print(error.localizedDescription)
return completion(.Error(error.localizedDescription))
}
}.resume()
}
And on my View Controller in the ViewDidLoad i have
let service = ApiService()
service.getBooksFrom { (result) in
switch result {
case .Succes(let data):
self.parseData(array: data)
case .Error(let message):
self.showAlertWith(title: "Error", and: message)
}
}
So that's pretty simple parsing, but...
When I want to map items into Book Object i have to :
func parseData(_ data: [[String: AnyObject]]) -> [Book]{
for item in data {
if let volumeInfo = item["volumeInfo"] as? [String: AnyObject] {
let books = data.map { (jsonDictionary) -> Book in
let title = volumeInfo["title"] as? String ?? ""
let publishedData = volumeInfo["publishedDate"] as? String ?? ""
let authors = volumeInfo["authors"] as? [String] ?? [""]
let description = volumeInfo["description"] as? String ?? ""
let newBook = Book(title: title, publishedData: publishedData, description: description)
return newBook
}
return books
}
}
return [Book]()
}
Which is super awful way to do it.. You have to return Book on the bottom, because of the for-loop, and
VolumeInfo is next Dictionary, so I really don't know exactly how to map it and get for example authors, because it's next Array..
One sample JSON object:
{
"items":[
{
"volumeInfo":{
"title":"The Ancestor's Tale",
"subtitle":"A Pilgrimage to the Dawn of Life",
"authors":[
"Richard Dawkins",
"Yan Wong"
]
"publishedDate":"2016-04-28",
"description":"A fully updated ",
"imageLinks":{
"smallThumbnail":"http://books.google.com/books/content?id=vzbVCQAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail":"http://books.google.com/books/content?id=vzbVCQAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
}
}
]}
So this is quite simple when you have array of String : Value, but how should you map in proper way, when you have for example dictionaries in dictionary VolumeInfo or array of strings like authors?
I personally find the way to parse objects in swift with URLSession relatively clumsy. Whenever I can I use Alamofire in combination with the AlamofireObjectMapper.
This allows you to create a simple object. For example:
class Book: Mappable {
var title: String?
var subtitle: String?
var description: String?
required init?(map: Map){
}
func mapping(map: Map) {
title <- map["title"]
subtitle <- map["subtitle"]
description <- map["description"]
}
}
When you make a request, you can then use the responseObject method to directly parse your object and assign the proper types.
Alamofire.request(URL).responseObject { (response: DataResponse<Book>) in
let book = response.result.value
print(book?.title)
}
For this example, I simply parsed only one book. But the concept can also be easily extended to arrays or nested json objects. I personally find this leads to much cleaner code than using URLSession directly.

Want to display API data to labels (Swift, Alamofire)

I am using Alamofire to call the Riot API and I want to display the information that it has called. I have the get request working, I just don't know how to link to a label in the application. I have included screenshots of the code!
Code
Response
It is just a simple app I am creating!
func callAlamo(url: String){
Alamofire.request(url).responseJSON(completionHandler: {
response in
self.pasrseData(JSONData: response.data!)
})
}
func parseData(JSONData: Data){
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as? JSONStandard
print(readableJSON)
}
catch {
print(error)
}
}
No need to serialize since responseJSONfrom Alamofire has done it. Since I don't know what is inside of your JSON object, let's say that you get a return of age and name:
struct InfoModel { // this struct will decompose our JSON object from the response that we get
var age:Int
var name:String
init(json:Dictionary<String,Any>?) {
guard let dict = json,
let age = dict["age"] as? Int,
let name = dict["name"] as? String
else {fatalError() }
self.age = age
self.name = name
}
}
func parse(url: String, completion:#escaping (InfoModel)-> Void) {
Alamofire.request(url).responseJSON {response in
// get the JSON dictionary
if let JSON = response.result.value {
// no need to decompose your object since your struct does it inside of its initializer
completion(InfoModel(json: JSON as? Dictionary<String, Any>))
}
}
}
// call this function anywhere
parse(url: "") { (m:InfoModel) in
print("age= \(m.age), name= \(m.name)")
// now populate your label
labelOne.text = "\(m.age)"
labelTwo.text = name
}
You set the text property of the label in the completion block, basically:
func callAlamo(url: String){
Alamofire.request(url).responseJSON(completionHandler: {
response in
// here we say get me a non optional data (otherwise don't do the if)
if let data = response.data {
// here we are saying if you can't get me a value (i.e. not nil) for:
// json (note the try? will give nil if there is an error)
// name, we get the name out of the json dictionary
// then go to the else block, where we exit the function
// Happy case where we values for json and name we now have non optional variables W00t
guard
let json = try? self.parseData(JSONData: data),
let name = json["name"] as? String
else {
print("name does not exist in json: \(json)")
return
}
// Time to set the label
self.name.text = name
}
})
}
// Changed this to return JSON as a dictionary (it looks like what you printed was a dictionary)
// I also changed this so it throws the error and doesn't deal with it.
// It probably doesn't know what to do if it can't read json something
// else should handle the error higher up the stack
func parseData(JSONData: Data) throws -> [String: Any]? {
return try JSONSerialization.jsonObject(with:
JSONData, options: .mutableContainers) as? [String: Any]
}
NB: This is untested if your having problems and I'll go for a tested solution.
Edit: Answering how to get another property.
The way we got "name" was this chunk of code:
guard
let json = try? self.parseData(JSONData: data),
let name = json["name"] as? String
else {
print("name does not exist in json: \(json)")
return
}
To get another property out we could do this:
guard
let json = try? self.parseData(JSONData: data),
let name = json["name"] as? String,
let summonerLevel = json["summonerLevel"] as? Int
else {
print("name does not exist in json: \(json)")
return
}
Then to display summonerLevel we do the same as with name (although we have an int not a String)
// Time to set the label
self.name.text = name
// (you will have to create this new label)
self.summonerLevelLabel.text = "Level: \(summonerLevel)"

Can´t parse JSON with AlamoFire and SwiftyJSON

I am stuck with parsing JSON with AlamoFire and SwiftyJSON for iOS. I have a JSON such as this one:
[{"id":23561,"name":"RFI - Persan رادیو صدای Ùرانسه Ùارسی","country":"FR","image":{"url":null,"thumb":{"url":null}},"slug":"rfi-persan-رادیو-صدای-Ùرانسه-Ùارسی","website":"rfi","twitter":"","facebook":"","categories":[{"id":21,"title":"News","description":"","slug":"news","ancestry":"4"}],"streams":[{"stream":"http://rfi-persan.scdn.arkena.com/rfienpersan.mp3","bitrate":0,"content_type":"audio/mpeg","status":1}],"created_at":"2016-01-12T07:52:08+01:00","updated_at":"2016-08-02T01:52:50+02:00"}]
This is what I´ve tried so far, but doesn't work:
func loadSomeJSONData() {
Alamofire.request(.GET, "http://example.com/json/")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
if let Name = json["name"].string {
println("name: \(firstName)") // Name should equal "RFI"
}
}
}
But for some reason it doesn't get name from json object.
Thank you very much!
Your json is Array not Dictionary, so access the json this way
if let arr = json.arrayObject as? [[String:AnyObject]] {
if let name = arr[0]["name"] as? String {
println("name: \(name)") // Name should equal "RFI"
}
}

Alamofire completion handler issue

I have a POST request with certain data that I would like to receive. However when I use responseArray I would be thrown this error
json data is nil
but when I use responseJSON everything would be fine. Why is this so?
This code does not work:
Alamofire.request(.POST, Data.todoEndpoint, parameters: parameters)
.responseArray { (response: Response<[Particulars], NSError>) in
print(response.request)
print(response.response)
print(response.result)
if let result = response.result.value
{
do{
print(Realm.Configuration.defaultConfiguration.fileURL)
let realm = try Realm()
realm.add(result, update: true)
}
catch let err as NSError {
print("Error with realm: " + err.localizedDescription)
}
}
else
{
print("JSON data is nil.")
}
}
But this is fine:
Alamofire.request(.POST, Data.todoEndpoint, parameters: parameters)
.responseJSON { response in
print(response.request)
print(response.response)
print(response.result)
if let result = response.result.value
{
print(result)
}
else
{
print("JSON data is nil.")
}
}
I need responseArray so that I can have (response: Response<[Particulars], NSError>) and store my JSON response into realm
UPDATE
This is the Particulars class that I want to connect to. I'm trying to map my JSON objects to Realm based on this article https://blog.hyphe.me/realm-and-alamofire-in-a-effective-harmony/
import Foundation
import RealmSwift
import ObjectMapper
class Particulars: Object, Mappable {
dynamic var name = ""
dynamic var email = ""
dynamic var id = ""
dynamic var profilePicture = ""
dynamic var username = ""
dynamic var apiToken = ""
override static func primaryKey() -> String? {
return "id"
}
//Impl. of Mappable protocol
required convenience init?(_ map: Map) {
self.init()
}
func mapping(map: Map) {
id <- map["id"]
name <- map["name"]
email <- map["email"]
profilePicture <- map["profile_picture"]
username <- map["username"]
apiToken <- map["api_token"]
}
}
And this is the JSON response:
[
"name" : "Jonny Walker",
"api_token" : "qwertyuiop1234567890",
"profile_picture" : "http:default_profile_picture.jpg",
"id" : 10,
"email" : "jwalker#gmail.com",
"username" : "jonny"
]
UPDATE 2
My completion handler is working fine with responseObject but my realm.add xxx is throwing up this error
Cannot convert value of type 'String' to expected argument type 'Object'
My code can be found here https://codeshare.io/v4M9M (lines 19- 25)
The Alamofire page shows how to handle response and does not list the responseArray method. https://github.com/Alamofire/Alamofire#response-serialization
You can use the responseJSON to get the JSON and convert into an array that you want. It would look something like this, (make changes to this based on your JSON response)
Alamofire.request(.POST, Data.todoEndpoint, parameters: parameters)
.responseJSON { response in
guard response.result.isSuccess else
{
//handle error
return
}
guard let value = response.result.value as? [String: AnyObject],
particularsArrayJson = value["particulars"] as? [[String: AnyObject]]
else{
//Malformed JSON, handle this case
}
var particulars = [Particulars]()
for particularsDict in paricularsArrayJson{
particulars.append(Pariculars(json:particularsDict))
}
}
You have to have an initializer in your Particulars that will initialise from the JSON provided.
Update:
The realm add method takes an instance of a class which extends from Object
Object is a class provided by Realm. So you may have to read up the documentation more.
You should be doing this instead
realm.add(particulars, updated:true)

Extract JSON data from feed using Alamofire

I am using Alamofire to parse a JSON API, however, I can't figure out how to parse the response data from Alamofire.
When I try to loop through the fetched data, XCode gives me "Segmentation Fault: 11" error.
Here is my current code:
var tableData:NSArray // I have tried several variable types, NSDictionary, String etc.
--
override func viewDidLoad() {
super.viewDidLoad()
self.getJsonData()
}
func getJsonData() {
Alamofire.request(.GET, "https://hotell.difi.no/api/json/mattilsynet/smilefjes/tilsyn", parameters: [:])
.responseJSON { response in
if let JSON = response.result.value {
// print("JSON: \(response.result)")
for entry in JSON["entries"] {
print("\(entry)") // this is where everything crashes
}
}
self.doTableRefresh()
}
}
func doTableRefresh() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
What is the correct data format for this JSON result: https://hotell.difi.no/api/json/mattilsynet/smilefjes/tilsyn ? And how do I take the data and populate the tableview?
Convert the response to NSDictionary and NSArray:
func getJsonData() {
Alamofire.request(.GET, "https://hotell.difi.no/api/json/mattilsynet/smilefjes/tilsyn", parameters: [:])
.responseJSON { response in
if let JSON = response.result.value as? NSDictionary{
if let entries = JSON["entries"] as? NSArray{
for entry in entries {
if let entry = entry as? NSDictionary {
for (key, value) in entry {
print("\(key) - \(value)")
}
}
}
}
}
}
}
Without using SwiftyJSON, the idea is to know the right type for each object and successfully downcast.
response.result.value is a dictionary: [String:AnyObject], and the content of json["entries"] is an array of dictionaries: [[String:AnyObject]]. Etc.
Example:
func getJsonData() {
Alamofire.request(.GET, "https://hotell.difi.no/api/json/mattilsynet/smilefjes/tilsyn", parameters: [:])
.responseJSON { response in
if let json = response.result.value as? [String:AnyObject] {
if let entries = json["entries"] as? [[String:AnyObject]] {
for entry in entries {
print(entry) // each entry is a dictionary of type [String:AnyObject]
}
// example of accessing an entry:
if let firstEntry = entries.first, value = firstEntry["adrlinje1"] as? String {
print(value) // "Christian IV gate 3"
}
}
}
}
}
You have to specify the type of your expected value (With SwiftyJSON):
for entry in JSON["entries"] { // Here
print(entry.stringValue)
}

Resources