1

Suppose there is a json map whose key is String that converted from Int, such as "1". How can I declare a Swift enum whose rawValue is Int to match its key?

In the code listed below, if the key of map in ModelA is String, then the model will be deserialized correctly.

But if I replace String with EnumA, EnumB, EnumC or EnumD, the model will be nil at last.

struct ModelA: Codable {
    let pan: String

    let map: [String: ModelB] // Try to replace string with a swift enum.

    enum CodingKeys: String, CodingKey {
        case pan, map
    }
}

struct ModelB: Codable {
    let pan: String

    enum CodingKeys: String, CodingKey {
        case pan
    }
}

enum EnumA: Int, Codable {
    case a = 1
    case b = 2
}

enum EnumB: String, Codable {
    case a = "1"
    case b = "2"
}

enum EnumC: Int, Codable {
    case a = 1
    case b = 2

    enum CodingKeys: String, CodingKey {
        case a = "1", b = "2"
    }
}

enum EnumD: Int, Codable {
    case a = 1, b = 2

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        let status = try? container.decode(String.self)
        switch status {
        case "1":
            self = .a
        case "2":
            self = .b
        default:
            throw DecodingError.typeMismatch(EnumD.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "??????", underlyingError: nil))
        }
    }
}

extension String {
    func toModel<Model: Decodable>(_ type: Model.Type) -> Model? {
        guard
            let data = data(using: .utf8)
        else {
            return nil
        }
        let decoder = JSONDecoder()
        return try? decoder.decode(Model.self, from: data)
    }
}

let str2 = """
{"pan": "234", "map":{"1":{"pan": "aaa"}}}
"""

let model = str2.toModel(ModelA.self)
print(model)
bigface00
  • 31
  • 2
  • you just have to write custom decode/encode methods https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types – lorem ipsum Mar 14 '22 at 13:21
  • You are changing the behavior in `ModelA` not the `enum` write the custom decode and encode for `ModelA` – lorem ipsum Mar 14 '22 at 13:44

1 Answers1

0

Solution for iOS 15.4+, swift 5.6+ :

You can use CodingKeyRepresentable

Example:

struct ModelA: Codable {
    let pan: String

    let map: [EnumA: ModelB] // Try to replace string with a swift enum.

    enum CodingKeys: String, CodingKey {
        case pan, map
    }
}

struct ModelB: Codable {
    let pan: String

    enum CodingKeys: String, CodingKey {
        case pan
    }
}

enum EnumA: Int, Codable, CodingKeyRepresentable {
    case a = 1
    case b = 2
}

extension String {
    func toModel<Model: Decodable>(_ type: Model.Type) -> Model? {
        guard
            let data = data(using: .utf8)
        else {
            return nil
        }
        let decoder = JSONDecoder()
        return try? decoder.decode(Model.self, from: data)
    }
}

let str2 = """
        {"pan": "234", "map":{"1":{"pan": "aaa"}}}
        """
let model = str2.toModel(ModelA.self)
print(model)

Proposal have accepted

For swift 5.5 and earlier:

Dictionary only decoded the Int or String types. For other Keys it used UnkeyedDecodingContainer You can see source code Codable.swift by swift 5.6

And maybe you can find the solution here stackoverflow

Georgi
  • 11
  • 1