Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom Struct: Type does not conform to protocol 'Decodable'

I would like to be able to save a Custom-struct to UserDefaults but for that I need it to be Codable.. I tried it like this:

struct Wishlist: Codable {
var name: String
var image: UIImage
var wishData: [Wish]
var color: UIColor
var textColor: UIColor
var index: Int
}

But that gives me this error:

Type 'Wishlist' does not conform to protocol 'Decodable'

Here is my Class Wish , maybe that's where the problem is:

class Wish: NSObject {
public var wishName : String?
public var checkedStatus : Bool?
public var wishLink : String?
public var wishPrice : String?
public var wishNote : String?
public var wishImage : UIImage?

init(withWishName name: String, link: String, price: String, note: String, image: UIImage, checked: Bool) {
    super.init()
    wishName = name
    checkedStatus = checked
    wishLink = link
    wishPrice = price
    wishNote = note
    wishImage = image
}
}

What am I doing wrong here??

like image 454
Chris Avatar asked Mar 02 '23 12:03

Chris


2 Answers

You will need to make Wish adopt Codable.

But because UIImage and UIColor are not Codable, you’ll have to manually implement them as outlined in Encoding and Decoding Custom Types:

struct Wishlist: Codable {
    var name: String
    var image: UIImage
    var wishes: [Wish]
    var color: UIColor
    var textColor: UIColor
    var index: Int

    init(name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int) {
        self.name = name
        self.image = image
        self.wishes = wishes
        self.color = color
        self.textColor = textColor
        self.index = index
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        name = try values.decode(String.self, forKey: .name)
        wishes = try values.decode([Wish].self, forKey: .wishData)
        color = try values.decode(Color.self, forKey: .color).uiColor
        textColor = try values.decode(Color.self, forKey: .textColor).uiColor
        index = try values.decode(Int.self, forKey: .index)

        let data = try values.decode(Data.self, forKey: .image)
        guard let image = UIImage(data: data) else {
            throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
        }
        self.image = image
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(wishes, forKey: .wishData)
        try container.encode(Color(uiColor: color), forKey: .color)
        try container.encode(Color(uiColor: textColor), forKey: .textColor)
        try container.encode(index, forKey: .index)
        try container.encode(image.pngData(), forKey: .image)
    }

}

struct Wish: Codable {
    public var name: String
    public var checkedStatus: Bool
    public var link: String
    public var price: String
    public var note: String
    public var image: UIImage

    init(name: String, link: String, price: String, note: String, image: UIImage, checkedStatus: Bool) {
        self.name = name
        self.checkedStatus = checkedStatus
        self.link = link
        self.price = price
        self.note = note
        self.image = image
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)

        name = try values.decode(String.self, forKey: .name)
        checkedStatus = try values.decode(Bool.self, forKey: .checkedStatus)
        link = try values.decode(String.self, forKey: .link)
        price = try values.decode(String.self, forKey: .price)
        note = try values.decode(String.self, forKey: .note)

        let data = try values.decode(Data.self, forKey: .image)
        guard let image = UIImage(data: data) else {
            throw DecodingError.dataCorruptedError(forKey: .image, in: values, debugDescription: "Invalid image data")
        }
        self.image = image
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)

        try container.encode(name, forKey: .name)
        try container.encode(checkedStatus, forKey: .checkedStatus)
        try container.encode(link, forKey: .link)
        try container.encode(price, forKey: .price)
        try container.encode(note, forKey: .note)
        try container.encode(image.pngData(), forKey: .image)
    }
}

Where I’d use this as a convenient way to encode UIColor objects:

struct Color: Codable {
    let red: CGFloat
    let green: CGFloat
    let blue: CGFloat
    let alpha: CGFloat

    init(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
        self.red = red
        self.green = green
        self.blue = blue
        self.alpha = alpha
    }

    init(uiColor: UIColor) {
        var red: CGFloat = 0
        var green: CGFloat = 0
        var blue: CGFloat = 0
        var alpha: CGFloat = 0

        uiColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

        self.red = red
        self.green = green
        self.blue = blue
        self.alpha = alpha
    }

    var uiColor: UIColor { UIColor(red: red, green: green, blue: blue, alpha: alpha) }
}

Note, I did a couple of unrelated changes:

  • I made both of these struct. I wouldn’t introduce reference types (much less NSObject subclasses) unless necessary.

  • I simplified some of the property names. E.g. in Wish, we wouldn’t generally use wish prefix in property names. I also wouldn’t use “data” in a property name unless it was, in fact, a Data.

  • I updated init methods to use standard naming conventions.


By the way, another approach is to avoid using UIKit types within model types at all. This eliminates the need for custom encoders/decoders at all. And you can have platform-specific extensions, that provide the necessary convenience initializers that bridge to the UIKit types. E.g.:

// MARK: - Wishlist

struct Wishlist: Codable {
    let name: String
    let imageData: Data     // rather than `UIImage`
    let wishes: [Wish]
    let color: Color        // rather than `UIColor`
    let textColor: Color    // rather than `UIColor`
    let index: Int
}

// MARK: Wishlist UIKit extension

#if os(iOS)
extension Wishlist {
    init(name: String, image: UIImage, wishes: [Wish], color: UIColor, textColor: UIColor, index: Int) {
        self.init(
            name: name,
            imageData: image.pngData()!,
            wishes: wishes,
            color: Color(uiColor: color),
            textColor: Color(uiColor: textColor),
            index: index
        )
    }

    var image: UIImage? { UIImage(data: imageData) }
}
#endif

// MARK: - Wish

struct Wish: Codable {
    let name: String
    let checkedStatus: Bool
    let link: URL           // rather than `String`
    let price: String
    let note: String
    let imageData: Data     // rather than `UIImage`
}

// MARK: Wish UIKit extension

#if os(iOS)
extension Wish {
    init(name: String, link: URL, price: String, note: String, image: UIImage, checkedStatus: Bool) {
        self.init(
            name: name,
            checkedStatus: checkedStatus,
            link: link,
            price: price,
            note: note,
            imageData: image.pngData()!
        )
    }

    var image: UIImage? { UIImage(data: imageData) }
}
#endif

Note, I not only eliminated UIColor from the model types, but also UIImage, too. Now, above I shifted the UIImage to Data, but really, the image payload probably does not belong in this model type at all. You should just have image URLs or asset identifiers, and decouple the image fetch and storage from the model altogether. (Because images and model objects tend to have very different memory characteristics, you often want images fetched as they are needed and stored within some flushable cache. But that is beyond the scope of this question.)

like image 145
Rob Avatar answered Apr 07 '23 05:04

Rob


in your case, you should add CodingKeys enum and not using UIColor nor UIImage data type. I got the same error before, but then I realized that the CodingKey doesn't match with the struct nor there is non-codable Data type. just change the data type to your custom codable object.

wrong example:

public struct DtClip: Codable {

    // MARK: Properties
    public var video: String?
    public var preview: String?
    public var clip: String?
    public var trailer: Any?

    enum CodingKeys: String, CodingKey {
        case video = "video"
        case preview = "preview"
        case clip = "clip"
    }
}

from the example we know that trailer isn't in the codingKeys yet. You should add all the props to the CodingKeys. And Any data type should be changed to codable data type like String, Int, or Trailer (custom codable data type). below is the correct example:

public struct DtClip: Codable {

    // MARK: Properties
    public var video: String?
    public var preview: String?
    public var clip: String?
    public var trailer: Trailer?

    enum CodingKeys: String, CodingKey {
        case video = "video"
        case preview = "preview"
        case clip = "clip"
        case trailer = "trailer"
    }
}

public struct Trailer: Codable {

    // MARK: Properties
    public var name: String?
    public var id: Int?

    enum CodingKeys: String, CodingKey {
       case name, url
    }
}
like image 23
Farras Doko Avatar answered Apr 07 '23 07:04

Farras Doko