13

Something I havent figured out or have been able to find online as of yet.

Is there a way to add additional fields onto a struct containing the decodable protocol in which are not present in the JSON Data?

For example and simplicity, say I have an array of json objects structured as such

{ "name": "name1", "url": "www.google.com/randomImage" }

but say I want to add a UIImage variable to that struct containing the decodable such as

struct Example1: Decodable {
    var name: String?
    var url: String?
    var urlImage: UIImage? //To add later
}

Is there a way to implement the decodable protocol in order to get the name and url from the JSON but allow me to add the UIImage later?

Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Rykuno
  • 458
  • 3
  • 14
  • @Rob: I don't think so--since `UIImage` isn't `Decodable` it won't even synthesize the protocol conformance – andyvn22 Sep 07 '17 at 21:09

2 Answers2

24

To exclude urlImage you must manually conform to Decodable instead of letting its requirements be synthesized:

struct Example1 : Decodable { //(types should be capitalized)
    var name: String?
    var url: URL? //try the `URL` type! it's codable and much better than `String` for storing URLs
    var urlImage: UIImage? //not decoded

    private enum CodingKeys: String, CodingKey { case name, url } //this is usually synthesized, but we have to define it ourselves to exclude `urlImage`
}

Before Swift 4.1 this only works if you add = nil to urlImage, even though the default value of nil is usually assumed for optional properties.

If you want to provide a value for urlImage at initialization, rather than using = nil, you can also manually define the initializer:

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        url = try container.decode(URL.self, forKey: .url)
        urlImage = //whatever you want!
    }
Jano
  • 62,815
  • 21
  • 164
  • 192
andyvn22
  • 14,696
  • 1
  • 52
  • 74
  • 5
    The compiler can actually synthesise `init(from:)` for you. You just need to say `var urlImage: UIImage? = nil` in order to satisfy its need for a default value for a property not listed in the `CodingKeys` (why it doesn't accept the implicit default value of `nil` I'm not too sure). – Hamish Sep 07 '17 at 21:15
  • thanks so much! That is kinda weird and that was also my problem I suppose. I was assigning the nil value to my variable excluded from the json property and kept throwing errors. – Rykuno Sep 07 '17 at 21:40
  • 1
    Needing to explicitly assign `nil` is a bug in the behavior (oversight, really) which I'm fixing now. This should not be necessary in the future. – Itai Ferber Sep 08 '17 at 00:32
  • 1
    And this is now fixed in Swift 4.1, you can omit the `= nil` :) – Hamish May 26 '18 at 16:57
  • Well in Swift 5 it requires the nil here. Also, it's nice to set it to nil as proposed in the comments but then how to you set it up after the decoding WITHOUT actually writting the init(from:) yourself? – Mikael Oct 24 '19 at 00:32
1

Actually, I'd make urlImage a lazy var. That way you don't have to worry about modifying the coding keys. All you have to do is write your getter, like so...

struct Example1 : Decodable {

    var name : String?
    var url  : URL?    // Better than String

    lazy var urlImage: UIImage? = {
        // Put your image-loading code here using 'url'
    }()
}
Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • The problem with that solution is it only works for var. If I want to do something like `let example = ...` I can't access urlImage anymore because example object is immutable now. – Sergnsk Dec 17 '18 at 08:43
  • His example specifically said 'to add later' which is why I wrote it like that, but if you want it to be treated as a 'let', at least from the outside world, just use `private(set) lazy var lazyTest: Int = { ... }` – Mark A. Donohoe Dec 19 '18 at 05:57