How to Decode selected keys manually and rest with the automatic decoding with swift Decodable?

ε祈祈猫儿з 提交于 2020-01-24 20:14:48

问题


Here is the code I am using,

struct CreatePostResponseModel : Codable{
    var transcodeId:String?
    var id:String = ""
    enum TopLevelCodingKeys: String, CodingKey {
        case _transcode = "_transcode"
        case _transcoder = "_transcoder"
    }
    enum CodingKeys:String, CodingKey{
        case id = "_id"
    }
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: TopLevelCodingKeys.self)
        if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcode) {
            self.transcodeId = transcodeId
        }else if let transcodeId = try container.decodeIfPresent(String.self, forKey: ._transcoder) {
            self.transcodeId = transcodeId
        }

    }
}

Here, transcodeId is decided by either _transcode or _transcoder. But I want id and rest of the keys (not included here) to be automatically decoded. How can I do it ?


回答1:


You need to manually parse all the keys once you implement init(from:) in the Codable type.

struct CreatePostResponseModel: Decodable {
    var transcodeId: String?
    var id: String

    enum CodingKeys:String, CodingKey{
        case id, transcode, transcoder
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decodeIfPresent(String.self, forKey: .id) ?? ""
        if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcode) {
            self.transcodeId = transcodeId
        } else if let transcodeId = try container.decodeIfPresent(String.self, forKey: .transcoder) {
            self.transcodeId = transcodeId
        }
    }
}

In the above code,

  1. In case you only want to decode the JSON, there is no need to use Codable. Using Decodable is enough.
  2. Using multiple enums for CodingKey seems unnecessary here. You can use a single enum CodingKeys.
  3. If the property name and the key name is an exact match, there is no need to explicitly specify the rawValue of that case in enum CodingKeys. So, there is no requirement of "_transcode" and "_transcoder" rawValues in TopLevelCodingKeys.

Apart from all that, you can use keyDecodingStrategy as .convertFromSnakeCase to handle underscore notation (snake case notation), i.e.

do {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase //here.....
    let model = try decoder.decode(CreatePostResponseModel.self, from: data)
    print(model)
} catch {
    print(error)
}

So, you don't need to explicitly handle all the snake-case keys. It'll be handled by the JSONDecoder on its own.




回答2:


This can be one of the good solution for you wherever you want you can add multiple keys for one variable:

var transcodeId:String?

public init(from decoder: Decoder) throws {

    do {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        transcodeId =  container.getValueFromAvailableKey(codingKeys: [CodingKeys._transcoder,CodingKeys._transcode])
    } catch {
        print("Error reading config file: \(error.localizedDescription)")
    }
}

extension KeyedDecodingContainerProtocol{

    func getValueFromAvailableKey(codingKeys:[CodingKey])-> String?{
         for key in codingKeys{
             for keyPath in self.allKeys{
                 if key.stringValue == keyPath.stringValue{
                    do{ 
                        return try self.decodeIfPresent(String.self, forKey: keyPath)
                    } catch {
                        return nil
                    }
                }
            }
        }
        return nil
    }
}

Hope it helps.




回答3:


The compiler-generated init(from:) is all-or-nothing. You can’t have it decode some keys and “manually” decode others.

One way to use the compiler-generated init(from:) is by giving your struct both of the possible encoded properties, and make transcodeId a computed property:

struct CreatePostResponseModel: Codable {
    var transcodeId: String? {
        get { _transcode ?? _transcoder }
        set { _transcode = newValue; _transcoder = nil }
    }

    var _transcode: String? = nil
    var _transcoder: String? = nil

    var id: String = “”
    // other properties
}


来源:https://stackoverflow.com/questions/57215567/how-to-decode-selected-keys-manually-and-rest-with-the-automatic-decoding-with-s

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!