[swift-users] Decodable where T: MyProtocol?

Stephen Celis stephen.celis at gmail.com
Mon Jul 17 08:39:57 CDT 2017


TL;DR: I want to decode types differently based on whether they conform to a protocol on top of "Decodable". I've figured out how to get keyed containers to decode these types, but can't figure out how to do the same for un-keyed and single value containers. Is it possible?

---
I have written some code that generates arbitrary values given a Decodable type. E.g.:


    struct User: Decodable {
      let id: Int
      let name: String
    }

    dump(User.arbitrary)
    // ▿ User
    //   - id: 787375
    //   - name: "服쎱竺观듰믻뜪킲啨磟°"


This works great! But the ability to override ".arbitrary" on a per-value basis would be much better. Something like:


    protocol Arbitrary: Decodable {
      static var arbitrary: Self { get }
    }

    struct User: Arbitrary {
      static var arbitrary: User {
        return User(
          id: Int(arc4random_uniform(100)),
          name: Fake.names[Int(arc4random_uniform(UInt32(Fake.names.count)))]
        )
      }
    }

    dump(User.arbitrary)
    // ▿ User
    //   - id: 43
    //   - name: "A. Anypenny"


This solves the problem for root, static calls (more on static vs. dynamic dispatch in a bit). So what about nested instances?


    struct Project: Decodable {
      let owner: User
    }

    dump(Project.arbitrary)
    // ▿ Project
    //   ▿ owner: User
    //     - id: 464786
    //     - name: "뗗涮ऒꂆ鳔ᩘꦞ꺰䙢﶑覧똮漽翮귦ꜛ●㬪ⴾ枵⿵먳⏈彲≲芁۫⨸콬蘆윰拺握ஷ䰒"


Oof. Well luckily I've found that I can extend "KeyedDecodingContainer" and convince the decoder to use my protocol:


    extension KeyedDecodingContainer {
      public func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Arbitrary {
        return T.arbitrary
      }
    }

    dump(Project.arbitrary)
    // ▿ Project
    //   ▿ owner: User
    //     - id: 76
    //     - name: "C. Doodler"


Unfortunately I've had no such luck convincing un-keyed containers to decode using this protocol:


    dump([User].arbitrary)
    // ▿ 2 elements
    //   ▿ User
    //     - id: 980813
    //     - name: "㎜⽪羋⢢"
    //   ▿ User
    //     - id: -216180
    //     - name: "橿鰉疍旱콠싺힞㘇"


("[Int: User].arbitrary" fails similarly, though I think "Dictionary" is technically a keyed container...)

I've tried adding similar "decode" functions as I did on the keyed container, but they don't seem to get called.

Now back to the static dispatch of "User.arbitrary". Any interface that takes a "T: Decodable" is going to dynamically dispatch to the single value decoder and bypass my overload all over again :(

(For un-keyed containers, I found that I could add extensions all day long to "Array where Element: Arbitrary", "Optional where Wrapped: Arbitrary", etc., but these too would only work from root-level static dispatch.)

My hope is I've overlooked something in the decodable interfaces that would allow my un-keyed and single value containers to decode "Arbitrary" data differently than "T: Decodable". Can anyone help me make my hopes come true?


---
Stephen



More information about the swift-users mailing list