Following function is given:
class func collection(#response: NSHTTPURLResponse,
representation: AnyObject) -> [City] {
return []
}
So this function should return an array of city objects. I have to somehow transform the representation variable that is of type AnyObject to a city array.
I don't know what the exact type of representation is but I can do things like
println(representation[0])
and it will print the object. Any ideas how to transform representation to [City] array?
Update
Doing
println(representation as [City])
prints nil.
City.swift:
final class City : ResponseCollectionSerializable {
let id: String
let name: String
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [City] {
return []
}
}
This is just copy and pasted from https://github.com/Alamofire/Alamofire#generic-response-object-serialization It should serialize a JSON response into objects:
#objc public protocol ResponseCollectionSerializable {
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
return (T.collection(response: response!, representation: JSON!), nil)
} else {
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object as? [T], error)
})
}
}
The representation parameter you're getting back is the result of a call to NSJSONSerialization.JSONObjectWithData..., so it's either a NSArray or a NSDictionary. Since you get a value for representation[0], we know it's an NSArray. Exactly what your code looks like will depend on the JSON (a sample of which you should include in a question like this), but your code will need to be something like (untested code ahead):
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [City] {
var cities: [City] = []
for cityRep in representation {
// these next two lines should grab the city data using the correct key
let id = cityRep.valueForKey("cityID") as String
let name = cityRep.valueForKey("cityName") as String
// now add the city to our list
cities.append(City(id: id, name: name))
}
return cities
}
Assuming (and although I hate to make assumptions, your question is a bit vague about the details) that the representation is either an NSData object that represents the response, or an Array that you have created from the response.
In my experience, such a response is an array of dictionaries that you can use to create city objects. So you need to write a function that transforms this dictionary into a City object. Something with the signature:
parser (AnyObject) -> City
Now, you could just iterate through the array, apply this function to each dictionary, collect the results into an Array and return the result.
But you could be classier and map your function over the array and return the result.
Related
I need to cast the below response from my server as [UserResult] but I cannot get it to work??
What am I doing wrong?
func userSearch(keyword: String, completion: #escaping (Result<[UserResult], ResponseError>) -> Void ) {
socket.emit("userSearch", keyword)
socket.on("userFound") { ( data, ack) in
print(data) // prints below NSArray
if !data.isEmpty {
if let response = data as? [UserResult] {
print("USERS \(response)") // WILL NOT WORK?
completion(.success(response))
}
} else {
completion(.failure(.badRequest("No users found")))
}
}
}
Data from server
[<__NSArrayM 0x60000040e5b0>(
{
profileUrl = "www.address1.com";
username = chrissmith;
},
{
profileUrl = "www.address2.com";
username = johnsmith;
},
{
profileUrl = "www.address3.com";
username = alicesmith;
}
)
]
UserResult Model
struct UserResult: Decodable {
let username: String
let profileUrl: String
}
Well you are using Socket.IO library and specifically method
socket.on(clientEvent: .connect) {data, ack in
...
}
defined as
#discardableResult
open func on(clientEvent event: SocketClientEvent, callback: #escaping NormalCallback) -> UUID
using typealias:
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()
So basically at the and you are being returned data of type [Any] according to documentation.
Since you do not know what is inside your data it is better for you to unwrap objects in your array one by one (instead casting it directly to [UserResult]) and try to find out what Type there are by comparing to some set of known types as some of answers from this question suggest.
I would start with verifying the data structure with example code below , and only move on with casting to various type afterwards:
Lets assume example data1 is your data:
let dict1 = ["profileUrl":"www.address1.com","username":"chrissmith"]
let data1: NSArray = [dict1]
//printed data1:
// (
// {
// profileUrl = "www.address1.com";
// username = chrissmith;
// }
// )
if data1[0] as? [String:String] != nil {
print("We found out that first object is dictionary of [String:String]!")
}
else if data1[0] as? Dictionary<NSObject, AnyObject> != nil {
print("We found out that first object is dictionary of mixed values!")
} else {
print("We found out that first object has different data structure")
}
Hopefully this answer was at least a little bit helpfull, even though not providing direct easy solution for your problem.
In my application, several controllers have a very similar code structure, the differences are minimal, so for optimization I decided to create a basis for these controllers, and inherit each specific controller from the basis.
I have a function for sending network requests and processing a response, I pass the response structure as a parameter to this function, so that the function returns a ready-made response structure to me. Each such structure is Decodable.
An example of such a structure:
struct APIAnswerUserActivity: Decodable {
let status: String
let code: Int?
let data: [UserActivity]?
let total: Int?
}
Function for network requests, an object (structure) of the Decodable.Protocol type is accepted as a jsonType parameter:
public func networkRequest<T: Decodable> (
url: String,
timeout: Double = 30,
method: URLMethods = .GET,
data: [String : String]? = nil,
files: [URL]? = nil,
jsonType: T.Type,
success: #escaping (T) -> Void,
failure: #escaping (APIError) -> Void
) -> URLSessionDataTask { ... }
There are several parameters in the main controller that I override through override in the child controllers, one of these parameters should be an object of type Decodable for the general function to receive data correctly. The JSON structures of the response are very similar, but still slightly different, a common structure for them cannot be created, because the data is still a little different.
If in the main controller do this:
public var decodableType: Decodable.Type {
return APIAnswerUserActivity.self
}
That will work, and it is possible to redefine types, but the network function does not accept this, it needs the Decodable.Protocol object. If the type decodable.Protocol is specified for the variable decodableType, then it is no longer possible to add APIAnswerUserActivity.self, which is quietly accepted when the networkRequest function is called.
How to be in this situation? I hope that I managed to correctly and clearly state the essence of my problem. Thanks!
#Владислав Артемьев, I'm still not sure that I completely understand the problem because you haven't shared the code that takes the Decodable class. But the issues seems to be about how to pass a Decodable class.
I hope the following can help clarify how you can impose the right constraint on the generic and how you should declare the variable. You can paste it into a playground and experiment.
import Foundation
struct FakeToDo: Decodable {
var userId: Int
var id: Int
var title: String
var completed: Bool
}
enum URLMethods {
case GET
case POST
}
func networkRequest<T: Decodable> (
url: String,
timeout: Double = 30,
method: URLMethods = .GET,
data: [String : String]? = nil,
files: [URL]? = nil,
jsonType: T.Type,
success: #escaping (T) -> Void,
failure: #escaping (Error) -> Void
) -> URLSessionDataTask {
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { (data, response, error) in
if error != nil {
failure(error!)
return
}
guard let data = data else { return }
let decoder = JSONDecoder()
guard let value = try? decoder.decode(T.self, from: data) else { return }
// get back on the main queue for UI
DispatchQueue.main.async {
success(value)
}
})
return task
}
class Example<T> where T: Decodable {
let type: T.Type
init(_ type: T.Type) {
self.type = type
}
public var decodableType: T.Type {
return type
}
}
let decodableType = Example(FakeToDo.self).decodableType
let url = "https://jsonplaceholder.typicode.com/todos/1"
let task = networkRequest(url: url, jsonType: decodableType,
success: { value in print(value) },
failure: { error in print(error) })
task.resume()
I am using ObjectMapper library to map my JSON object to Swift object. The traditional method of library is working fine for me like below code.
tmpArray1 = Mapper<UserModel>().mapArray(JSONArray: result1)
tmpArray2 = Mapper<CompanyModel>().mapArray(JSONArray: result2)
Now, I want to create a generic method to return dynamic object according to argument which i pass in that function. I want somewhat like below.
tmpArray1 = WrapperClass.shared.getModelObject(objType: UserModel, data: Any)
tmpArray2 = WrapperClass.shared.getModelObject(objType: CompanyModel, data: Any)
Here is my WrapperClass.swift class code:
open class WrapperClass: NSObject {
static let shared = WrapperClass()
open func getModelObject(objType: Mappable, data: Any) -> Any? {
// Need Help Here
return <dynamic object>
}
}
I don't know my approach is 100% right but i want somewhat like whatever object type i pass in the argument of the function i want same object type in return with mapped with ObjectMapper object. I am using Swift 4.0 version.
You can find the ObjectMapper
here.
Update:
I have tried below thing but it will not work, show an error
func getModelObject<T: Mappable>(modelType: T.Type, data: Any) -> [T]? {
if data is Array<Any> {
return Mapper<modelType>().mapArray(JSONArray: data as! [[String: Any]])
//ERROR: Use of undeclared type 'modelType'
}
return nil
}
You can achieve that by combination of generics and Type. It allows you to instantiate the mappable object with generic T (no Mapper<...> here):
func getModelObject<T: Mappable>(objType: T.Type, data: Any) -> T? {
if let data = data as? [String: Any] {
return T(JSON: data)
} else if let data = data as? String {
return T(JSONString: data)
}
return nil
}
Example of usage:
class User: Mappable {
var name: String!
var age: Int!
required init?(map: Map) {}
func mapping(map: Map) {
name <- map["name"]
age <- map["age"]
}
}
let json = "{\"name\":\"Bob\",\"age\":100}"
if let user = WrapperClass.shared.getModelObject(objType: User.self, data: json) {
print(user.name, user.age)
}
Answer with Mapper<...>:
func getModelObject<T: Mappable>(data: Any) -> [T]? {
if let data = data as? [[String: Any]] {
return Mapper<T>().mapArray(JSONArray: data)
}
return nil
}
I have generic method to create object that extend protocol FromResponse.
extension FromResponse {
static func object<T>(_ response: [String: Any]?) -> T? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
let obj: T = T()
return obj
}
}
So whenever I want to call it from anywhere in a code there is no issue. Let's say:
let myObject: MyObject? = MyObject.object(response)
Work's perfectly. But sometimes I'm getting array of objects from my response so I would like to have generic parser as well:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
var returnArray: [T] = [T]()
for singleResponse in response {
if let object: T = T.object(singleResponse) {
returnArray.append(object)
}
}
return returnArray
}
So I expect from this method to return array of MyObject, but In fact I'm getting compiler error when I'm calling this:
let myObjects: [MyObject]? = MyObject.objects(response)
It says:
Generic parameter 'T' could not be inferred
Well, I know what does it mean but I did specify type, so this error should not happen. Also when I do this:
var typ: [MyObject] = [MyObject]()
for singleResponse in (response as? [[String: Any]])! {
let pack: MyObject? = MyObject.object(singleResponse)
typ.append(pack!)
}
It works!
Why? How to have parser that returns array of generics objects?
I don't know for sure why Swift says “Generic parameter 'T' could not be inferred”, but my guess is it has to do with array covariance.
What's covariance? Consider this:
class Base { }
class Sub: Base { }
func f(_ array: [Base]) { }
Can you pass an [Sub] to f? In Swift, you can. Because Sub is a subtype of Base, [Sub] is a subtype of [Base]. (This is called “covariance”.) So you can pass a [Sub] anywhere that a [Base] is allowed:
f([Sub]())
// No errors.
And you can return a [Sub] where a [Base] is expected:
func g() -> [Base] { return [Sub]() }
// No errors.
And you can assign a [Sub] to a [Base] variable:
let bases: [Base] = [Sub]()
// No errors.
So back to your code:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? ...
let myObjects: [MyObject]? = MyObject.objects(response)
Certainly MyObject.objects(_:) must return a type that can be treated as [MyObject]?. But any subtype of [MyObject]? is also acceptable. The type is not tightly constrained. I guess this is why Swift doesn't like it.
The fix is to tell Swift explicitly what type you want, using a pattern you'll see in many places in the Swift standard library:
static func objects<T>(ofType type: T.Type, from response: [[String: Any]]?) -> [T]? ...
// Note that you might not actually have to use the `type` parameter
// in the method definition.
let myObjects = MyObject.objects(ofType: MyObject.self, from: response)
It's not clear why this method is on the MyObject class at all. Perhaps you should make it a method on [[String: Any]]:
extension Collection where Element == [String: Any] {
func objects<T>(ofType type: T.Type) -> [T]? ...
}
let myObjects = response.objects(ofType: MyObject.self)
Related question: Generic completion handler in Swift
In a Swift app I'm writing, I'm downloading JSON and I want to convert it into model objects. Right now, I'm doing that like this:
func convertJSONData<T: Entity>(jsonData: NSData?, jsonKey: JSONKey, _: T.Type) -> [T]? {
var entities = [T]()
if let data = jsonData {
// Left out error checking for brevity
var json = JSON(data: data, options: nil, error: nil)
var entitiesJSON = json[jsonKey.rawValue]
for (index: String, subJson: JSON) in entitiesJSON {
// Error: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)
let entity = T(json: subJson)
entities.append(entity)
}
}
return entities
}
Each object conforming to Entity implements init(json: JSON). JSON is a type defined in the SwiftyJSON library. That's also the reason the enumeration looks a bit weird.
I call convertJSONData() in this method:
public func performJSONRequest<T where T: Entity>(jsonRequest: JSONRequest<T>) {
var urlString = ...
Alamofire.request(.GET, urlString, parameters: nil, encoding: .JSON).response { (request, response, data, error) -> Void in
var books = self.convertJSONData(data as? NSData, jsonKey: jsonRequest.jsonKey, T.self)
jsonRequest.completionHandler(books, error)
}
}
I get a runtime EXC_BAD_ACCESS(code=EXC_I386_GPFLT) error calling T(json: subJSON). There are no compiler warnings or errors. Although I left out error checking in the above code, there is error checking in the actual code and error is nil.
I'm not sure whether this is a compiler bug or my fault and any help figuring that out is much appreciated.
Several things are going on here, and I suspect the problem lies somewhere in the initializer of the class implementing the Entity protocol.
Assuming the code resembles the following:
protocol Entity {
init(json: JSON)
}
class EntityBase: Entity {
var name: String = ""
required init(json: JSON) { // required keyword is vital for correct type inference
if let nameFromJson = json["name"].string {
self.name = nameFromJson
}
}
func getName() -> String { return "Base with \(name)" }
}
class EntitySub: EntityBase {
convenience required init(json: JSON) {
self.init(json: json) // the offending line
}
override func getName() -> String { return "Sub with \(name)" }
}
The code compiles with self.init(json: json) in the sub-class, but actually trying to initialize the instance using the convenience method results in an EXC_BAD_ACCESS.
Either remove the initializer on the sub-class or simply implement required init and call super.
class EntitySub: EntityBase {
required init(json: JSON) {
super.init(json: json)
}
override func getName() -> String { return "Sub with \(name)" }
}
The method to convert the jsonData to an Entity (modified slightly to specifically return .None when jsonData is nil):
func convertJSONData<T:Entity>(jsonData: NSData?, jsonKey: JSONKey, type _:T.Type) -> [T]? {
if let jsonData = jsonData {
var entities = [T]()
let json = JSON(data: jsonData, options:nil, error:nil)
let entitiesJSON = json[jsonKey.rawValue]
for (index:String, subJson:JSON) in entitiesJSON {
let entity:T = T(json: subJson)
entities.append(entity)
}
return entities
}
return .None
}