Mojtaba Hosseini
Mojtaba Hosseini

Reputation: 119302

Conforming to more specified extended version of a protocol

Assuming there is an empty protocol to limit the types:

public protocol DataType { }

protocol Parser {
    func parseData<T: DataType>(_ data: Data, to: T.Type) throws -> T
}

So we need a parser specifically for parsing JSON objects:

typealias DecodableDataType = Decodable & DataType

protocol JSONParser: Parser {
    var jsonDecoder: JSONDecoder { get }
    func parseData<T: DecodableDataType>(_ data: Data, to: T.Type) throws -> T
}

So it is matching the parser needs too and as jsonDecoder is already defined, a simple extension would be great:

extension JSONParser {
    func parseData<T: DecodableDataType>(_ data: Data, to: T.Type) throws -> T { try jsonDecoder.decode(T.self, from: data) }
}

So if we implement a manager class for this:

class JSONParsingManager: JSONParser {
    public var jsonDecoder: JSONDecoder

    init(jsonDecoder: JSONDecoder) {
        self.jsonDecoder = jsonDecoder
    }
}

Expect to everything works automatically but it throws:

Type 'JSONParsingManager' does not conform to protocol 'Parser'

What I've missed? I need to define managers for other serializers like Protobuf parser and etc. so I can't just conform to Decodable at the first place.


More clarifying:

Another protocol that should work the same way:

protocol ProtobufParser: Parser {
    func parseData<T: Message>(_ data: Data, to: T.Type) throws -> T
}

extension ProtobufParser {
    func parseData<T: Message>(_ data: Data, to: T.Type) throws -> T { try T.init(serializedData: data) }
}

Update:

I can't define standalone protocols, because there is a function that needs to get any kind of Parser to parse any parsable objects.

P.S. This question probably had been asked before with a different title and scenario. Please feel free to mention the answer if you know how this question should be asked.

Upvotes: 0

Views: 89

Answers (2)

Max
Max

Reputation: 22325

There is a function that needs to get any kind of Parser to parse any parsable objects.

Why not use Swift's built-in Decodable protocol? It is designed to solve this problem for a broad category of data. Here is what it assumes: every kind of data you might want to parse is in one of these categories:

  1. Data representing a single primitive value, like a number or string
  2. Data representing a collection of objects, where each object is in one of these three categories
  3. Similar to 2, but each object also has a "key"

Note that these categories are recursive, so this very naturally represents something like JSON where you can have an array containing objects containing strings.

A decoder in Swift (such as JSONDecoder) is something that can take Data and try to interpret it as one of those three categories (specifically SingleValueDecodingContainer, UnkeyedDecodingContainer, or KeyedDecodingContainer).

Then a Decodable class knows how to take one of those three categories and instantiate itself.*

So as long as you think you can write initializers for all of your parsable objects that use one or more of those categories, your objects can simply conform to the built in Decodable protocol.

If you want a custom parser such as a ProtoBufParser, all you have to do is tell it how to produce one of the generic container types by implementing the Decoder protocol.

That is how you are allowed to mix and match any kind of parser with any kind of parsable object.


*Technically the Decodable object uses the Decoder itself to instantiate itself. This allows you, for example, to try multiple alternative decoding strategies in the constructor until something works.

Upvotes: 0

Max
Max

Reputation: 22325

The error message is accurate. The requirement of the protocol is

func parseData<T: DataType>(_ data: Data, to: T.Type) throws -> T

but you have an extension that implements

func parseData<T: Decodable & DataType>(_ data: Data, to: T.Type) throws -> T

i.e. your parser can only parse things that are Decodable but your protocol says it has to be able to parse anything conforming to DataType.


You might be able to use a where clause to specify the more specific requirement

extension JSONParser {
  func parseData<T: DataType>(_ data: Data, to: T.Type) throws -> T where T: Decodable {
    try jsonDecoder.decode(T.self, from: data)
  }

  func parseData<T: DataType>(_ data: Data, to: T.Type) throws -> T {
    throw CannotDecodeError
  }
}

Upvotes: 1

Related Questions