13

Recently i incorporated Codable in a project and to get a JSON object from a type conforming to Encodable i came up with this extension,

extension Encodable {

    /// Converting object to postable JSON
    func toJSON(_ encoder: JSONEncoder = JSONEncoder()) -> [String: Any] {
        guard let data = try? encoder.encode(self),
              let object = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
              let json = object as? [String: Any] else { return [:] }
        return json
    }
}

This works well but could there be any better way to achieve the same?

Kamran
  • 14,987
  • 4
  • 33
  • 51
  • Not related but You can combine 3 separate guard condition in one comma separated !! – Prashant Tukadiya Oct 22 '18 at 05:38
  • Why do you need this? Do you use it with alamofire? – metinn Oct 22 '18 at 05:40
  • I use it with Alamofire. In some cases it needs to add/remove a key-value from the object's JSON or replace values for some keys while sending without changing the object. – Kamran Oct 22 '18 at 05:48
  • You could add a collection `[String: Codable]` to your object to store keys that can change. Then you will only need JSONEncoder to convert object to json data. Use that data to prepare URLRequest and send it to Alamofire's `request` method that takes URLRequestConvertible protocol – metinn Oct 22 '18 at 06:21
  • 3
    The name of the function is misleading. You are going to convert structs to a dictionary via JSON. **Never** ignore `Codable` and `JSONSerialization` errors. Make the function `throw`, remove the question marks after `try` and hand over the error to the caller. – vadian Oct 22 '18 at 06:47

2 Answers2

28

My suggestion is to name the function toDictionary and hand over possible errors to the caller. A conditional downcast failure (type mismatch) is thrown wrapped in an typeMismatch Decoding error.

extension Encodable {

    /// Converting object to postable dictionary
    func toDictionary(_ encoder: JSONEncoder = JSONEncoder()) throws -> [String: Any] {
        let data = try encoder.encode(self)
        let object = try JSONSerialization.jsonObject(with: data)
        if let json = object as? [String: Any]  { return json }
        
        let context = DecodingError.Context(codingPath: [], debugDescription: "Deserialized object is not a dictionary")
        throw DecodingError.typeMismatch(type(of: object), context)
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
7

Convert encodable object to JSON string using this extension:

extension Encodable {
    /// Converting object to postable JSON
    func toJSON(_ encoder: JSONEncoder = JSONEncoder()) throws -> NSString {
        let data = try encoder.encode(self)
        let result = String(decoding: data, as: UTF8.self)
        return NSString(string: result)
    }
}
Ramis
  • 13,985
  • 7
  • 81
  • 100