简体   繁体   中英

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

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 ?

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 ), ie

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.

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.

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
}

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