1

I have a Codable struct that is part of my app, RemoteData. I’m building a reusable package that will fetch the data and store it in UserDefaults. The data fetching, DataFetcher class has a Codable generic parameter. I am subclassing DataFetcher to pass in RemoteData as the generic param.

// in my app
struct RemoteData: Codable {
    var experimentOne: [Variant<[Page]>]
    var experimentTwo: [Variant<Bool>]
    var experimentThree: [Variant<String>]
}

All of the properties in RemoteData will be arrays of type Variant<T> where T is Codable:

// in my package
public struct Variant<T: Codable>: Codable, VariantProtocol {
    public var experimentName: String
    public var variantName: String
    public var percent: Int
    public var value: T
}

I’d like to be able to save this data in UserDefaults. I’d like to perform some filtering on the Variant array to see if this user should see that configuration. I’d like to save the data so that each experiment name is the key and the single variant the user should see is the value rather than the whole array. Although if the whole array is the only option, I’d be ok with that too.

However, since my DataFetcher doesn’t know what the properties are since it is just taking in a generic I don’t think I can do that. My first thought was to create a protocol that RemoteConfig confirms to and that the DataFetcher generic also conforms to.

// in my package, but subclassing in my app to provide url
open class DataFetcher<T: Decodable> {
    var remoteConfig: T?
    var url: URL

public init(url: String) {
    self.url = url
}

func fetchAndSaveData() { ... } 

}

That doesn’t work because I then need to specify T in Variant and I will only be able to have Variant arrays of one type.

I’m stuck here and not sure how to move forward.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
mergesort
  • 5,087
  • 13
  • 38
  • 63
  • 1
    The fundamental issue is that typical serialization formats like JSON, UserDefaults, etc. don't capture type information. To serialize values of different types, you need a mechanism that creates _new_ values that describe their own type in some way. So you have a hard-stop where the typesyste and generics must end. You're essentially going to need something like an `AnyVariant: Codable` with a `type` property that's an enum of string/int/etc. You're going to need to use that type to determine which decoding logic to use for its `value`. – Alexander Jul 15 '22 at 15:13
  • Got it, so you are suggesting that in the JSON I explicitly state the type using a string? In this example, `value` is an array of `Page` structs, which is made up of 3 String fields. How would I go about encoding things like String dicts and arrays and nested objects? I guess I could just make up different enum `type`s like "Array" etc. and even include a `type` field on the nested objects? Then I would just need to build parsers for any type that I want to use, which I guess will almost always be a Bool, String, Int and then arrays and dictionaries of those primitives – mergesort Jul 15 '22 at 15:26
  • "Got it, so you are suggesting that in the JSON I explicitly state the type using a string?" It depends on the serialization format. In the case of JSON, it already has some native types (string, number, bool, array, dict). You can just encode the value as one of those, and you can check the type of that encoded value in your Swift code, and produce one of several different structs based on the type of the value. – Alexander Jul 15 '22 at 16:08
  • How can I check the type of the encoded value in my swift code? That may be the piece that I'm missing here. – mergesort Jul 15 '22 at 16:23
  • I don't think you can read out the type, but yo ucan try decoding and catch the error (or just `try?`). See https://stackoverflow.com/a/60246989/3141234 and the api docs (e.g. for single values https://developer.apple.com/documentation/swift/singlevaluedecodingcontainer) – Alexander Jul 15 '22 at 16:38
  • I'm able to successfully decode my Struct. The problem is actually saving it to UserDefaults. I can save all the JSON easily, but I'd like to do some processing on the data before saving and save it as the `Variant` above (ideally the ones specified in my `RemoteData` struct). In other words, the `DataFetcher` package doesn't know about the specific types in `RemoteConfig`. My guess is that overriding some save method in the `DataFetcher` could help because then I'll have the types. I'd ideally like to do it in a loop or something, so I don't need to save each individually – mergesort Jul 15 '22 at 17:03
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/246479/discussion-between-alexander-and-mergesort). – Alexander Jul 15 '22 at 18:08

0 Answers0