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
}
Related
I'm using HandyJSON library to convert json to object and i made a static function to do so and it works as i want, here is the code
static func objectFromJSONstring<T : HandyJSON>(object: T.Type, JSONString : String) -> T? {
if let obj = object.deserialize(from: JSONString) {
return obj
}
return nil
}
But when i tried to do the same for an Array of objects
static func arrayOfObjectsFromJSONstring<T : HandyJSON>(objt : T.Type, JSONString: String, objectPath: String) -> [T?]? {
if let obj = [objt].deserialize(from: JSONString) {
return obj
}
return nil
}
I get this message :
Static member 'deserialize' cannot be used on instance of type '[T.Type]'.
I want to know what i'm doing wrong and how to fix it while keeping the same implementation of passing a generic object class as a parameter.
Thanks..
Just use the generic type parameter T instead of the variable to which the argument type is being passed.
if let obj = [T].deserialize(from: JSONString) { ... }
Let me explain the issue with example.
I have two protocols named - Mappable(available on Git) and Responsable(I created that which conforms to the Mappable protocol)
protocol Responsable: Mappable {
static func getResponse(map: Map) -> Self
}
Approach 1
then I have struct 'NSIResponse' which is generic and conforms to Mappable and generic T is Resposable type
struct NSIResponse<T>: Mappable where T: Responsable {
mutating func mapping(map: Map) {
response = T.getResponse(map: map)
}
}
After that I created the struct of User Object which conforms to the Responsable protocol.
struct User: Responsable {
var id: Int ?
mutating func mapping(map: Map) {
id <- map["id"]
}
static func getResponse(map: Map) -> User {
guard let response = Mapper <User>().map(JSONObject: map["response"].currentValue)
else {
return User()
}
return response
}
}
Approach 2
Well, I created getResponse or Responsable because I don't want to use more lines in mapping method in NSIResponse like following
mutating func mapping(map: Map) {
switch T.self {
case is User.Type: response = Mapper<User>().map(JSONObject: map["response"].currentValue) as? T
case is Array<User>.Type: response = Mapper<User>().mapArray(JSONObject: map["response"].currentValue) as? T
default: response <- map["response"]
}
}
I dont want to use previous approach because if I do then I have to write every single two lines of code for every class. Result, function length will increase. Therefore, I created the T.getResponse(map: map) method.
Now Issue which I am facing
let jsonResponse = response.result.value as? [String: AnyObject]
let nsiResponse = NSIResponse<User>(JSON: jsonResponse) // WORKING
let nsiResponse1 = NSIResponse<[User]>(JSON: jsonResponse) // NOT WORKING and getting Type '[User]' does not conform to protocol Responsable
However, its working fine in case of Approach 2..
I would be very appreciated if you could help me with Approach 1.
I hope you understood my question.
The reason for this error is because the type Array does not conform to Responsable, only User does. In approach 2, you cover that case so it works.
What you have to do is extend the Array type, so it can conform to Responsable:
extension Array: Responsable {
mutating func mapping(map: Map) {
// Logic
}
static func getResponse(map: Map) -> User {
// Logic
}
}
What is the recommended approach in RxSwift to implement RAC tryMap-like functionality?
The following code is how I do mapping of json objects to an internal response wrapper class. If the response fails to comply with certain conditions, nil will be returned, which turns into an Error Event(tryMap implementation).
extension RACSignal{
func mapToAPIResponse() -> RACSignal{
return tryMap({ (object) -> AnyObject! in
if let data = object as? [String:AnyObject]{
//Some Logic
return data["key"]
}
return nil
})
}
}
How should this be implemented in RxSwift?
Updated-Possible Solution
I came up with following solution for Rx-Swift. Open for better solutions.
extension Observable{
func mapToAPIResponse() -> Observable<APIResponse>{
return map({ (object) in
guard let dictionary = object as? [String:AnyObject] else{
//APIResponseError.InvalidResponseFormat is defined in other class.
throw APIResponseError.InvalidResponseFormat
}
let response = APIResponse()
//Complete API Response
return response
})
}
My conclusion is to use throw inside a map to handle errors.
Your solution is correct, that's why map operator in RxSwift is annotated with throws. Release notes of RxSwift 2 explicitly state this:
Adds support for Swift 2.0 error handling try/do/catch.
You can now just write
API.fetchData(URL)
.map { rawData in
if invalidData(rawData) {
throw myParsingError
}
...
return parsedData
}
Even in `RxCocoa
There is a great way to implement network layer with these set of PODs
RxSwift + Moya/RxSwift + Moya-ObjectMapper/RxSwift
Finally your code for model will looks like
import ObjectMapper
final class Product: Mappable {
var id: String?
var categoryId: String?
var name: String?
func mapping(map: Map) {
id <- map["id"]
categoryId <- map["category_id"]
name <- map["name"]
}
}
And for service
final class ProductService {
class func productWithId(id: String, categoryId: String) -> Observable < Product > {
return networkStubbedProvider
.request(.Product(id, categoryId))
.filterSuccessfulStatusAndRedirectCodes()
.mapObject(Product)
}
}
I have a method that loads an array of dictionaries from a propertylist. Then I change those arrays of dictionaries to array of a defined custom type;
I want to write that method in generic form so I call that method with the type I expect, then the method loads it and returns an array of my custom type rather than dictionaries
func loadPropertyList(fileName: String) -> [[String:AnyObject]]?
{
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist")
{
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path)
{
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]]
{
return temp
}
}catch{}
}
}
return nil
}
//
func loadList<T>(fileName: String) -> [T]?{//**Here the answer I am expecting**}
I am assuming your function to read from a Plist works and that you don't want to subclass NSObject.
Since Swift reflecting does not support setting values this is not possible without some implementation for each Type you want this to work for.
It can however be done in a pretty elegant way.
struct PlistUtils { // encapsulate everything
static func loadPropertyList(fileName: String) -> [[String:AnyObject]]? {
if let path = NSBundle.mainBundle().pathForResource(fileName, ofType: "plist") {
if let plistXML = NSFileManager.defaultManager().contentsAtPath(path) {
do {
if let temp = try NSPropertyListSerialization.propertyListWithData(plistXML, options: .Immutable, format: nil) as? [[String:AnyObject]] {
return temp
}
} catch {
return nil
}
}
}
return nil
}
}
This protocol will be used in a generic fashion to get the Type name and read the corresponding Plist.
protocol PListConstructible {
static func read() -> [Self]
}
This protocol will be used to implement Key Value setters.
protocol KeyValueSettable {
static func set(fromKeyValueStore values:[String:AnyObject]) -> Self
}
This is the combination of both to generate an array of objects. This does require that the Plist is named after the Type.
extension PListConstructible where Self : KeyValueSettable {
static func read() -> [Self] {
let name = String(reflecting: self)
var instances : [Self] = []
if let data = PlistUtils.loadPropertyList(name) {
for entry in data {
instances.append(Self.set(fromKeyValueStore: entry))
}
}
return instances
}
}
This is some Type.
struct Some : PListConstructible {
var alpha : Int = 0
var beta : String = ""
}
All you have to do is implement the Key Value setter and it will now be able to be read from a Plist.
extension Some : KeyValueSettable {
static func set(fromKeyValueStore values: [String : AnyObject]) -> Some {
var some = Some()
some.alpha = (values["alpha"] as? Int) ?? some.alpha
some.beta = (values["beta"] as? String) ?? some.beta
return some
}
}
This is how you use it.
Some.read()
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
}