3

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 ?

infiniteLoop
  • 2,135
  • 1
  • 25
  • 29

3 Answers3

2

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.

PGDev
  • 23,751
  • 6
  • 34
  • 88
1

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.

Qiniso
  • 2,587
  • 1
  • 24
  • 30
1

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
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848