20

I try to parse an api returning a json object. My problem is that some keys are sometime a string, sometime an object like the key "Value" in the following example:

[
{
    "Description": null,
    "Group": "Beskrivning av enheten",
    "GroupDescription": null,
    "Id": "Description",
    "Name": "Mer om enheten",
    "Value": "Det finns möjlighet till parkering på gatorna runt om, men det är kantstenar och ganska branta backar för att komma upp till lekplatsen.\r\n\r\nUtanför själva lekplatsen finns en gungställning med en plan omväg in. Alla lekredskap står i sandytor, det finns många kanter. Runt hela lekplatsen går ett staket med öppningar i olika riktningar."
},
{
    "Description": null,
    "Group": "Bilder och film",
    "GroupDescription": null,
    "Id": "Image",
    "Name": "Huvudbild",
    "Value": {
        "__type": "FileInfo",
        "Id": "8871b3b1-14f4-4054-8728-636d9da21ace",
        "Name": "ullerudsbacken.jpg"
    }
}
]

My struct looks like this:

struct ServiceUnit: Codable {
        let description: String?
        let group: String?
        let groupDescription: String?
        let id: String
        let name: String
        var value: String?
        struct ServiceUnitTypeInfo: Codable {
            let id: String
            let singularName: String?
            enum CodingKeys: String, CodingKey {
                case id = "Id"
                case singularName = "SingularName"
            }
        }
        let serviceUnitTypeInfo: ServiceUnitTypeInfo?
        let values: [String]?
        enum CodingKeys: String, CodingKey {
            case description = "Description"
            case group = "Group"
            case groupDescription = "GroupDescription"
            case id = "Id"
            case name = "Name"
            case value = "Value"
            case serviceUnitTypeInfo = "ServiceUnitTypeInfo"
            case values = "Values"
            case image = "Image"
        }
    }

I have to admin that I am totally lost (yes, I am a beginner in swift) and I can't find a solution to my problem. I understand that I have to use a custom init, but I don't know how.

Sylvain
  • 509
  • 1
  • 7
  • 17

2 Answers2

44

You can try

struct Root: Codable {
    let description,id: String
    let group,groupDescription: String?
    let name: String
    let value: MyValue

    enum CodingKeys: String, CodingKey {
        case description = "Description"
        case group = "Group"
        case groupDescription = "GroupDescription"
        case id = "Id"
        case name = "Name"
        case value = "Value"
    }
}

enum MyValue: Codable {
    case string(String)
    case innerItem(InnerItem)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        if let x = try? container.decode(String.self) {
            self = .string(x)
            return
        }
        if let x = try? container.decode(InnerItem.self) {
            self = .innerItem(x)
            return
        }
        throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        switch self {
        case .string(let x):
            try container.encode(x)
        case .innerItem(let x):
            try container.encode(x)
        }
    }
}

struct InnerItem: Codable {
    let type, id, name: String

    enum CodingKeys: String, CodingKey {
        case type = "__type"
        case id = "Id"
        case name = "Name"
    }
}

do {
  let result = try JSONDecoder().decode([Root].self,from:data)
  print(result)
}
catch {
  print(error)
}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • 2
    When I try to access the value of "value" for example to use it in a label.text, I get an error "Cannot assign value of type 'MyValue?' to type 'String?'". When I print the value to terminal, it says ... unknown context at 0x109d06188).MyValue.string ... Do you know how I can solve that problem? – Sylvain Oct 07 '18 at 07:19
  • @Sylvain it probably happens because it is enum, not String value. – Zaporozhchenko Oleksandr Jan 22 '19 at 16:57
  • 2
    How you will get value from variable value? Whenever I try print the value, it shows MyValue.string("value") – Nikhil Feb 26 '19 at 16:10
  • MyValue.string("value"), what is the "Value" , this exactly what we pass value to the enum, but before that we need to get that particular value. – Pintu Rajput Aug 31 '20 at 06:44
11

Building on the answer of @Sh_Khan and to answer the question of @Nikhi in the comments (how can you access the values) I like to do add this to the enum declaration:

var innerItemValue: InnerItem? {
    switch self {
    case .innerItem(let ii):
        return ii
    default:
        return nil
    }
}

var stringValue: String? {
    switch self {
    case .string(let s):
        return s
    default:
        return nil
    }
}
guido
  • 2,792
  • 1
  • 21
  • 40