Search code examples
swiftnsdatacodable

Why "Expected to decode Data but found an array instead" I have no arrays in my code


I have this codable struct:

struct Foo: Codable {
    let rect: CGRect
    let image: UIImage

    init(rect: CGRect, image: UIImage) {
        self.rect = rect
        self.image = image
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(rect, forKey: .rect)
        try container.encode(UIImagePNGRepresentation(image), forKey: .image)
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        rect = try container.decode(CGRect.self, forKey: .rect)
        image = try UIImage(data: container.decode(Data.self, forKey: .image))!
    }

    enum CodingKeys : CodingKey {
        case rect
        case image
    }
}

Now I create a Foo and attempt to encode it and decode it:

let image = UIImage(named: "my_image")
let foo = Foo(rect: .zero, image: image!)
let encoder = PropertyListEncoder()
let data = try! encoder.encode(foo)
let decoder = PropertyListDecoder()
try! decoder.decode(Foo.self, from: data)

Now at the decode line, this error occurs:

Swift.DecodingError.typeMismatch(Foundation.Data, Swift.DecodingError.Context(codingPath: [__lldb_expr_244.Foo.CodingKeys.image], debugDescription: "Expected to decode Data but found an array instead.", underlyingError: nil))

Apparently the decoder found an array while trying to decode the image data. Why does this happen? It seems that somehow the Data becomes an array. I am very confused because

Data() is Codable

evaluates to true, so the coders should theoretically be able to en/decode a Data. Why does it become an array?

Edit: I'm using Xcode 9.1

Here is my_image:

enter image description here


Solution

  • I'm sure this is a bug, it seems like the issue is during the encoding process, when encoding the UIImagePNGRepresentation data, which is returned as Optional things go really wrong, if you change the encode method to force the data like this:

    container.encode(UIImagePNGRepresentation(image)!, forKey: .image)
    

    instead of:

    container.encode(UIImagePNGRepresentation(image), forKey: .image)
    

    Things will work both in Xcode 9.1 and 9.2, if you leave the data optional as it is, the encoding will work only on 9.2 and fail 9.1, for some reason the image data size will be doubled on 9.1.

    I'm pretty sure it's this bug report on swift.org

    Which also mentions that it causes memory usage double the size of the data, which is what I'm seeing on my local setup.

    So the easy way around is to force the UIImagePNGRepresentation(..) data or upgrade to 9.2 where this bug is fixed.

    Hope this answered your question, very interesting find indeed!