Genric object as a parmenter with HandyJSON - ios

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) { ... }

Related

How to create a nested Encodable class and return it from a function in Swift?

In my iOS project, I want to do the following:
create an Encodable class (named ChannelAnswer) which has multiple attributes, including another generic Encodable object
pass an instance of that class as an argument of a function or return it from a function
Here is what I tried:
class ChannelAnswer<T> : Encodable where T : Encodable
{
let errorCode: String?
let data: T?
let origin: Int = 2
init(_ errorCode: String?, _ data: T? = nil)
{
self.errorCode = errorCode
self.data = data
}
}
Now, if I want to return an instance of that class from a function, like the following:
func test() -> ChannelAnswer
{
...
return ChannelAnswer("abc", anyEncodableObject)
}
I get the following error:
Reference to generic type 'ChannelAnswer' requires arguments in <...>
The thing is: the data attribute could be of any type, I only know that that type is Encodable (the test()function above is just an example for the sake of simplicity).
So how can I create my ChannelAnswer class and successfully pass it as an argument of a function or return it from a function?
Thanks.
What you need is a generic method.
func test<T: Encodable>(data: T) -> ChannelAnswer<T> {
// ...
return ChannelAnswer("abc", data)
}

Casting to specific type from a generic type

I want to cast to a specific class how can I achieve this?
public class func parseResponse<T:Decodable>(className:T.Type, response:[String: AnyObject],myid:String,index : Int)->(Bool,T?)
{
guard let data = try? JSONSerialization.data(withJSONObject: response) else {
return (false,nil)
}
if myid.count > 0 {
// let clas1 = NSClassFromString(String(describing: T.Type.self))!
let obj = T.self as! BaseResponse
print(String(describing: T.self))
obj.setKeys(myid: myid, index: index)
}
let decoder = JSONDecoder()
do {
let object = try decoder.decode(T.self, from: data)
return (true,object)
}
catch {
return (false,nil)
}
}
I have this method where it is crashing at let obj = T.self as! BaseResponse this line saying that it cannot be type cast.
The statement let obj = T.self as! BaseResponse doesn't make much sense. You're trying to create an object by casting a class into another class, when you should be trying to cast an instance of a class into another class.
In order for this algorithm to work, you need T to be both Decodable and a subtype of BaseResponse. Since that's the case, you should say so in your signature.
public class func parseResponse<T>(...) -> (Bool,T?)
where T: BaseResponse & Decodable
With that, there's no need to cast anything. Your code hints that BaseResponse has a class-level method setKeys, so you can just call it on T (which you now know to be a subtype of BaseResponse):
T.setKeys(...)
On an unrelated note (Bool, T?) is a very strange return type for this usage. The optionality of T already provides everything the Bool is doing, much more cleanly.

How to create a wrapper function for "ObjectMapper" library in swift

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
}

Generic parameter could not be inferred - Array extension

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)

Write Generic Swift Method to load data from property list

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()

Resources