Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add a (Swift4) init to the Decodable protocol

I am trying to create a Codable extension that is capable of initialising a Decodable (Swift 4) object with only a json string. So what Should work is:

struct MyObject: Decodable {
   var title: String?
}

let myObject = MyObject(json: "{\"title\":\"The title\"}")

I think this means that I should create an init that calls the self.init with a Decoder. Here is the code that I came up with:

public init?(json: String) throws {
    guard let decoder: Decoder = GetDecoder.decode(json: json)?.decoder else { return }
    try self.init(from: decoder) // Who what should we do to get it so that we can call this?
}

That code is capable of getting the decoder, but I get a compiler error on calling the init. The error that I get is:

'self' used before self.init call

Does this mean that there is no way to add an init to the Decodable protocol?

For the complete source code see the codable extension on github

update: After debugging the solution below from @appzYourLive I found out that I had a conflict with the init(json: initialisers on Decodable and Array. I just published a new version of the extension to GitHub. I also added the solution as a new answer to this question.

like image 955
Edwin Vermeer Avatar asked Sep 06 '25 03:09

Edwin Vermeer


1 Answers

A possible workaround

DecodableFromString

A possible solution is defining another protocol

protocol DecodableFromString: Decodable { }

with its own initializer

extension DecodableFromString {
    init?(from json: String) throws {
        guard let data = try json.data(using: .utf8) else { return nil }
        guard let value = try? JSONDecoder().decode(Self.self, from: data) else { return nil }
        self = value
    }
}

Conforming to DecodableFromString

Now you need to conform your type to DecodableFromString

struct Person:Codable, DecodableFromString {
    let firstName: String
    let lastName: String
}

Result

And finally given a JSON

let json =  """
            {
            "firstName": "Luke",
            "lastName": "Skywalker"
            }
            """

you can build your value

if let luke = try? Person(from: json) {
    print(luke)
}

Person(firstName: "Luke", lastName: "Skywalker")

like image 140
Luca Angeletti Avatar answered Sep 07 '25 22:09

Luca Angeletti