This specific use-case is something that Codable
does not explicitly support in the general case, but is something you can support if you want (with additional work).
The essence of the issue here is that in order to decode value
, you need to decode(Base.self, forKey: .value)
— there is no indication that value
may be a Subclass
. That info either needs to come from the decode call itself, or from the payload you're decoding from. NSKeyedArchiver
, for instance, makes this work by encoding the name of the actual class you're trying to encode into the archive, so that on decode, with NSClassFromString
, you get back a Subclass
; this approach has shortcomings (primarily that in order to decode polymorphic values, you have to largely trust the information that's in the archive, which requires a lot of validation) which are discussed in Data You Can Trust.
Obj-C has it easy, though — the above works primarily because class names must be unique, and we don't have nested classes, private types, generic types, etc. You can't rely on class names in Swift to do this for a few reasons, stemming primarily from the fact that class names need not be unique:
- Class names are qualified in the runtime. You can have
Foundation.NSObject
andMyModule.NSObject
just fine; in order to disambiguate, you need to use the fully qualified name. This means, though, that names are more fragile — renaming your module changes the name of your class - Classes can be nested, and the fully qualified names need to reflect that.
MyModule.ClassA.NestedClassA
is different fromMyModule.ClassB.NestedClassA
. This introduces another layer of fragility: moving nested classes out of scope or into a different scope changes their name - Swift has generic classes, whose names are determined at runtime by their concrete type arguments.
MyGenericClass<T>
has a different runtime name fromMyGenericClass<U>
, so depending on how you ask for the type, you can get a different name - Classes can have the same name at the same scope if they are in different files and are
fileprivate
.private
andfileprivate
classes have their runtime names mangled to represent the file/scope they come from, which means that changing that scope/file can change the name. Name mangling has also not been stable over time (but will be one we hit ABI stability), which introduce complexity over time
Depending on your exact needs, these things may or may not be relevant to you — but in general, if you want to support this, you'll likely need a mechanism other than class names to identify types in archives stably over time. Because you're not looking to handle this in the general case, your data and usage patterns may lend themselves to one approach over another.
In the general case, this is something that you'd leave up to individual types to solve. If it makes sense for your use-case, you can have your Base
class know about its subclasses and can include specific identifiers in the payload to decode the right type; alternatively, you can encode a wrapping enum
with associated types for the individual types you expect to decode and it encodes/decodes a marker to do that.