Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert a Swift object to a dictionary

I'm relatively new to iOS programming. However, I would have assumed that Swift would have an automated way of converting objects to JSON and vice versa. That being said, I have found several libraries that can do this.

HOWEVER...

It seems that no matter how you post data to a web service (even using something like AlamoFire), the requests must be a dictionary. All these forums show examples of how easy it is to convert the returned JSON string to objects. True. But the request needs to be manually coded. That is, go through all of the object properties and map them as a dictionary.

So my question is this: Am I missing something? Have I got this all wrong and there's a super-easy way to either (a) send JSON (instead of a dictionary) in the REQUEST or (b) convert an object automatically to a dictionary?

Again, I see how easy it is to deal with a JSON response. I'm just looking for an automatic way to convert the request object I want to post to a web service into a format that a library like AlamoFire (or whatever) requires. With other languages this is fairly trivial, so I'm hoping there's an equally easy and automated way with Swift.

like image 506
JL Gradley Avatar asked Aug 12 '15 16:08

JL Gradley


People also ask

Can a custom struct be used as a dictionary key Swift?

Any type that conforms to the Hashable protocol can be used as a dictionary's Key type, including all of Swift's basic types. You can use your own custom types as dictionary keys by making them conform to the Hashable protocol.

What is a swift dictionary?

Swift dictionary is an unordered collection of items. It stores elements in key/value pairs. Here, keys are unique identifiers that are associated with each value.

Is dictionary mutable in Swift?

If you assign a created dictionary to a variable, then it is always mutable which means you can change it by adding, removing, or changing its items. But if you assign a dictionary to a constant, then that dictionary is immutable, and its size and contents cannot be changed.


6 Answers

I must disagree with @Darko.

In Swift 2,

use protocol oriented programming and the simple reflection offered by Mirror class :

protocol JSONAble {}

extension JSONAble {
    func toDict() -> [String:Any] {
        var dict = [String:Any]()
        let otherSelf = Mirror(reflecting: self)
        for child in otherSelf.children {
            if let key = child.label {
                dict[key] = child.value
            }
        }
        return dict
    }
}

then you can use this protocol with your request class and produce the desired dictionary :

class JsonRequest : JSONAble {
    var param1 : String?
    // ...
}

let request = JsonRequest()
// set params of the request
let dict = request.toDict()
// use your dict
like image 111
fencingCode Avatar answered Oct 03 '22 14:10

fencingCode


My solution to this will be something like this:

extension Encodable {

    var dict : [String: Any]? {
        guard let data = try? JSONEncoder().encode(self) else { return nil }
        guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { return nil }
        return json
    }
}

and usage will be something like this:

movies.compactMap { $0.dict }
like image 20
Michał Ziobro Avatar answered Oct 03 '22 12:10

Michał Ziobro


Swift currently does not support advanced reflection like Java or C# so the answer is: no, there is not an equally easy and automated way with pure Swift.

[Update] Swift 4 has meanwhile the Codable protocol which allows serializing to/from JSON and PLIST.

typealias Codable = Decodable & Encodable
like image 23
Darko Avatar answered Oct 03 '22 14:10

Darko


Without using reflection, and works for nested objects (Swift 4):

protocol Serializable {
    var properties:Array<String> { get }
    func valueForKey(key: String) -> Any?
    func toDictionary() -> [String:Any]
}

extension Serializable {
    func toDictionary() -> [String:Any] {
        var dict:[String:Any] = [:]

        for prop in self.properties {
            if let val = self.valueForKey(key: prop) as? String {
                dict[prop] = val
            } else if let val = self.valueForKey(key: prop) as? Int {
                dict[prop] = val
            } else if let val = self.valueForKey(key: prop) as? Double {
                dict[prop] = val
            } else if let val = self.valueForKey(key: prop) as? Array<String> {
                dict[prop] = val
            } else if let val = self.valueForKey(key: prop) as? Serializable {
                dict[prop] = val.toDictionary()
            } else if let val = self.valueForKey(key: prop) as? Array<Serializable> {
                var arr = Array<[String:Any]>()

                for item in (val as Array<Serializable>) {
                    arr.append(item.toDictionary())
                }

                dict[prop] = arr
            }
        }

        return dict
    }
}

Just implement properties and valueForKey for the custom objects you want to convert. For example:

class Question {
    let title:String
    let answer:Int

    init(title:String, answer:Int) {
        self.title = title
        self.answer = answer
    }
}
extension Question : Serializable {
    var properties: Array<String> {
        return ["title", "answer"]
    }

    func valueForKey(key: String) -> Any? {
        switch key {
        case "title":
            return title
        case "answer":
            return answer
        default:
            return nil
        }
    }
} 

You can add more value types in the toDictionary function if you need.

like image 31
Michael Eilers Smith Avatar answered Oct 03 '22 14:10

Michael Eilers Smith


You can also use the ObjectMapper library. It has a "toJSON" method that converts your object to a dictionary.

like image 30
Remy Cilia Avatar answered Oct 03 '22 14:10

Remy Cilia


The latest solution that I found after lots of digging throughout Stack Overflow is:

//This block of code used to convert object models to json string
let jsonData = try JSONEncoder().encode(requestData)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)

//This method is used to convert jsonstring to dictionary [String:Any]
func jsonToDictionary(from text: String) -> [String: Any]? {
    guard let data = text.data(using: .utf8) else { return nil }
    let anyResult = try? JSONSerialization.jsonObject(with: data, options: [])
    return anyResult as? [String: Any]
}

//Use above method something like this
let params = jsonToDictionary(from: jsonString) ?? [String : Any]()
 
//Use params to pass in paramters
Alamofire.request(completeUrl, method: .post, parameters: params, encoding:JSONEncoding.prettyPrinted, headers: myHeaders){
        
        response in

       //Do whatever you want with response of it.
        
    }

Note:

  1. I combine this solution from multiple answers.
  2. This solution i used with alamofire because alamofire only accept parameter at this format "[String:Any]".
like image 45
Mr. Sagar Avatar answered Oct 03 '22 13:10

Mr. Sagar