Decoding a Codable with class type returned from a function actually returns an item with its superclass as type - swift

I wrote a custom decoder for a json response where I have a "content" field that can be decoded to various different classes, all of them inheriting from the same ContentItem superclass. I also have a function that returns a class type, which I use in my decoder (I know that there may be different ways to do this, but that's not the problem):
init(from decoder: Decoder) throws {
...
guard let type = type, let contentItemType = getItemClass(from: type) else { return }
content = try container.decode(contentItemType, forKey: .content)
}
func getItemClass(from type: ProductContentType) -> ContentItem.Type? {...}
ProductContentType is a String enum. The getItemClass function returns the correct class, I've checked while debugging:
getItemClass(from: .type1) === Item1Class.self //<-- this returns true
The problem is the following:
guard let type = type, let contentItemType = getItemClass(from: type) else { return }
content = try container.decode(contentItemType, forKey: .content)
// ^ In this case content is kind of class ContentItem
let downcastContent = content as? Item1Class // <- downcastContent is nil
content = try container.decode(Item1Class.self, forKey: .content)
// ^ In this case content is kind of class Item1Class
let downcastContent = content as? Item1Class // this works
Downcasting with the content in the first case returns nil, but it doesn't make sense to me. Also I've noticed that when decoding the item the init(from: Decoder) in the subclass is never called.
Is it supposed to be this way? I was expecting both decode to return a content with class Item1Class. Am I missing something in the decode process in the first case?

Is it supposed to be this way?
Yes. Note that you don't know exactly what the decoded type is - getItemClass could have returned Item1Class.self, or Item2Class.self, or Item3Class.self, and so on. But you do know that whatever it returns, it is a subclass of ContentItem, so at least, you can assign it to a variable of type ContentItem, so content could only be of type ContentItem.Type.
More accurately, getItemClass returns ContentItem.Type, which becomes the inferred type of contentItemType, and when you passed it to the decode method, the generic parameter T is inferred to be ContentItem. The return type of decode is also T, so content has the type ContentItem.
However, just like how even though contentItemType has the type ContentItem.Type, its value is actually a Item1Class.self, even though content has the type ContentItem, it actually has a reference to a Item1Class instance. You can show this by showing that casting succeeds:
let item1 = content as! Item1Class // this succeeds
or you can check the runtime type of content directly:
type(of: content) == Item1Class.self / true
JSONDecoder will actually look at the contentItemType that you passed to it. That parameter is not just used to infer T.
Here is a minimal reproducible example:
class Foo : Decodable {
let a: String
}
class Bar: Foo {
enum CodingKeys: CodingKey {
case b
}
let b: String
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
b = try container.decode(String.self, forKey: .b)
try super.init(from: decoder)
}
}
let json = """
{"a": "Foo", "b": "Bar"}
""".data(using: .utf8)
func getItemClass() -> Foo.Type {
Bar.self
}
let type = getItemClass()
let fooButItsActuallyBar = try JSONDecoder().decode(type, from: json!)
let bar = fooButItsActuallyBar as! Bar // this succeeds
print(bar.a, bar.b)
Note that in the above example, the subclass Bar has overridden the required initialiser to implement custom decoding, because the auto-generated implementation isn't generated for subclasses (See also). If you didn't override it, you will see the subclass's properties uninitialised. That could be another reason why you think decode decodes an instance of ContentItem.

Related

How do I (simply) "do something else" to the resulting struct after the JSONDecoder().decode does it's job?

Very simply, I have this:
public struct SomeAPI: Codable {
public let a: String
public let b: String
public let c: String
public let d: String
}
and I of course do this ...
let s = try? JSONDecoder().decode(SomeAPI.self, from: data)
Imagine I'm decoding a few hundred of those, perhaps in an array.
Very simply, after each one is decoded, I want to run some code.
So conceptually something like ..
public struct SomeAPI: Codable {
init-something() {
super.something = magic
print("I just decoded one of the thingies! a is \(a)")
}
public let a: String
public let b: String
public let c: String
public let d: String
}
I'm afraid I have absolutely no idea how to do that, having searched much. How is it done? It seems a shame to do it manually as a separate step.
The Encoding and Decoding Custom Types documentation is a great starting point for understanding how to implement the Encodable and Decodable method requirements to add to your types — and specifically, the Encoding and Decoding Manually section shows how to override the compiler-synthesized implementation of these methods to provide your own.
It's important to understand how the automated approach to Codable works, though. In a nutshell: for a type that adopts Encodable/Decodable, the compiler checks whether it's provided its own implementation of the associated method, and if not, attempts to fill it in on its own. There's no magic there: it fills in its internal state (manipulating the AST) with an implementation as if you had written it yourself in code.
For your example SomeAPI type, the generated code inside of the compiler looks exactly as if you had typed the following in yourself:
struct SomeAPI: Codable {
let a: String
let b: String
let c: String
let d: String
private enum CodingKeys: String, CodingKey {
case a, b, c, d
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
a = try container.decode(String.self, forKey: .a)
b = try container.decode(String.self, forKey: .b)
c = try container.decode(String.self, forKey: .c)
d = try container.decode(String.self, forKey: .d)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(a, forKey: .a)
try container.encode(b, forKey: .b)
try container.encode(c, forKey: .c)
try container.encode(d, forKey: .d)
}
}
This only happens if you haven't implemented the protocol requirement yourself, though: if you have, the compiler leaves the existing implementation alone, assuming you've done exactly what you want.
This last point is relevant to your use-case, because it sounds like what you'd like to do is implement this type like
struct SomeAPI: Codable {
let a: String
let b: String
let c: String
let d: String
init(from decoder: Decoder) throws {
somehowCallTheCompilerSynthesizedImplementationOfThisMethod()
/* perform some other stuff */
}
}
The issue is that when you provide your own init(from:) implementation, the compiler won't synthesize anything for your type, so there's no "default" implementation to call; unfortunately, it's an all-or-nothing deal.
To do this, then, you can either:
Implement init(from:) on your types by implementing the full method, then adding whatever code you'd like, or
Inherit the compiler-synthesized implementation in a bit of a roundabout way: if you turn SomeAPI into a class, you can allow it to receive the default synthesized implementation, then override the synthesized implementation to do what you want (demonstrated here by moving its contents into a "dummy" parent type, then keep using SomeAPI as a subclass):
class _SomeAPI: Codable {
let a: String
let b: String
let c: String
let d: String
}
class SomeAPI: _SomeAPI {
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
/* do whatever */
}
}
The downside of this approach, of course, is that this changes the semantics of the SomeAPI type, which may very well not be what you want
Approach (1) is likely a good starting approach as it keeps the semantics of this type the same, and there are tools out there that can help automatically output code for a given Swift type (e.g. Sourcery) if this gets repetitive.

Decoding objects without knowing their type first

There is a likelihood this is an XY problem, I am open to these suggestions as well !
I am trying to work with Minecraft save data. Minecraft encodes Entities (basically anything that is not strictly a block) with their type inside an id property . The file then contains a big array of entities, which I want to decode and instantiate.
The problem is that, using Decodable, I must know an object's type before I start instantiating it like container.decode(Zombie.self). I can't figure out how to create a function that would read the id and return the right type of entity ?
I think this explains what I need better than any explanation could :
//Entity objects don't actually store their ID since re-encoding it is trivial.
protocol Entity : Decodable {var someProperty : Int {get set}}
struct Zombie : Entity {var someProperty : Int}
struct Skeleton : Entity {var someProperty : Int}
//Using JSON instead of SNBT so we can use JSONDecoder
let jsonData = """
[
{
"id":"zombie",
"someProperty":"3"
},
{
"id" : "skeleton",
"someProperty":"3"
}
]
"""
struct EntityList : Decodable {
var list : [any Entity] = []
init(from decoder : Decoder) throws {
var container = try decoder.unkeyedContainer()
//What should we put here ?
}
}
let decoder = JSONDecoder()
let entityList = try decoder.decode(EntityList.self, from: Data(jsonData.utf8))
//entityList should be [Zombie, Skeleton]
At the moment I'm looking into the Factory pattern, maybe that's an interesting lead ? In any case, thank you for your help !
( Please note this question has nothing to do with decoding the actual binary contents of the file, it was honestly quite hard to do but I already have a working Encoder / Decoder. It is only about unpacking those contents, hence why I just used JSON in the example above, since we have a common Decoder for that. )
I honestly haven't used the new any syntax enough to know if that can help but I have done what you're trying to do numerous times and here is how I do it.
Set up the data first
We first declare what a Zombie and a Skeleton are. They could just inherit from a protocol or they could be separate structs...
struct Zombie: Decodable {
let someProperty: Int
}
struct Skeleton: Decodable {
let someProperty: Int
let skeletonSpecificProperty: String
}
Then we can turn your array of [anyEntityType] into a homogeneous array by using an enum and embedding the entities into it...
enum Entity: Decodable {
case zombie(Zombie)
case skeleton(Skeleton)
}
Decode the enum given your JSON structure
We have to provide a custom decoder for the Entity type...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
// First get the `id` value from the JSON object
let type = try container.decode(String.self, forKey: .id)
// check the value for each type of entity we can decode
switch type {
// for each value of `id` create the related type
case "zombie":
let zombie = try Zombie(from: decoder)
self = .zombie(zombie)
case "skeleton":
let skeleton = try Skeleton(from: decoder)
self = .skeleton(skeleton)
default:
// throw an error here... unsupported type or something
}
}
This should now let you decode an array of Entities from JSON into an [Entity] array.
Deal with "unknown" types
There is an extra step required for dealing with the "unknown" types. For instance, in the code above. If the JSON contains "id": "creeper" this will error as it can't deal with that. And you'll end up with your whole array failing to decode.
I've created a couple of helper functions that help with that...
If you create an object like...
struct Minecraft: Decodable {
let entities: [Entity]
enum RootKeys: String, CodingKey {
case entities
}
}
And these helpers...
extension KeyedDecodingContainer {
func decodeAny<T: Decodable>(_ type: T.Type, forKey key: K) throws -> [T] {
var items = try nestedUnkeyedContainer(forKey: key)
var itemsArray: [T] = []
while !items.isAtEnd {
guard let item = try? items.decode(T.self) else {
try items.skip()
continue
}
itemsArray.append(item)
}
return itemsArray
}
}
private struct Empty: Decodable { }
extension UnkeyedDecodingContainer {
mutating func skip() throws {
_ = try decode(Empty.self)
}
}
You can create a custom decoder for the Minecraft type like this...
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RootKeys.self)
self.entities = try container.decodeAny(Entity.self, forKey: .entities)
}

Xcode warning: Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten

Running Xcode 12, my Swift 5 Xcode project now has warnings whenever a Decodable or Codable type declares a let constant with an initial value.
struct ExampleItem: Decodable {
let number: Int = 42 // warning
}
Immutable property will not be decoded because it is declared with an initial value which cannot be overwritten
Xcode suggests changing the let to a var:
Fix: Make the property mutable instead
var number: Int = 42
It also suggests the fix:
Fix: Set the initial value via the initializer or explicitly define a CodingKeys enum including a 'title' case to silence this warning
What is the purpose of this new warning? Should it be heeded, or ignored? Can this type of warning be silenced?
Should Xcode's fix be implemented? Or is there a better solution?
Noah's explanation is correct. It’s a common source of bugs and it's not immediately obvious what’s happening due to the “magical” behaviour of Codable synthesis, which is why I added this warning to the compiler, since it brings your attention to the fact that the property won't be decoded and makes you explicitly call it out if that's the expected behaviour.
As the fix-it explains, you have a couple of options if you want to silence this warning - which one you choose depends on the exact behaviour you want:
Pass the initial value via an init:
struct ExampleItem: Decodable {
let number: Int
init(number: Int = 42) {
self.number = number
}
}
This will allow number to be decoded, but you can also pass around instances of ExampleItem where the default value is used.
You can also use it directly inside init instead, during decoding:
struct ExampleItem: Decodable {
let number: Int
private enum CodingKeys: String, CodingKey {
case number
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
number = try container.decodeIfPresent(Int.self, forKey: .number) ?? 42
}
}
This will allow number to be decoded, but use 42 as the default value if the decoding fails.
Make the property a var, although you can also make it a private(set) var:
struct ExampleItem: Decodable {
var number: Int = 42
}
Making it a var will allow number to be decoded, but it will also allow callers to modify it. By marking it as private(set) var instead, you can disallow this if you want.
Define an explicit CodingKeys enum:
struct ExampleItem: Decodable {
let number: Int = 42
private enum CodingKeys: CodingKey {}
}
This will prevent number from being decoded. Since the enum has no cases, this makes it clear to the compiler that there are no properties that you want to decode.
This warning appears because immutable properties with initial values don't participate in decoding - after all, they're immutable and they have an initial value, which means that initial value will never be changed.
For example, consider this code:
struct Model: Decodable {
let value: String = "1"
}
let json = """
{"value": "2"}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model)
This will actually print Model(value: "1"), even though the json we gave it had value as "2".
In fact, you don't even need to provide the value in the data you're decoding, since it has an initial value anyway!
let json = """
{}
"""
let decoder = JSONDecoder()
let model = try! decoder.decode(Model.self, from: json.data(using: .utf8)!)
print(model) // prints "Model(value: "1")"
Changing the value to a var means it will decode correctly:
struct VarModel: Decodable {
var value: String = "1"
}
let json = """
{"value": "2"}
"""
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
print(varModel) // "VarModel(value: "2")"
If you're seeing this error, it means your code has never correctly parsed the property in question when decoding. If you change it to a var, the property will be parsed correctly, which might be what you want - however, you should make sure that the data you're decoding always has that key set. For example, this will throw an exception (and crash since we're using try!):
let json = """
{}
"""
let decoder = JSONDecoder()
struct VarModel: Decodable {
var value: String = "1"
}
let varModel = try! decoder.decode(VarModel.self, from: json.data(using: .utf8)!)
In conclusion, Xcode's suggestion is probably viable in many cases, but you should evaluate on a case by case basis whether changing the property to a var will break your app's functionality.
If you want the property to always return the hard-coded initial value (which is what's happening right now), consider making it a computed property or a lazy var.
Solution: define an explicit CodingKeys enum to prevent id from decoded.
For example,
struct Course: Identifiable, Decodable {
let id = UUID()
let name: String
private enum CodingKeys: String, CodingKey {
case name
}
init(name: String) { self.name = name }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let name = try container.decodeIfPresent(String.self, forKey: .name)
self.name = name ?? "default-name"
}
}
The suggested workarounds by #SuyashSrijan suppresses the warning but may also lead to further developer errors.
I've written an alternative work around here:
public struct IdentifierWrapper<T>: Identifiable {
public let id = UUID()
public let value: T
}
Usage:
struct Model: Codable, Identifiable {
public let name: String
}
let wrapper = IdentifierWrapper(value: Model(name: "ptrkstr"))

Trying to make a class codable in Swift but it is not the correct format?

I am creating a class that conforms to codable.
I have this:
import Foundation
class Attribute : Decodable {
var number: Int16
var label: String?
var comments: String?
init(number:Int16, label:String?, comments:String?) {
self.number = number
self.label = label
self.comments = comments
}
// Everything from here on is generated for you by the compiler
required init(from decoder: Decoder) throws {
let keyedContainer = try decoder.container(keyedBy: CodingKeys.self)
number = try keyedContainer.decode(Int16.self, forKey: .number)
label = try keyedContainer.decode(String.self, forKey: .label)
comments = try keyedContainer.decode(String.self, forKey: .comments)
}
enum CodingKeys: String, CodingKey {
case number
case label
case comments
}
}
extension Attribute: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(number, forKey: .number)
try container.encode(label, forKey: .label)
try container.encode(comments, forKey: .comments)
}
}
This is apparently fine.
I create an instance of Attribute and encode it using:
let newAttribute = Attribute.init(number:value.number, label:value.label, comments:value.shortcut)
Then I create an array of these attributes and encode that array using
let array = try JSONEncoder().encode(array)
This will encode the array of Attribute to Data.
Then I try to convert the Data object back to the array of Attribute using this:
let array = try JSONDecoder().decode(Attribute.self, from: data) as! Array<Attribute>
First error I get is this:
Cast from 'Attribute' to unrelated type 'Array< Attribute>' always fails
If I remove the cast part I catch this error when the decode tries...
Optional("The data isn’t in the correct format.")
Any ideas?
You need to pass in the array to decode, don't pass in the array element type, then try to force-cast that to an array, that doesn't make any sense. YourType and Array<YourType> are two different and completely unrelated types, so you cannot cast one to the other and you need to use the specific type when calling JSONDecoder.decode(_:from:).
let array = try JSONDecoder().decode([Attribute].self, from: data)
Btw as already pointed out in your previous question, there is no need to manually write the init(from:) and encode(to:) methods or the CodingKeys enum since for your simple type, the compiler can auto-synthesise all of those for you. Also, if you used a struct instead of class, you'd also get the member wise initialiser for free.

What is difference between optional and decodeIfPresent when using Decodable for JSON Parsing?

I am using Codable protocol from Swift 4 first time, I am not able to understand use of decodeIfPresent from Decodable.
/// Decodes a value of the given type for the given key, if present.
///
/// This method returns `nil` if the container does not have a value associated with `key`, or if the value is null. The difference between these states can be distinguished with a `contains(_:)` call.
///
/// - parameter type: The type of value to decode.
/// - parameter key: The key that the decoded value is associated with.
/// - returns: A decoded value of the requested type, or `nil` if the `Decoder` does not have an entry associated with the given key, or if the value is a null value.
/// - throws: `DecodingError.typeMismatch` if the encountered encoded value is not convertible to the requested type.
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String?
Here it suggest that it returns nil, if value not present with associated key. If this is the only reason , then how it differ from optional property, as optional variable also set to nil if value is not present in response.
There's a subtle, but important difference between these two lines of code:
// Exhibit 1
foo = try container.decode(Int?.self, forKey: .foo)
// Exhibit 2
foo = try container.decodeIfPresent(Int.self, forKey: .foo)
Exhibit 1 will parse:
{
"foo": null,
"bar": "something"
}
but not:
{
"bar": "something"
}
while exhibit 2 will happily parse both. So in normal use cases for JSON parsers you'll want decodeIfPresent for every optional in your model.
Yes, #Sweeper's comment makes a sense.
I will try to explain it according to my understanding.
public class User : Decodable{
public var firstName:String
public var lastName:String
public var middleName:String?
public var address:String
public var contactNumber:String
public enum UserResponseKeys: String, CodingKey{
case firstName = "first_name"
case lastName = "last_name"
case middleName = "middle_name"
case address = "address"
case contactNumber = "contact_number"
}
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: UserResponseKeys.self)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.lastName = try container.decode(String.self, forKey: .lastName)
self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)
self.address = try container.decode(String.self, forKey: .address)
self.contactNumber = try container.decode(String.self, forKey: .contactNumber)
}
}
Above is my User class, in which I marked middleName as optional parameter, because it may possible that JSON response may not provide middleName key-value pair in response, so we can use decodeIfPresent.
self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)
While for others variables which are mandatory fields so we are sure that no need to use of optional for that. We used only decode for that as that method does not return optional.
public func decode(_ type: String.Type, forKey key: KeyedDecodingContainer.Key) throws -> String
Above decode function returns String while decodeIfPresent returns String?, so we can use optional variable to store that.
So final conclusion is that if you are not sure of service response contract or you may dealing with any third party services where JSON response and parameters may change without your knowledge then you can use decodeIfPresent so it can handle absence of particular parameter in response and set value as nil.
I think it makes sense to use decodeifPresent rather than an optional property if you want to use a default value for a property that could be missing from the JSON.
For example, let's examine 3 situations:
1. All the keys are present in the JSON:
Let's suppose you must decode this JSON:
{
"project_names": ["project1", "project2", "project3"],
"is_pro": true
}
You can use this struct:
struct Program: Codable {
let projectNames: [String]
let isPro: Bool
}
and you will get a Program object with a isPro value equal to true.
(I suppose your decoder keyDecodingStrategy is .convertFromSnakeCase in the rest of this example)
2. Some keys are missing in the JSON and you're ok to have an optional in Swift:
{
"project_names": ["project1", "project2", "project3"]
}
You can now use this struct:
struct Program: Codable {
let projectNames: [String]
var isPro: Bool?
}
and you will get a Program object with a isPro value equal to nil.
If the JSON looked like this:
{
"project_names": ["project1", "project2", "project3"],
"is_pro": true
}
then isPro would be a Bool? with value true.
Maybe that's what you want, but probably you would like to have a Bool with a default value of false. That's where decodeIfPresent could be useful.
3. Some keys are missing in the JSON and you want a non-optional property with a default value in Swift:
If your struct looks like this:
struct Program: Codable {
let projectNames: [String]
var isPro: Bool = false
}
then you will get a parsing error if the "is_pro" attribute is not present in your JSON. Because Codable expects to find a value to parse a Bool property.
In that situation, a good idea would be to have an initializer with decodeIfPresent, like so:
struct Program: Codable {
let projectNames: [String]
let isPro: Bool
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.projectNames = try container.decode([String].self, forKey: .projectNames)
self.isPro = try container.decodeIfPresent(Bool.self, forKey: .isPro) ?? false
}
}
This allows you to have the best of both worlds:
your struct has a Bool, not a Bool? property
you are still able to parse a JSON that does NOT contain the "is_pro" field
you can get a default value of false if the field is not present in the JSON.