0

I have a generic struct allowing different types to be used. I do not want to constrain the whole struct to only Decodable items.

What is the best way to fix the following error, where I try to only execute some code if T conforms to Decodable: Instance method '...' requires that 'T' conform to 'Decodable'

struct Something<T> {
    ...

    func item<T>(from data: Data) -> T? where T: Decodable {
        try? JSONDecoder().decode(T.self, from: data)
    }
    
    func getter() -> T {
        let value = ...
        
        if let value = value as? T { return value } // something simple like string
        if let data = value as? Data, T.self is Decodable { // something complex
            return item(from: data) ?? defaultValue // error is thrown here
        }
        
        return defaultValue
    }
}

As you can see I'm checking the conformance with the if-clause, but that isn't enough to access the constrained method? :/

smat88dd
  • 2,258
  • 2
  • 25
  • 38

3 Answers3

4

It makes no sense to me that T only needs to conform to Decodable in some part but not others. I would rewrite the struct as

struct Something<T: Decodable> {

    func item(from data: Data) -> T?  {
        try? JSONDecoder().decode(T.self, from: data)
    }

    func getter() -> T {
        let value = ...
     
        if let data = value as? Data
            return item(from: data) ?? defaultvalue
        }

        return defaultvalue
    }
}
Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • Because there might be types which do not conform to Decodable? And for those other types I still would like to use the same struct to handle it. – smat88dd Jul 03 '20 at 09:02
  • I don't think it is a good idea to mix it like that, maybe have T not conform to Decodable and have a separate Factory class (or factory methods) that can create `Something` objects from different types of input like json data. I think your `getter` function will soon become a giant mess if you need to support different conversion of different types to T. You want to have that outside of your class. – Joakim Danielson Jul 03 '20 at 09:04
0

First of all you should constrain T to Decodable when you define the struct. Second, you must not define T as a generic parameter of the functions internally as it will not be treated as the same T the struct conforms to by the compiler. Instead it will be treated as a new and different generic type constraint (that you just happened to give the same name). Doing it like this is enough:

struct Something<T: Decodable> {
  
  var defaultValue: T
  var data: Data
  
  func item(from data: Data) -> T? {
    try? JSONDecoder().decode(T.self, from: data)
  }
  
  func getter() -> T {
    item(from: data) ?? defaultValue
  }
}
erik_m_martens
  • 496
  • 3
  • 8
0

You can use an extension to define the more constrained method:

struct Something<T> {
   var defaultValue: T

   func getter() -> T {
      return defaultValue
   }
}

extension Something where T: Decodable {
   func getter() -> T {

      // I'm assuming here that you have a property data: Data
      try? JSONDecoder().decode(T.self, from: data) ?? defaultValue
   }
}

It's not entirely clear how you're going to use this type in a meaningful way. The way your code is constructed, value is Any type. Is this what you meant? (I'm guessing, not)

Somewhere, you'd need to make a concrete version of Something - i.e. it would be Something<Int> or Something<String> or Something<SomeDecodableType> - at that point T will be that concrete type, and as you can see, there's nothing common between the various versions of T, except Any.

So figure out what parts of Something truly are common.

New Dev
  • 48,427
  • 12
  • 87
  • 129