evermeer / EVReflection

Reflection based (Dictionary, CKRecord, NSManagedObject, Realm, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift
Other
966 stars 119 forks source link

Cant Parse Dynamic object like array string and dictonary #317

Closed maulikshah09 closed 4 years ago

maulikshah09 commented 4 years ago

Language : Swift version : 5.0

Response from server

{
    "status": "success",
    "code": 200,
    "message": "Data received successfully",
    "data": [
        {
            "PId": 1,
            "PharmacyName": "Xyz",
            "ContactPerson": "test",
            "EmailId": "ZYZ@gmail.com",
            "PhoneNumer": "1234567890",
            "Address1": "test",
            "Address2": "test",
            "City": "test",
            "EntryDate": "2020-06-06T11:05:59.42",
            "ModifyDate": "2020-06-06T11:05:59.42",
            "UserId": 1,
            "IsDelete": false
        },
        {
            "PId": 2,
            "PharmacyName": "ZZZ",
            "ContactPerson": "ZZZ",
            "EmailId": "ZZz@gmail.com",
            "PhoneNumer": "1234567890",
            "Address1": "test",
            "Address2": "test",
            "City": "test",
            "EntryDate": "2020-06-06T11:05:32.967",
            "ModifyDate": "2020-06-06T11:05:32.967",
            "UserId": 1,
            "IsDelete": false
        }
    ]
}

I have a the below model class

I have below model

Code

protocol arrayorDictonary { }

extension Array: arrayorDictonary { }
extension Dictionary: arrayorDictonary { }

//empty model
class EmptyModelWithData : EVObject {
    var message = ""
    var status  = ""
    var code = 0
    var data : arrayorDictonary?
}
Alamofire.request(baseURL + urlMethod , method: method ,parameters: nil, encoding: JSONEncoding.default, headers:headerDic as? HTTPHeaders).responseObject {(response : DataResponse<EmptyModelWithData>) in
            SVProgressHUD.dismiss()
            let statusCode = response.response?.statusCode
            if response.result.value != nil {
             }
  }

and i get the following warning: while doing this

WARNING: The class 'EmptyModelWithData' is not key value coding-compliant for the key 'data' There is no support for optional type, array of optionals or enum properties. As a workaround you can implement the function 'setValue forUndefinedKey' for this. See the unit tests for more information

Note: Data is not specific type. some time data is array. some time data is dictionary. and sometime it would be string. so i create protocol but not working

I hope I get solution from here..

Thank you so much..

evermeer commented 4 years ago

your data property should be an array of objects like this: var data : [Pharmacy]? And your Pharmacy object would look like:

class Pharmacy: EVObject {
   var Pid: NSNumber?
   var PharmacyName: String?
   var ContactPerson: String?
  //rest of vars
}

Since there is support for single object to array this should work if you only get 1 pharmacy as an object instead of an array

maulikshah09 commented 4 years ago

your data property should be an array of objects like this: var data : [Pharmacy]? And your Pharmacy object would look like:

class Pharmacy: EVObject {
   var Pid: NSNumber?
   var PharmacyName: String?
   var ContactPerson: String?
  //rest of vars
}

Since there is support for single object to array this should work if you only get 1 pharmacy as an object instead of an array

Sir my data variable is not fixed...sometime it's array..sometime it's string or sometime it's dictionary..when i work on codable decodable it's working fine..also I tried any and anyobject but not working

maulikshah09 commented 4 years ago

If i work with codable then it's working fine.

class EmptyModelWithData: Codable {
   var message = ""
   var status = ""
   var code = 0
   var data: ParseType?
}

enum ParseType: Codable {
   case stringValue(String)
   case arrayValue([String])
   init(from decoder: Decoder) throws {
       let container = try decoder.singleValueContainer()

       if let values = try? container.decode(String.self) {
           self = .stringValue(values)
           return
       }
       if let values = try? container.decode([String].self) {
           self = .arrayValue(values)
           return
       }

       throw DecodingError.typeMismatch(ParseType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type"))
   }

   func encode(to encoder: Encoder) throws {
       var container = encoder.singleValueContainer()
       switch self {
       case let .stringValue(x):
           try container.encode(x)
       case let .arrayValue(x):
           try container.encode(x)
       }
   }
}
evermeer commented 4 years ago

Ah, then I think a property converter would be the way to go. Below is a code snipped that you could use. You probably have to finetune it.

class EmptyModelWithData: EVObject {
   var message: String? 
   var status: String?
   var code: NSNumber?
   var data: ParseType?

    override func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
        return [( key: "data", decodeConverter: {
           if let d = $0 as? String {
            self.data = ParseType(d)
           } else if let d = $0 as? NSArray {
             self.data - PareType(d)
           } else if let d = $0 as? NSDictionary {
             self.data - PareType(d)
           }
        },  encodeConverter: {
            return self.data
        })]
    }
}

here the propertyConverters function will return a decodeConverter for the data element. This means that when the data element should be parsed it will use that function. It will have a parameter of type Any which comes from the raw json serialization. So indeed it could be a NSArray, NSDictionary or a simple type like String. So the code just checks the type by casting it and then creating the right enum for it.

maulikshah09 commented 4 years ago

Didn't get you... please provide me model.. so i can move ahead .. I think you can't understand my question.

One more time Explain you....

class EmptyModelWithData : EVObject {
    var message = ""
    var status  = ""
    var code = 0
    var data :  // array dictionay or string or  something else. Don't know the type 
}

I give you example for understanding..

1 ) if response is array

class EmptyModelWithData : EVObject { var message = "" var status = "" var code = 0 var data : then auto add this array type }

2) if response is string then

class EmptyModelWithData : EVObject { var message = "" var status = "" var code = 0 var data : "" }

data variable type is not fixed.if you have proper solution then provide me..I waste my 5 days to solve this issue.Please provide me proper model of EVOBject.

evermeer commented 4 years ago

I updated my previous command so that it shows more of the model an dI added a comment

maulikshah09 commented 4 years ago

Don't want codable ... I want using EVOBject...I have solved with codable

maulikshah09 commented 4 years ago

If i work with codable then it's working fine.


class EmptyModelWithData: Codable {
   var message = ""
   var status = ""
   var code = 0
   var data: ParseType?
}

enum ParseType: Codable {
   case stringValue(String)
   case arrayValue([String])
   init(from decoder: Decoder) throws {
       let container = try decoder.singleValueContainer()

       if let values = try? container.decode(String.self) {
           self = .stringValue(values)
           return
       }
       if let values = try? container.decode([String].self) {
           self = .arrayValue(values)
           return
       }

       throw DecodingError.typeMismatch(ParseType.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type"))
   }

   func encode(to encoder: Encoder) throws {
       var container = encoder.singleValueContainer()
       switch self {
       case let .stringValue(x):
           try container.encode(x)
       case let .arrayValue(x):
           try container.encode(x)
       }
   }

Check this answer ... I have already Solved using Codable .. I want this using EVObject..

maulikshah09 commented 4 years ago

Please help me To go ahead I Stuck Here... My client is reviewing my code that's why I Finding a solution otherwise I go with codable.. Data is Generic Data type...

i want like this

class EmptyModelWithData : EVObject { var message = "" var status = "" var code = 0 var data : Genric datatype --> it's store dictionary ,array or something else }

evermeer commented 4 years ago

What do you mean? This should work:

class EmptyModelWithData: EVObject {
   var message: String? 
   var status: String?
   var code: NSNumber?
   var data: ParseType?

    override func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
        return [( key: "data", decodeConverter: {
           if let d = $0 as? String {
            self.data = ParseType(d)
           } else if let d = $0 as? NSArray {
             self.data - PareType(d)
           } else if let d = $0 as? NSDictionary {
             self.data - PareType(d)
           }
        },  encodeConverter: {
            return self.data
        })]
    }
}
maulikshah09 commented 4 years ago

Yes I have solved my issue using following code:

class EmptyModelWithData : EVObject {
    var message = ""
    var status  = ""
    var code = 00
    var data: Any?

    override func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
        return [( key: "data", decodeConverter: {
            if let d = $0 as? String {
                self.data = d as AnyObject
            } else if let d = $0 as? NSArray {
                self.data = d
            } else if let d = $0 as? NSDictionary {
                self.data = d
            }
        },  encodeConverter: {
            return self.data
        })]
    }
}
evermeer commented 4 years ago

But then if you use any, you don't have to cast. This would be good enough:

class EmptyModelWithData : EVObject {
    var message = ""
    var status  = ""
    var code = 00
    var data: Any?

    override func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
        return [( key: "data", decodeConverter: {
                self.data = $0
        },  encodeConverter: {
            return self.data
        })]
    }
}

I wonder if you need a property converter at all when you use Any? Or maybe you would not need it when its of NSObject?

maulikshah09 commented 4 years ago

I have stuck Here. don't know what is the solution I have to converted In any But when I pass this model to main view controller then I want to convert on model... Is it possible..


data(Any) if array then convert

to

let arrPharmacy : [PharmacyInfo]?


data(Any) if dictionary then

to

let arrPharmacy : PharmacyInfo?


class PharmacyInfo{
    var Address1 = ""
    var Address2 = ""
    var City = ""
    var ContactPerson = ""
    var EmailId = ""
    var EntryDate = ""
    var IsDelete = ""
    var ModifyDate = ""
    var PId = ""
    var PharmacyName = ""
    var honeNumer = ""
    var serId = ""
}
maulikshah09 commented 4 years ago

Please reply on this...

evermeer commented 4 years ago

EVObject has an init with dictionary an Array as an init with dictionaryArray. So if you make the base class of PharmacyInfo EVObject then you could do this: var myPI = PharmacyInfo(dictionary: theDictionary) or var myPIarray = [PharmacyInfo](dictionaryArray: theDictionaryArray)

maulikshah09 commented 4 years ago

Finally i got my solution...THanks...