简体   繁体   中英

Swift Decodable nestedContainer and backwards compatibility

I just stumbled upon something but I can't find the behavior documented anywhere so I wanted to check if it is correct.

Scenario:

I make a Swift app that downloads and parses a JSON file. Please note that the actual implementation is more complex, so a custom implementation is needed for decoding (which is not the case in the below example, but it exhibits the same behaviour as the one I want to show)

The objects look like this:

enum ScreenName: String, Decodable, CaseIterable {
    case screen1 = "screen1"
    case screen2 = "screen2"
}

struct ScreenContents: Decodable {
 ...
}

struct Screens {
    let screens: [ScreenName: ScreenContents]

    enum CodingKeys: String, CodingKey {
        case screens
    }
}

The data:

{
  "screens":{
    "screen1":{..},
    "screen2":{..}
  }
}

The implementation to load the Screens :

extension Screens: Decodable {

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let screensContainer = try container.nestedContainer(keyedBy: ScreenName.self, forKey: .screens)

        for enumKey in screensContainer.allKeys {
               ....
        }
}

This works fine. The data downloaded from a URL is parsed an all is well.

Now I want to release a new version of my app, where I introduce case screen3 = "screen3" . For the new version of the app there is no isse, the item is added to the enum, the JSON on the server is updated and all works fine.

However I was worried about backwards compatibility. Since the current version of the app in production does not know about screen3 and therefore Swift cannot cast the incoming string to an enum, what will happen? Will an exception be thrown in init(from: decoder) ?

So I tried loading a more recent file, containing screen3 , with the above code. To my amazement it worked without throwing any errors.

What actually happens is that

let screensContainer = try container.nestedContainer(keyedBy: ScreenName.self, forKey: .screens)

ignored the screen3 entry and only returned screen1 and screen2 .

This is obviously good news as my backwards compatibility problem is solved, however I would like to see this backed by some kind of documentation, so I am not relying on a bug or another quirk before going live with this, and I didn't find any.

Can anyone help?

Thanks

The whole point of CodingKey is that it lists the keys that you're interested in. A keyed container is a view over data, and may not include all the data (in your case it doesn't include screen3 ). The documentation for allKeys touches on this (emphasis added):

Different keyed containers from the same decoder may return different keys here, because it is possible to encode with multiple key types which are not convertible to one another. This should report all keys present which are convertible to the requested type .

The type you've requested is ScreenName, which is an enum and supports two specific strings. So those are the only things you're going to get. But CodingKey does not have to be an enum. For example, I could build a struct CodingKey (and I do this all the time, and wish it would be added to stdlib).

struct StringKey: CodingKey, Hashable, CustomStringConvertible {
    var description: String {
        return stringValue
    }

    let stringValue: String
    init(_ string: String) { self.stringValue = string }
    init?(stringValue: String) { self.init(stringValue) }
    var intValue: Int? { return nil }
    init?(intValue: Int) { return nil }
}

This is a CodingKey that can accept any string at all (I use this to decode arbitrary JSON where the keys may not be known in advance). I could build another one that only accepted strings that began with some prefix and returned nil for everything else. CodingKeys can do all kinds of things.

But the final rule is that the keyed container only contains entries for keys that could be converted to the requested CodingKey.

So yes, this is officially backward compatible as written. You're not just lucky. This is how it works.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM