3

I've created a custom Decoder that decodes a very specific data format, following this tutorial.

Please note, my data format is not XML or JSON.

I started with:

public final class MyDecoder: Decoder {
    public var codingPath: [CodingKey] = []
    public var userInfo: [CodingUserInfoKey:Any] = [:]
    public let input: String
    
    public init(_ input: String) {
        self.input = input
    }

    // all stubs are below

And added all the stubs needed for the protocol. Then, following the video, I also added a struct that conforms to KeyedDecodingContainerProtocol, and filled out the required parsing in decode(_ type: String.Type, forKey key: Key).

To get the info out, I do this:

let decoder = MyDecoder(input)
let info = try! myRecord(from: decoder)

So far so good this works.

What I want to do next is to see if it is possible to do this like is done for a JSONDecoder:

let decoder = MyDecoder()
guard let info = try? decoder.decode(MyInfo.self, from: input) else {
   fatalError("Failed to decode data")
}

But of course that doesn't work, because MyDecoder doesn't have a decode() function.

So, my question is, if I add this function, what should go in it?

koen
  • 5,383
  • 7
  • 50
  • 89
  • 2
    You can take a look at [the implementation of `JSONDecoder`](https://github.com/apple/swift/blob/master/stdlib/public/Darwin/Foundation/JSONEncoder.swift#L1038) to get a better sense of how this works, but in general, the type you write which conforms to `Decoder` is usually _not_ the top-level type which implements `decode(_:from:)`. `Decoder` is used at the "middle" and "bottom" levels of decoding but the top-level interface is different. `JSONDecoder` uses a private `_JSONDecoder` class as its actual `Decoder` instance. – Itai Ferber Aug 17 '20 at 22:02
  • 1
    In other words, the tutorial gives you a working `Decoder` implementation, but you'll likely want to wrap it in another type to give a different API surface which is _not_ accessible during an actual `init(from:)`. – Itai Ferber Aug 17 '20 at 22:04
  • 1
    https://developer.apple.com/documentation/combine/topleveldecoder – Leo Dabus Aug 17 '20 at 22:23

1 Answers1

2

So based on the very useful comments, I changed my code as follows. First I added a top level MyDecoder struct, and changed the name of the original decoder to _MyDecoder.

public struct MyDecoder: TopLevelDecoder, Decodable {
    public typealias Input = String

    public init() {}

    public func decode<T>(_ type: T.Type, from input: String) throws -> T where T : Decodable {
        let decoder = _MyDecoder(input)
        
        return try! MyRecord(from: decoder) as! T
    }    
}

Alternatively, this also works without using the TopLevelDecoder protocol (and thus can run on < iOS 13):

//...

public func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
   let decoder = _MyDecoder(input)
        
   return try! MyRecord(from: decoder) as! T
}    

And now I can call the decoder as follows:

let decoder = MyDecoder()
return try! decoder.decode(MyRecord.self, from: input)
koen
  • 5,383
  • 7
  • 50
  • 89