Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Protocol extending Encodable (or Codable) does not conform to it

I have 2 protocols, Filters and Parameters, both of which extend Encodable

protocol Filters: Encodable {
    var page: Int { get }
}

protocol Parameters: Encodable {
    var type: String { get }
    var filters: Filters { get }
}

I create structs conforming to these protocols, thusly…

struct BankAccountFilters: Filters {
    var page: Int
    var isWithdrawal: Bool
}

struct BankAccountParamters: Parameters {
    let type: String = "Bank"
    var filters: Filters
}

let baf = BankAccountFilters(page: 1, isWithdrawal: true)
let bap = BankAccountParamters(filters: baf)

Which fails because…

error: type 'BankAccountParamters' does not conform to protocol 'Encodable'

note: cannot automatically synthesize 'Encodable' because 'Filters' does not conform to 'Encodable'

Filters clearly does conform to Encodable (at least it seems that way to me). Is there a way around this?

like image 512
Ashley Mills Avatar asked May 15 '18 08:05

Ashley Mills


2 Answers

As discussed in Protocol doesn't conform to itself?, a protocol does not conform to itself, or to a protocol that it inherits from. In your case, Filters does not conform to Encodable.

A possible solution is to make struct BankAccountParamters and protocol Parameters generic:

protocol Filters: Encodable {
    var page: Int { get }
}

protocol Parameters: Encodable {
    associatedtype T: Filters
    var type: String { get }
    var filters: T { get }
}

struct BankAccountFilters: Filters {
    var page: Int
    var isWithdrawal: Bool
}

struct BankAccountParamters<T: Filters>: Parameters {
    let type: String = "Bank"
    var filters: T
}

Now var filters has type T, which conforms to Filters and consequently, to Encodable.

This compiles and produces the expected result:

let baf = BankAccountFilters(page: 1, isWithdrawal: true)
let bap = BankAccountParamters(filters: baf)

let data = try! JSONEncoder().encode(bap)
print(String(data: data, encoding: .utf8)!)
// {"type":"Bank","filters":{"isWithdrawal":true,"page":1}}
like image 112
Martin R Avatar answered Sep 23 '22 02:09

Martin R


You cannot have protocol reference in the struct as the compiler will not be able to know the type at the time of encoding. Here is the bug reported SR-5853.

What you can do is create a type erasure for your protocol and use the erasure in place of protocol.

Something like this:

Update: As @MartinR answered there is no need of type erasure here.

protocol Filters: Encodable {
    var page: Int { get }
}

protocol Parameters: Encodable {
    associatedtype T: Filters
    var type: String { get }
    var filters: T { get }
}

struct BankAccountFilters: Filters {
    var page: Int
    var isWithdrawal: Bool
}

struct BankAccountParamters<T: Filters>: Parameters {
    let type: String = "Bank"
    var filters: T
}

let baf = BankAccountFilters(page: 1, isWithdrawal: true)
let bap = BankAccountParamters(filters: baf)

let encoder = JSONEncoder()
let data = try! encoder.encode(bap)
print(String(data: data, encoding: .utf8)!)

Here you will get the output:

{"type":"Bank","filters":{"isWithdrawal":true,"page":1}}
like image 21
Mukesh Avatar answered Sep 23 '22 02:09

Mukesh