I try to map a string response with object mapper by my result base.
this is result class :
import ObjectMapper
class Result< T : Mappable > : Mappable {
var data: T?
var status: String?
var message: String?
var error: String?
init?(data: T?, status: String?, error: String?){
self.data = data
self.status = status
self.error = error
}
required init?(map: Map){
}
func mapping(map: Map) {
data <- map["data"]
status <- map["status"]
message <- map["message"]
error <- map["error"]
}
}
and also this is my network class:
import Foundation
import ObjectMapper
final class Network<T:Mappable>{
init() {
}
open func requestItem(_ router: BaseRouter, completionHandler: #escaping (Any?, Error?) -> Void) {
APIClient.Instance.requestJSON(router) { (response, error) in
if let error = error {
completionHandler(nil, APIError(code:ErrorCode.NetworkFailed, message:error.localizedDescription))
}
else if let json = response {
var convertedString : String?
do {
let data1 = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted)
convertedString = String(data: data1, encoding: String.Encoding.utf8)
print(convertedString!.description)
} catch let myJSONError {
print(myJSONError)
}
let result : Result<T>? = Mapper<Result<T>>().map(JSONString: convertedString!)
if let success = result?.status, success == "success" {
completionHandler(result?.data, nil)
}
else {
completionHandler(nil, APIError(code:ErrorCode.HandledInternalError, message:(result?.error)!))
}
}
else {
completionHandler(nil, APIError(code:ErrorCode.EmptyJSONException, message:"Empty JSON Exception"))
}
}
}
}
the response is :
{
"status" : "success",
"data" : "01CPSE6AQXVK554MTGENETKW24"
}
I try to map it but because of String is not a mappable class, I can not do. map["data"] variable should assign only string, not another complex class. Is there anyone can help me about this problem?
Finally error is :
Finally I can relies that what the wrong is with this code.
extension String : Mappable {
}
That's all.
Related
I'm new to SwiftUI and have worked through the server requests and JSON. I now need to programmatically transition to a new view which is where I get stuck with a "Cannot find 'json' in scope" error on the NavigationLink in ContentView.swift. I've watched videos and read articles but nothing quite matches, and everything I try just seems to make things worse.
JSON response from server
{"status":{"errno":0,"errstr":""},
"data":[
{"home_id":1,"name":"Dave's House","timezone":"Australia\/Brisbane"},
{"home_id":2,"name":"Mick's House","timezone":"Australia\/Perth"},
{"home_id":3,"name":"Jim's House","timezone":"Australia\/Melbourne"}
]}
JSON Struct file
import Foundation
struct JSONStructure: Codable {
struct Status: Codable {
let errno: Int
let errstr: String
}
struct Home: Codable, Identifiable {
var id = UUID()
let home_id: Int
let name: String
let timezone: String
}
let status: Status
let data: [Home]
}
ContentView file
import SwiftUI
struct ContentView: View {
#State private var PushViewAfterAction = false
var body: some View {
NavigationLink(destination: ListView(json: json.data), isActive: $PushViewAfterAction) {
EmptyView()
}.hidden()
Button(action: {
Task {
await performAnAction()
}
}, label: {
Text("TEST")
.padding()
.frame(maxWidth: .infinity)
.background(Color.blue.cornerRadius(10))
.foregroundColor(.white)
.font(.headline)
})
}
func performAnAction() {
PushViewAfterAction = true
return
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ListView file
import SwiftUI
struct ListView: View {
#State var json: JSONStructure
var body: some View {
VStack {
List (self.json.data) { (home) in
HStack {
Text(home.name).bold()
Text(home.timezone)
}
}
}.onAppear(perform: {
guard let url: URL = URL(string: "https://... ***removed*** ") else {
print("invalid URL")
return
}
var urlRequest: URLRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// check if response is okay
guard let data = data, error == nil else { // check for fundamental networking error
print((error?.localizedDescription)!)
return
}
let httpResponse = (response as? HTTPURLResponse)!
if httpResponse.statusCode != 200 { // check for http errors
print("httpResponse Error: \(httpResponse.statusCode)")
return
}
// convert JSON response
do {
self.json = try JSONDecoder().decode(JSONStructure.self, from: data)
} catch {
print(error.localizedDescription)
print(String(data: data, encoding: String.Encoding.utf8)!)
}
print(json)
if (json.status.errno != 0) {
print(json.status.errstr)
}
print("1. \(json.data[0].name)), \(json.data[0].timezone)")
print("2. \(json.data[1].name)), \(json.data[1].timezone)")
}).resume()
})
}
}
struct ListView_Previews: PreviewProvider {
static var previews: some View {
ListView()
}
}
I've tried to keep the code to a minimum for clarity.
It's because there is no "json" in ContentView, you need to pass json object to ListView, but since you load json in ListView, then you need to initialize json in ListView like:
struct ListView: View {
#State var json: JSONStructure = JSONStructure(status: JSONStructure.Status(errno: 0, errstr: ""), data: [JSONStructure.Home(home_id: 0, name: "", timezone: "")])
var body: some View {
and remove it form NavigationLink in ContentView like:
NavigationLink(destination: ListView(), isActive: $PushViewAfterAction) {
or you could build your JSONStructure to accept optional like:
import Foundation
struct JSONStructure: Codable {
struct Status: Codable {
let errno: Int?
let errstr: String?
init() {
errno = nil
errstr = nil
}
}
struct Home: Codable, Identifiable {
var id = UUID()
let home_id: Int?
let name: String?
let timezone: String?
init() {
home_id = nil
name = nil
timezone = nil
}
}
let status: Status?
let data: [Home]
init() {
status = nil
data = []
}
}
but then you need to check for optionals or provide default value like:
struct ListView: View {
#State var json: JSONStructure = JSONStructure()
var body: some View {
VStack {
List (self.json.data) { (home) in
HStack {
Text(home.name ?? "Could not get name").bold()
Text(home.timezone ?? "Could not get timeZone")
}
}
}.onAppear(perform: {
guard let url: URL = URL(string: "https://... ***removed*** ") else {
print("invalid URL")
return
}
var urlRequest: URLRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
URLSession.shared.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
// check if response is okay
guard let data = data, error == nil else { // check for fundamental networking error
print((error?.localizedDescription)!)
return
}
let httpResponse = (response as? HTTPURLResponse)!
if httpResponse.statusCode != 200 { // check for http errors
print("httpResponse Error: \(httpResponse.statusCode)")
return
}
// convert JSON response
do {
self.json = try JSONDecoder().decode(JSONStructure.self, from: data)
} catch {
print(error.localizedDescription)
print(String(data: data, encoding: String.Encoding.utf8)!)
}
print(json)
if (json.status?.errno != 0) {
print(json.status?.errstr)
}
print("1. \(json.data[0].name)), \(json.data[0].timezone)")
print("2. \(json.data[1].name)), \(json.data[1].timezone)")
}).resume()
})
}
}
I've been messing around with CoreData and this time I'm trying to save my downloaded JSON to core data. I don't get any error until I try to display the list and it would return empty.
Any suggestions would be much appreciated!
Model
struct ArticleData: Identifiable, Decodable {
let id : String
let type : String
let attributes: ArticleAttributee
struct ArticleAttributee: Codable {
let name: String
let card_artwork_url: String
...
}
AttributeArticle+CoreDataProperties.swift
extension AttributeArticle {
#nonobjc public class func fetchRequest() -> NSFetchRequest<AttributeArticle> {
return NSFetchRequest<AttributeArticle>(entityName: "AttributeArticle")
}
#NSManaged public var card_artwork_url: String?
#NSManaged public var content_type: String?
...
public var wrapperCard_artwork_url : String {
card_artwork_url ?? "Unknown"
}
...
}
Download JSOn and load to Core Data
class Article {
static func loadDataFromJSON(completion: #escaping ([ArticleData]) -> ()) {
let stringURL = "https://api.jsonbin.io/b/5ed679357741ef56a566a67f"
guard let url = URL(string: stringURL) else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {
data, response, error in
guard let data = data else {
print("No data in response \(error?.localizedDescription ?? "No data response")")
return
}
if let decoderLoadedUser = try? JSONDecoder().decode([ArticleData].self, from: data) {
completion(decoderLoadedUser)
}
}.resume()
}
static func loadDataToCD(moc: NSManagedObjectContext) {
loadDataFromJSON { (articles) in
DispatchQueue.main.async {
var tempArticles = [AttributeArticle]()
for article in articles {
let newArticle = AttributeArticle(context: moc)
newArticle.name = article.attributes.name
newArticle.card_artwork_url = article.attributes.card_artwork_url
... so on
...
tempArticles.append(newArticle)
}
do {
try moc.save()
} catch let error {
print("Could not save data: \(error.localizedDescription)")
}
}
}
}
}
my list :
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: AttributeArticle.entity(), sortDescriptors: []) var articles: FetchedResults<AttributeArticle>
VStack {
List() {
ForEach(articles, id: \.id) { article in
NavigationLink(destination: ArticleDetailView(articles: article)){
ArticleRowView(articles: article)
}
}
}
}.onAppear {
if self.articles.isEmpty {
print("Articles is empty \(self.articles)")
Article.loadDataToCD(moc: self.moc)
}
Sorry for the long post and thank you for your helps!
I want use ObjectMapper to parsing Json string within a singleton situation. Example code :
class User: Mappable {
var username: String?
var signature: String?
//Singleton
static let shared = User()
private init() {}
//Mappable functions
required init?(map: Map) {}
func mapping(map: Map) {
username <- map["username"]
signature <- map["signature"]
}
//Update userInfo after network request
func getUserInfo() {
//Network things
...
//Example data
let data = [
"username": "Eason",
"signature": "I love U"
]
//Some thing like this to update userInfo
Mapper<User>().map(data)
}
}
So, what is the right way to use ObjectMapper in singleton situation?
I prefer the following option.
1) UserManager (singleton):
class UserManager: NSObject {
static let shared = UserManager()
var profile: UserProfile?
private override init() {}
func loadUserProfile(completion: #escaping () -> ()) {
RestClient.shared.getUserProfile() { [weak self] (profile, error) in
guard let `self` = self else { return }
self.profile = profile as? UserProfile
completion()
}
}
}
2) User model:
class UserProfile: Mappable {
var username: String?
var signature: String?
required init?() {}
required init?(map: Map) {}
func mapping(map: Map) {
username <- map ["username"]
signature <- map ["signature"]
}
}
3) RestClient
typealias IdResponseBlock = (_ swiftObj: Any?, _ error: Error?) -> Void
class RestClient: NSObject {
static let shared = RestClient()
// Additional class where sending and receiving information from the server occurs
private var http = HttpService()
let baseUrl = ApiSettings.shared.kServerBaseURL
override init() {
super.init()
}
func parseData<P: BaseMappable>(object: Any, modelCls: P.Type, response: (IdResponseBlock)) {
if object is NSArray {
let result = Mapper<P>().mapArray(JSONObject: object)
return response(result, nil)
}
if object is NSDictionary {
let model: P = Mapper<P>().map(JSONObject: object)!
return response(model, nil)
}
}
//MARK: - API
//MARK: - User
func getUserProfile(resp: #escaping IdResponseBlock) {
let url = baseUrl + Requests.profile
http.queryBy(url, method: .get, queue: .background, resp: { (response, error) in
if let err = error {
return resp(nil, err)
}
guard let data = response else {
return resp(nil, error)
}
let jsonData = JSON(data)["data"]
self.parseData(object: jsonData.rawValue,
modelCls: UserProfile.self,
response: resp)
})
}
}
class PostFOrData {
let url = NSURL( string: "http://210.61.209.194:8088/SmarttvWebServiceTopmsoApi/GetReadlist")
var picUrl = NSURL(string : "http://210.61.209.194:8088/SmarttvMedia/img/epi00001.png")
var responseString : NSString = ""
func forData() -> NSString {
let request = NSMutableURLRequest( URL: url!)
request.HTTPMethod = "POST"
var s : NSString = ""
let postString : String = "uid=59"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
println("error=\(error)")
return
} else {
println("response = \(response!)")
self.responseString = NSString(data: data, encoding: NSUTF8StringEncoding)!
println("responseString = \(self.responseString)")
}
}
// I want to return NSString here, but I always get nothing
return self.responseString
}
}
Anyone know how to get the data from task?
You can't return data directly from an asynchronous task.
The solution with Swift 2 is to make a completion handler like this:
class PostFOrData {
// the completion closure signature is (NSString) -> ()
func forData(completion: (NSString) -> ()) {
if let url = NSURL(string: "http://210.61.209.194:8088/SmarttvWebServiceTopmsoApi/GetReadlist") {
let request = NSMutableURLRequest( URL: url)
request.HTTPMethod = "POST"
let postString : String = "uid=59"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if let data = data,
jsonString = NSString(data: data, encoding: NSUTF8StringEncoding)
where error == nil {
completion(jsonString)
} else {
print("error=\(error!.localizedDescription)")
}
}
task.resume()
}
}
}
let pfd = PostFOrData()
// you call the method with a trailing closure
pfd.forData { jsonString in
// and here you get the "returned" value from the asynchronous task
print(jsonString)
}
That way, the completion is only called when the asynchronous task is completed. It is a way to "return" the data without actually using return.
Swift 3 version
class PostFOrData {
// the completion closure signature is (String) -> ()
func forData(completion: #escaping (String) -> ()) {
if let url = URL(string: "http://210.61.209.194:8088/SmarttvWebServiceTopmsoApi/GetReadlist") {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let postString : String = "uid=59"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data, let jsonString = String(data: data, encoding: String.Encoding.utf8), error == nil {
completion(jsonString)
} else {
print("error=\(error!.localizedDescription)")
}
}
task.resume()
}
}
}
let pfd = PostFOrData()
// you call the method with a trailing closure
pfd.forData { jsonString in
// and here you get the "returned" value from the asynchronous task
print(jsonString)
}
There are some very generic requirements that would like every good API Manager to satisfy: will implement a protocol-oriented API Client.
APIClient Initial Interface
protocol APIClient {
func send(_ request: APIRequest,
completion: #escaping (APIResponse?, Error?) -> Void)
}
protocol APIRequest: Encodable {
var resourceName: String { get }
}
protocol APIResponse: Decodable {
}
Now Please check complete api structure
// ******* This is API Call Class *****
public typealias ResultCallback<Value> = (Result<Value, Error>) -> Void
/// Implementation of a generic-based API client
public class APIClient {
private let baseEndpointUrl = URL(string: "irl")!
private let session = URLSession(configuration: .default)
public init() {
}
/// Sends a request to servers, calling the completion method when finished
public func send<T: APIRequest>(_ request: T, completion: #escaping ResultCallback<DataContainer<T.Response>>) {
let endpoint = self.endpoint(for: request)
let task = session.dataTask(with: URLRequest(url: endpoint)) { data, response, error in
if let data = data {
do {
// Decode the top level response, and look up the decoded response to see
// if it's a success or a failure
let apiResponse = try JSONDecoder().decode(APIResponse<T.Response>.self, from: data)
if let dataContainer = apiResponse.data {
completion(.success(dataContainer))
} else if let message = apiResponse.message {
completion(.failure(APIError.server(message: message)))
} else {
completion(.failure(APIError.decoding))
}
} catch {
completion(.failure(error))
}
} else if let error = error {
completion(.failure(error))
}
}
task.resume()
}
/// Encodes a URL based on the given request
/// Everything needed for a public request to api servers is encoded directly in this URL
private func endpoint<T: APIRequest>(for request: T) -> URL {
guard let baseUrl = URL(string: request.resourceName, relativeTo: baseEndpointUrl) else {
fatalError("Bad resourceName: \(request.resourceName)")
}
var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)!
// Common query items needed for all api requests
let timestamp = "\(Date().timeIntervalSince1970)"
let hash = "\(timestamp)"
let commonQueryItems = [
URLQueryItem(name: "ts", value: timestamp),
URLQueryItem(name: "hash", value: hash),
URLQueryItem(name: "apikey", value: "")
]
// Custom query items needed for this specific request
let customQueryItems: [URLQueryItem]
do {
customQueryItems = try URLQueryItemEncoder.encode(request)
} catch {
fatalError("Wrong parameters: \(error)")
}
components.queryItems = commonQueryItems + customQueryItems
// Construct the final URL with all the previous data
return components.url!
}
}
// ****** API Request Encodable Protocol *****
public protocol APIRequest: Encodable {
/// Response (will be wrapped with a DataContainer)
associatedtype Response: Decodable
/// Endpoint for this request (the last part of the URL)
var resourceName: String { get }
}
// ****** This Results type Data Container Struct ******
public struct DataContainer<Results: Decodable>: Decodable {
public let offset: Int
public let limit: Int
public let total: Int
public let count: Int
public let results: Results
}
// ***** API Errro Enum ****
public enum APIError: Error {
case encoding
case decoding
case server(message: String)
}
// ****** API Response Struct ******
public struct APIResponse<Response: Decodable>: Decodable {
/// Whether it was ok or not
public let status: String?
/// Message that usually gives more information about some error
public let message: String?
/// Requested data
public let data: DataContainer<Response>?
}
// ***** URL Query Encoder OR JSON Encoder *****
enum URLQueryItemEncoder {
static func encode<T: Encodable>(_ encodable: T) throws -> [URLQueryItem] {
let parametersData = try JSONEncoder().encode(encodable)
let parameters = try JSONDecoder().decode([String: HTTPParam].self, from: parametersData)
return parameters.map { URLQueryItem(name: $0, value: $1.description) }
}
}
// ****** HTTP Pamater Conversion Enum *****
enum HTTPParam: CustomStringConvertible, Decodable {
case string(String)
case bool(Bool)
case int(Int)
case double(Double)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let int = try? container.decode(Int.self) {
self = .int(int)
} else if let double = try? container.decode(Double.self) {
self = .double(double)
} else {
throw APIError.decoding
}
}
var description: String {
switch self {
case .string(let string):
return string
case .bool(let bool):
return String(describing: bool)
case .int(let int):
return String(describing: int)
case .double(let double):
return String(describing: double)
}
}
}
/// **** This is your API Request Endpoint Method in Struct *****
public struct GetCharacters: APIRequest {
public typealias Response = [MyCharacter]
public var resourceName: String {
return "characters"
}
// Parameters
public let name: String?
public let nameStartsWith: String?
public let limit: Int?
public let offset: Int?
// Note that nil parameters will not be used
public init(name: String? = nil,
nameStartsWith: String? = nil,
limit: Int? = nil,
offset: Int? = nil) {
self.name = name
self.nameStartsWith = nameStartsWith
self.limit = limit
self.offset = offset
}
}
// *** This is Model for Above Api endpoint method ****
public struct MyCharacter: Decodable {
public let id: Int
public let name: String?
public let description: String?
}
// ***** These below line you used to call any api call in your controller or view model ****
func viewDidLoad() {
let apiClient = APIClient()
// A simple request with no parameters
apiClient.send(GetCharacters()) { response in
response.map { dataContainer in
print(dataContainer.results)
}
}
}
I have a function called getEarthquake() that parses JSON using SwiftyJSON and returns all of the organized information (such as title, magnitude, and time) into an NSMutableArray called info.
var info = NSMutableArray()
func getEarthquake(completion: (results : NSMutableArray) ->Void) {
DataManager.getEarthquakeDataFromFileWithSuccess {
(data) -> Void in
let json = JSON(data: data)
if var JsonArray = json.array {
JsonArray.removeAtIndex(0)
for appDict in JsonArray {
var mag: String? = appDict["mag"].stringValue
var title: String? = appDict["title"].stringValue
var time: String? = appDict["time"].stringValue
var information = AppModel(title: title, magnitude: mag, time1: time)
info.addObject(information)
// info.removeRange(3...48)
completion(results: info)
}
}
}
}
I created another function called getEarthquake2() which calls getEarthquake() and retrieves info. In getEarthquake2() I want it to return only title1, which is a String. However my attempts result only in title1 being nil by the time is it returned.
func getEarthquake2()->String? {
var title1: String?
getEarthquake{ (info) in
var title = info[0].title
title1 = title
}
return title1
}
Can someone guide me in the right direction upon making getEarthquake2()successfully return title1 that doesn't return nil? (I'm sure it's not a matter of Info being nil, as it gets populated at the end of getEarthquake().)
My AppModel.swift file where I can easily organize my code:
import Foundation
class AppModel: NSObject, Printable {
let title: String
let magnitude: String
let time1: String
override var description: String {
return "TITLE: \(title), TIME: \(time1), MAG: \(magnitude)"
}
init(title: String?, magnitude: String?, time1: String?) {
self.title = title ?? ""
self.time1 = time1 ?? ""
self.magnitude = magnitude ?? ""
}
}
My DataManager.swift file where I call the web service:
import Foundation
let earthquakeURL = "http://www.kuakes.com/json/"
class DataManager {
class func getEarthquakeDataFromFileWithSuccess(success: ((websiteData: NSData) -> Void)) {
//1
loadDataFromURL(NSURL(string: earthquakeURL)!, completion:{(data, error) -> Void in
//2
if let urlData = data {
//3
success(websiteData: urlData)
}
else {
println("nothing")
}
})
}
class func loadDataFromURL(url: NSURL, completion:(data: NSData?, error: NSError?) -> Void) {
var session = NSURLSession.sharedSession()
// Use NSURLSession to get data from an NSURL
let loadDataTask = session.dataTaskWithURL(url, completionHandler: { (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if let responseError = error {
completion(data: nil, error: responseError)
} else if let httpResponse = response as? NSHTTPURLResponse {
if httpResponse.statusCode != 200 {
var statusError = NSError(domain:"com.kuakes", code:httpResponse.statusCode, userInfo:[NSLocalizedDescriptionKey : "HTTP status code has unexpected value."])
completion(data: nil, error: statusError)
} else {
completion(data: data, error: nil)
}
}
})
loadDataTask.resume()
}
}
In getEarthquake2 you forget to cast your object to AppModel:
func getEarthquake2() -> String? {
var title1: String?
getEarthquake { info in
let title = (info[0] as! AppModel).title
title1 = title
}
return title1
}