How to save data from JSON to model - swift

I have an API, I parse it with alomofire, and I want to save parsed json to Model
This is my request and I want to save chats to ChatInstanceModel.
This is my NetworkingManager class
func getChatList(completionHandler: (#escaping (ChatInstanceModel) -> Void)) {
guard let url = URL(string: urlOfIntanceAPI) else { return }
let headers: HTTPHeaders = [
"Authorization" : "FastToken 8af34fc7c2557517d8a3e747e497de6491a16350df6fa0af8afcbbe72035484efae69d46efa8a47feb75be3250d025b01f42b3b5ad723e5a32afc887c1f6879629b86655ce49e19cada8e289b38d79061160f538c6fa59388076396de1cd2886e2c2e8c5f706ad2bb79a23f034b889c835dd288e44c0cc257d048a22093e51226a60b198bf72aa40a11829771147b4d5"
]
AF.request(url, method: .get, headers: headers).responseData { (responseData) in
switch responseData.result {
case .success(let data):
guard let chats = try? JSONDecoder().decode(ChatInstanceModel.self, from: data) else { return }
completionHandler(chats)
case .failure(let error):
print(error)
}
}
}
This is my model:
struct ChatInstanceModel: Codable {
let title: String
let avatar: String?
let unread_client: Int?
}
This is api which I want to save
{
"pk": 3,
"title": "None",
"avatar": null,
"unread_client": 0,
"unread_manager": 0,
"is_online": false,
"is_typing": false,
"last_seen": null,
"manager": null,
"last_update": null
}

Here is an example of how you can save the ChatInstanceModel instance after the getting the response from API using the completionHandler.
class ViewController: UIViewController {
var chats: ChatInstanceModel?
override func viewDidLoad() {
super.viewDidLoad()
getChatList { (chats) in
self.chats = chats
}
}
}
That's just a simple example for your problem statement currently specified.

Please find an example:
import Alamofire
// Your Model
struct ChatInstanceModel: Codable {
let title: String
let avatar: String?
let unread_client: Int?
}
// Your View Controller
class ViewController: UIViewController {
// Your chat instance
var chats: ChatInstanceModel? = nil
override func viewDidLoad() {
super.viewDidLoad()
getChatList { (chats) in
self.chats = chats
print(self.chats?.title)
}
}
// Get chat list from api
func getChatList(completionHandler: (#escaping (ChatInstanceModel) -> Void)) {
guard let url = URL(string: "<API_URL>") else { return }
let headers: HTTPHeaders = [
"Authorization" : "<TOKEN>"
]
let request = AF.request(url, method: .get, headers: headers)
request.responseDecodable(of: ChatInstanceModel.self) { (dataResponse) in
switch dataResponse.result {
case .success(let data):
completionHandler(data)
case .failure(let error):
print("Error", error.localizedDescription)
}
}
}
}

Related

Parse Wiki Nearby

I'm new to Swift, and trying to figure out how to parse the JSON returned by Wiki Nearby. With the help of https://www.youtube.com/watch?v=sqo844saoC4 here's where I am right now:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=37.7891838%7C-122.4033522&gsradius=10000&gslimit=2&format=json"
getData(from: url)
}
private func getData(from url: String) {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: {data, response, error in
guard let data = data, error == nil else {
print("Something went wrong")
return
}
var result: Response?
do {
result = try JSONDecoder().decode(Response.self, from: data)
}
catch {
print("failed to convert \(error.localizedDescription) ")
}
guard let json = result else {
return
}
print(type(of: json.query.geosearch))
print(json.query.geosearch.self)
})
task.resume()
}
}
struct Response: Codable {
let batchcomplete: String
let query: Query
}
struct Query: Codable {
let geosearch: [Geosearch]?
}
struct Geosearch: Codable {
let pageid: Int?
let title: String?
}
Following Need help parsing wikipedia json api in SWIFT I believe that geosearch is a dictionary. The code runs, but how do I retrieve the title or pageid from geosearch (print(json.query.geosearch.title) gives an error)?
Sorry - probably something really basic... any pointers would be appreciated.
Philipp
try the following code. It uses a completion closure to "wait" until the results are fetched before
returning them. Since geosearch is an array you need to loop through it. Note, the json response is parsed without errors. Works for me.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = "https://en.wikipedia.org/w/api.php?action=query&list=geosearch&gscoord=37.7891838%7C-122.4033522&gsradius=10000&gslimit=2&format=json"
getData(from: url) { response in
if let result = response,
let geosearch = result.query.geosearch {
print("-----> result: \(result) \n")
for geo in geosearch {
print("geo.title: \(geo.title)")
}
}
}
}
func getData(from url: String, completion: #escaping(Response?) -> ()) {
if let theURL = URL(string: url) {
let task = URLSession.shared.dataTask(with: theURL) { data, response, error in
guard let data = data, error == nil else {
print("Something went wrong")
completion(nil)
return
}
do {
let result = try JSONDecoder().decode(Response.self, from: data)
completion(result)
} catch {
print("failed to convert \(error) ")
completion(nil)
}
}
task.resume()
}
}
}
You need to access the item of array to get the pageid or title like:
print(json.query.geosearch?[0].title ?? "")
Since the json returns an array
{
"batchcomplete": "",
"query": {
"geosearch": [
{
"pageid": 18618509,
"ns": 0,
"title": "Wikimedia Foundation",
"lat": 37.78916666666667,
"lon": -122.40333333333334,
"dist": 2.5,
"primary": ""
},
{
"pageid": 42936625,
"ns": 0,
"title": "Foxcroft Building",
"lat": 37.78916666666667,
"lon": -122.40333333333334,
"dist": 2.5,
"primary": ""
}
]
}
}
print(json.query.geosearch.self) //This will store the repsone in the let geosearch: [Geosearch]?
//You can check the individual result for the 1st and 2nd object
print(json.query.geosearch[0].pageid)//18618509
print(json.query.geosearch[0].title) //"Wikimedia Foundation"
print(json.query.geosearch[1].pageid)//42936625
print(json.query.geosearch[1].title) //"Foxcroft Building"

keyNotFound CodingKeys when interacting with API

I am working with the TMDB api and I'm building a networking layer to do this. I'm following a tutorial as well which I'm pretty sure I followed correctlly. I'm calling this API Endpoint:
{"page":1,
"results":[{"adult":false,"backdrop_path":"/fev8UFNFFYsD5q7AcYS8LyTzqwl.jpg","genre_ids":[16,28,35,10751],
"id":587807,
"original_language":"en","original_title":"Tom & Jerry",
"overview":"Jerry moves into New York City's finest hotel on the eve of the wedding of the century, forcing the desperate event planner to hire Tom to get rid of him. As mayhem ensues, the escalating cat-and-mouse battle soon threatens to destroy her career, the wedding, and possibly the hotel itself.",
"popularity":4136.493,
"poster_path":"/6KErczPBROQty7QoIsaa6wJYXZi.jpg",
"release_date":"2021-02-12",
"title":"Tom & Jerry",
"video":false,
"vote_average":8,
"vote_count":541}]
}
Unfortunately I'm getting this error:
keyNotFound(CodingKeys(stringValue: "backdrop_path", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "results", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"backdrop_path\", intValue: nil) (\"backdrop_path\").", underlyingError: nil))
This applies to every key in my Resulting struct.
Here is the method in my ViewController:
func fetchUpComing() {
let api = MovieDB.api
api.send(request: .upcomingMovies(completion: { result in
print("result", result)
switch result {
case .success(let page):
print(page.results)
self.upcomingMovies = page.results
var basicSection = MovieSection()
basicSection.numberOfItems = self.upcomingMovies.count
basicSection.upcomingItems = page.results
self.sections = [TitleSection(title: "Upcoming Movies"), basicSection]
self.setupCollectionView()
case .failure(let error): print(error)
}
}))
}
MovieDB:
struct MovieDB { // logic specific to the TMDB API
public static let baseURL = URL(string: "https://api.themoviedb.org/3/")!
public static var api: APIClient = {
let configuration = URLSessionConfiguration.default
let apiKey = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmMzM2Yzk4MmY0MzdhMGQ1OTQ1MDFlY2U1YTA0ZmI3NiIsInN1YiI6IjYwM2ZjMTg5OTdlYWI0MDA3NzViYWIyMCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.J5hW42SMtm7RBeVGy9Z_Ex"
configuration.httpAdditionalHeaders = [
"Authorization": "Bearer"
]
return APIClient(configuration: configuration)
}()
public static let baseImageURL = URL(string: "https://image.tmdb.org/t/p/w500")!
}
public extension Result where Success == Data, Failure == Error {
func decoding<M: Model>(_ model: M.Type, completion: #escaping (Result<M, Error>) -> Void) {
// decode the JSON in the background and call the completion block on the main thread
DispatchQueue.global().async {
//Result’s flatMap() method takes the successful case (if it was successful) and applies your block. You can return a new Result that contains a successful value or an error.
let result = self.flatMap { data -> Result<M, Error> in
do {
let decoder = M.decoder
let model = try decoder.decode(M.self, from: data)
return .success(model)
} catch {
print("Error: \(error) with :\(String(data: data, encoding: .utf8))")
return .failure(error)
}
}
DispatchQueue.main.async {
completion(result)
}
}
}
API Client:
private let session: URLSession
init(configuration: URLSessionConfiguration) {
session = URLSession(configuration: configuration)
}
public func send(request: Request) {
let urlRequest = request.builder.toURLRequest()
let task = session.dataTask(with: urlRequest) { data, response, error in
let result: Result<Data, Error>
if let error = error {
result = .failure(error)
} else {
result = .success(data ?? Data())
}
DispatchQueue.main.async {
request.completion(result)
}
}
task.resume()
}
}
Request:
public struct Request {
let builder: RequestBuilder
let completion: (Result<Data, Error>) -> Void
init(builder: RequestBuilder, completion: #escaping (Result<Data, Error>) -> Void) {
self.builder = builder
self.completion = completion
}
public static func basic(method: HTTPMethod = .get, baseURL: URL, path: String, params: [URLQueryItem]? = nil, completion: #escaping (Result<Data, Error>) -> Void) -> Request {
let builder = BasicRequestBuilder(method: method, baseURL: baseURL, path: path, params: params)
return Request(builder: builder, completion: completion)
}
}
extension Request {
static func popularMovies(completion: #escaping (Result<PagedResults<Movie>, Error>) -> Void) -> Request {
Request.basic(baseURL: MovieDB.baseURL, path: "discover/movie", params: [
URLQueryItem(name: "sort_by", value: "popularity.desc")
]) { result in
result.decoding(PagedResults<Movie>.self, completion: completion) }
}
static func upcomingMovies(completion: #escaping (Result<Upcoming<Resulting>, Error>) -> Void) -> Request {
Request.basic(baseURL: MovieDB.baseURL, path: "movie/upcoming", params: [
URLQueryItem(name: "sort_by", value: "popularity.desc")
]) { result in
result.decoding(Upcoming<Resulting>.self, completion: completion) }
}
}
RequestBuilder:
public enum HTTPMethod: String {
case get
case post
case put
case delete
}
public protocol RequestBuilder {
var method: HTTPMethod { get }
var baseURL: URL { get }
var path: String { get }
var params: [URLQueryItem]? { get }
var headers: [String: String] { get }
func toURLRequest() -> URLRequest
}
public extension RequestBuilder {
func toURLRequest() -> URLRequest {
var components = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false)!
components.queryItems = params
let url = components.url!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = headers
request.httpMethod = method.rawValue.uppercased()
return request
}
}
struct BasicRequestBuilder: RequestBuilder {
var method: HTTPMethod
var baseURL: URL
var path: String
var params: [URLQueryItem]?
var headers: [String: String] = [:]
}
Here is my struct used in decoding:
import Foundation
struct Movie: Model, Hashable {
let id: Int
let title: String
let posterPath: String
let releaseDate: String
}
struct PagedResults<T: Model>: Model {
let page: Int
let totalPages: Int
let results: [T]
}
extension PagedResults {
static var decoder: JSONDecoder { T.decoder }
}
struct Upcoming<T: Model>: Model {
let dates: Dates
let page: Int
let results: [Resulting]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case dates, page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
extension Upcoming {
static var decoder: JSONDecoder { T.decoder }
}
// MARK: - Dates
struct Dates: Codable {
let maximum, minimum: String
}
// MARK: - Result
struct Resulting: Model, Hashable{
let adult: Bool
let backdropPath: String
let genreIDS: [Int]
let id: Int
let originalLanguage: OriginalLanguage
let originalTitle, overview: String
let popularity: Double
let posterPath, releaseDate, title: String
let video: Bool
let voteAverage: Double
let voteCount: Int
enum CodingKeys: String, CodingKey {
case adult
case backdropPath = "backdrop_path"
case genreIDS = "genre_ids"
case id
case originalLanguage = "original_language"
case originalTitle = "original_title"
case overview, popularity
case posterPath = "poster_path"
case releaseDate = "release_date"
case title, video
case voteAverage = "vote_average"
case voteCount = "vote_count"
}
}
enum OriginalLanguage: String, Codable {
case en = "en"
case es = "es"
case ja = "ja"
}

How can I write a generic wrapper for alamofire request in swift?

How can I write a generic wrapper for alamofire request ?
How can I convert the POST and GET function to Generic function in the following code?
I need to have generic request functions that show different behaviors depending on the type of data received.
Also, Can the response be generic?
My non-generic code is fully outlined below
import Foundation
import Alamofire
import SwiftyJSON
// for passing string body
extension String: ParameterEncoding {
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var request = try urlRequest.asURLRequest()
request.httpBody = data(using: .utf8, allowLossyConversion: false)
return request
}
}
public class ConnectionManager {
func Post (FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .post, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
guard let data = response.result.value else { return }
print(data)
completion(true)
} else {
print("error reg auth service")
guard let er = response.result.value else { return }
print(er)
completion(false)
debugPrint(response.result.error as Any)
}
}
}
func get(FirstName: String, LastName: String, NationalID: String, NationalCode: String, BirthDate: Date,Image: String,isFemale: Bool,Age:Int64,Avg:Double, completion: #escaping CompletionHandler) -> [Person] {
let body: [String:Any] = [
"FirstName":FirstName,
"LastName": LastName,
"NationalID": NationalID,
"NationalCode": NationalCode,
"BirthDate":BirthDate,
"Image": Image,
"isFemale": isFemale,
"Age": Age,
"Avg": Avg
]
Alamofire.request(BASE_URL, method: .get, parameters: body, encoding: JSONEncoding.default, headers: HEADER).responseJSON { (response) in
if response.response?.statusCode == 200 {
print("no error login in authservice")
guard let data = response.result.value else { return }
print(data)
completion(true)
}
else if response.response?.statusCode == 400 {
print("error 400 login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
} else {
print("error ## login in authservice")
guard let er = response.result.value else { return }
print(er)
debugPrint(response.result.error as Any)
completion(false)
}
}
return [Person]()
}
}
The best idea is to use the URLRequestConvertible Alamofires protocol and create your own protocol and simple structs for every API request:
import Foundation
import Alamofire
protocol APIRouteable: URLRequestConvertible {
var baseURL: String { get }
var path: String { get }
var method: HTTPMethod { get }
var parameters: Parameters? { get }
var encoding: ParameterEncoding { get }
}
extension APIRouteable {
var baseURL: String { return URLs.baseURL }
// MARK: - URLRequestConvertible
func asURLRequest() throws -> URLRequest {
let url = try baseURL.asURL().appendingPathComponent(path)
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue
return try encoding.encode(urlRequest, with: parameters)
}
}
and request can look like this:
struct GetBooks: APIRouteable {
var path: String
var method: HTTPMethod
var parameters: Parameters?
var encoding: ParameterEncoding
}
and inside the APIClient prepare the generic method:
func perform<T: Decodable>(_ apiRoute: APIRouteable,
completion: #escaping (Result<T>) -> Void) {
let dataRequest = session
.request(apiRoute)
dataRequest
.validate(statusCode: 200..<300)
.responseDecodable(completionHandler: { [weak dataRequest] (response: DataResponse<T>) in
dataRequest.map { debugPrint($0) }
let responseData = response.data ?? Data()
let string = String(data: responseData, encoding: .utf8)
print("Repsonse string: \(string ?? "")")
switch response.result {
case .success(let response):
completion(.success(response))
case .failure(let error):
completion(.failure(error))
}
})
}
and use it:
func getBooks(completion: #escaping (Result<[Book]>) -> Void) {
let getBooksRoute = APIRoute.GetBooks()
perform(getBooksRoute, completion: completion)
}

unable to load JSON data in 'data' array

when I call this function the array 'data' is showing empty square brackets, it is not giving me any errors though
here is the code :-
import Foundation
import Alamofire
import SwiftyJSON
class Networking {
var data = [Item]()
let tVC = TableViewController()
let url = "https://api.coinmarketcap.com/v1/ticker/"
func getCoinData(url: String) {
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let coinJSON : JSON = JSON(response.result.value!)
for i in 0..<coinJSON.count{
let coinName = Item(bitJSON: coinJSON[i])
self.data.append(coinName)
self.tVC.tableView.reloadData()
}
}
else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}
Try this one.
Change your url, method is "Get" and your mapping model.
public static func getArrayInformation(completionHandler: #escaping (APIResponse) -> Void) {
let url = "your url"
let params: Parameters = ["username": "admin",
"password": "1234"]
Alamofire.request(url,
method: .post,
parameters: params,
encoding: JSONEncoding.default,
headers: ["Content-Type": "application/json"])
.responseDecodableObject(keyPath: nil, decoder: JSONDecoder(), completionHandler: { (handler: DataResponse<[Your Object Array]>) in
completionHandler(handler.result.value ?? [Your Object Array]())
})
}
why you reload Table every loop iteration , and instead Loop use Map
class Networking {
var data = [Item]()
let tVC = TableViewController()
let url = "https://api.coinmarketcap.com/v1/ticker/"
func getCoinData(url: String) {
Alamofire.request(url, method: .get)
.responseJSON { response in
if response.result.isSuccess {
let coinJSON : JSON = JSON(response.result.value!)
data = coinJSON.map({ (coinJson) -> Item in
return Item(bitJSON: coinJson)
})
DispatchQueue.main.async {
self.tVC.tableView.reloadData()
}
}
else {
print("Error: \(String(describing: response.result.error))")
}
}
}
}

Swift using Alamofire to make a http request

I got a problem in using Alamofire recently.
Here is my code
LoginViewController.swift
class LoginViewController: UIViewController {
#IBOutlet weak var name: UITextField!
#IBOutlet weak var email: UITextField!
#IBOutlet weak var password: UITextField!
let baseApi = BaseApi()
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func loginButton(_ sender: UIButton) {
let dict = ["name":name.text,"email":email.text,
"password":password.text]
print("api succeed1")
let result = baseApi.login(paras: dict as! [String : String])
print("api succeed2")
if result[0]["status"].string == "success" {
print("api succeed3")
present( UIStoryboard(name: "Main", bundle:
nil).instantiateViewController
(withIdentifier:"TabBarController")
as! TabBarController, animated: true, completion: nil)
}
}
BaseApi.swift
class BaseApi{
func login(paras : [String:String]) -> JSON {
let url = URL(string: "http://127.0.0.1:8000/api/login")
let result = baseApi(url: url!,paras: paras)
print("BaseApi3333")
return result
}
func baseApi(url : URL,paras : [String:String]) -> JSON {
var json:JSON = []
let toke = getToken()
let parameters: Parameters = [
"name": paras["name"]!,
"email": paras["email"]!,
"password": paras["password"]!
]
let headers: HTTPHeaders = [
"Authorization": "Basic "+toke,
"Accept": "application/json"
]
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success(let value):
json = JSON(value)
print("baseAp2222")
print(json)
case .failure(let error):
print(error)
}
}
print("baseApi111")
print(json)
return json
}
}
Here is the log
api succeed1
baseApi111
[
]
BaseApi3333
api succeed2
baseAp2222
{
"status_code" : 200,
"status" : "success",
"data" : {
"token" : "xxxx"
}
}
My question is why print(baseApi111) come out before print("baseAp2222"),I need to return json,but looks like the excute orders are not right,so the return json is nil,how should I solve this problem?
You need to use callback closures to make a return call , you can not return data like this from api calls .
Let me give you an example - following method is making call to api using almofire -
func fetchDataFromWebService<T: Mappable>(_ parameters: Dictionary<String , AnyObject>, closure:#escaping (_ response: T) -> Void){
let url = getWebServiceUrl()
// let url = NSURL(string: getWebServiceUrl)
print("parameters = \(parameters)")
Alamofire.request(url, method: .get, parameters: parameters, headers: nil).responseJSON { (response:DataResponse<Any>) in
switch(response.result) {
case .success(_):
if response.response?.statusCode == 200 || response.response?.statusCode == 201 {
// print(response.result.value)
var user = Mapper<T>().map(JSONObject: response.result.value)
// var user = Mapper<T>().map(response.result.value)
if self.processSingleRecord() == true {
user = Mapper<T>().map(JSONObject: (response.result.value as! NSArray).object(at: 0))
// user = Mapper<T>().map(response.result.value?.objectAtIndex(0))
}
closure(user!)
// print("user = ",user)
}
else if response.response?.statusCode == 0{
// print(self.DisplayNetworkAvailabilityMessage())
}
else {
if let _ = response.result.value as? Error {
}
}
break
case .failure(let error):
debugPrint("getEvents error: \(error)")
SVProgressHUD.dismiss()
break
}
}
}
Now here is call to this method -
let anotherWebServiceHandler = DeviceTokenDataHandler.init() anotherWebServiceHandler.fetchDataFromWebService(["":""], closure: { (response:SignUpResponse) -> Void in
})
You need to understand sequential code execution - and Closures