Sylvain
Sylvain

Reputation: 529

Codable struct - enum when key missing when parsing json

I try to parse JSON data from an API. Some keys return multiple types or are not present sometimes. Everything works fine as long as the key (value) is here. But even if I declared it as optional in my struct, an error is thrown if the key is absent. The error is thrown from init block in enum MyValue.

My code looks like this:

struct ServiceUnit: 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)
    case double(Double)
    case int(Int)
    case bool(Bool)

    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
            if let string = try? container.decode(String.self)  {
                self = .string(string)
                return
            }
            if let innerItem = try? container.decode(InnerItem.self) {
                self = .innerItem(innerItem)
                return
            }
            if let double = try? container.decode(Double.self) {
                self = .double(double)
                return
            }
            if let int = try? container.decode(Int.self) {
                self = .int(int)
                return
            }
            if let bool = try? container.decode(Bool.self){
                self = .bool(bool)
            }
            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)
        case .double(let x):
            try container.encode(x)
        case .int(let x):
            try container.encode(x)
        case .bool(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"
    }
}

And the JSON looks like this:

{
    "Description": null,
    "Group": "Beskrivning av enheten",
    "GroupDescription": null,
    "Id": "Description",
    "Name": "Mer om enheten",
    "Value": "Förskolans inriktningen omfattar barns flerspråkighet och integration. Förskolan ligger i Järva med närhet till parker, skog och natur och vi tillbringar mycket tid där. Vi ger barnen möjligheter till upplevelser och inlärning där deras nyfikenhet och upptäckarlust får styra. Förskolan använder sig av ett språkutvecklande och utforskande arbetssätt under hela förskoledagen. Vi arbetar med pedagogisk dokumentation, vi observerar och reflekterar kring arbetssättet för att utveckla verksamheten framåt. Vi dokumenterar vad barnen gör, så att det blir synligt vad och hur barnen lär sig. \r\nPedagogerna skapar förutsättningar för barnens utveckling genom att, i en tillåtande miljö, ge dem möjligheter att få arbeta med material som inbjuder till rolig och utforskande lek. Förskolan har ett eget tillagningskök som erbjuder näringsrik och spännande mat."
},
{
    "Description": null,
    "Group": "Relaterade dokument",
    "GroupDescription": null,
    "Id": "Documents",
    "Name": "Filer",
    "Value": [
        {
            "__type": "FileInfo",
            "Id": "040e5147-35a4-488e-8356-f47dad1fdc68",
            "Name": "Forskolan_Umma___Hyppingeplan_-_Foraldrar_Forskola.pdf"
        },
        {
            "__type": "FileInfo",
            "Id": "41202e0d-b642-40d0-b2c4-5af871c5a028",
            "Name": "Spånga-Tensta_033527 Umma Förskola, Hyppingeplan 2017.pdf"
        }
    ]
}

I tried to do like this in init in enum MyValue:

 if container.decodeNil() == false {
   ...
 }else{
   self = .string("")
   return
 }

I also tried this in the struct init, but it doesn't work either:

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        value = try! container.decodeIfPresent(MyValue.self, forKey: .value)
        description = try container.decode(String.self, forKey: .description)
        id = try container.decode(String.self, forKey: .id)
        group = try container.decode(String.self, forKey: .group)
        groupDescription = try container.decode(String.self, forKey: .groupDescription)
        name = try container.decode(String.self, forKey: .name)
    }

The error is:

Err typeMismatch(stockholmsParks.detailViewController.MyValue, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 25", intValue: 25), CodingKeys(stringValue: "Value", intValue: nil)], debugDescription: "Wrong type for MyValue", underlyingError: nil))

Does anybody knows how to handle missing keys in this situation?

Upvotes: 1

Views: 841

Answers (1)

Shehata Gamal
Shehata Gamal

Reputation: 100523

The problem is that you assign a value to Value key that isn't String, Bool , Int , Double or InnerItem so return doesn't hit and control reaches to

 throw DecodingError.typeMismatch(MyValue.self,
  DecodingError.Context(codingPath: decoder.codingPath,
    debugDescription: "Wrong type for MyValue"))     

Note: Wrong type for MyValue in debugDescription

which prints the error you see in console , Btw making it optional will pass only if it looks like

"Value":null

or the key/value totally don't exist

Upvotes: 1

Related Questions