laurent espinosa
laurent espinosa

Reputation: 57

How to decode JSON with codable in Swift 4?

i have a big problem with decoding JSON with Codable

I got the error

Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.keyNotFound(SpecieKeys(stringValue: "v", intValue: nil), Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key SpecieKeys(stringValue: \"v\", intValue: nil) (\"v\").", underlyingError: nil))

I spend many time but I don't understand why .. :-(

This is my json

let myJson = """
       [{"i":"4","p":"4","l":["Ail"],"ll":["Allium sativum L."]},
        {"i":"20.1","l":["Artichaut"],"ll":["Cynara cardunculus"]},
        {"i":"XX.3",
         "l":["Tomate cerise"],
         "ll":["Solanum humboldtii"],
         "v":[{"s":1,
               "i":"0",
               "l":"Orange Grape Tress",
               "c":"Orange",
               "h":992,
               "ss":12
              }]

        }]
    """
    let jsonDATA = myJson.data(using: .utf8)!

and my struct

struct Specie : Decodable {
var id : String?
var name : [String]?
var latinName : [String]?
var varieties : [Variety]?

// keys
enum SpecieKeys: String, CodingKey {
    case id = "i"
    case name = "l"
    case latinName = "ll"
    case varieties = "v"
}

struct Variety : Decodable {
    var source : Int?
    var id : String?
    var color : String?
    var name : String?
    var photo : String?
    var harvest : Int?
    var semiShelter : Int?
    var semiOutside : Int?

    // keys
    enum VarietyKeys: String, CodingKey {
        case id = "i"
        case source = "s"
        case color = "c"
        case photo = "p"
        case harvest = "h"
        case semiShelter = "ss"
        case semiOutside = "so"
        case name = "l"
    }

    init(from decoder: Decoder) throws
    {
        let vValues = try decoder.container(keyedBy: VarietyKeys.self)
        id = try vValues.decode(String.self, forKey: .id)
        source = try vValues.decode(Int.self, forKey: .source)
        name = try vValues.decode(String.self, forKey: .name)
        color = try vValues.decode(String.self, forKey: .color)
        photo = try vValues.decode(String.self, forKey: .photo)
        harvest = try vValues.decode(Int.self, forKey: .harvest)
        semiShelter = try vValues.decode(Int.self, forKey: .semiShelter)
        semiOutside = try vValues.decode(Int.self, forKey: .semiOutside)
    }
}

init(from decoder: Decoder) throws
{
    let sValues = try decoder.container(keyedBy: SpecieKeys.self)
    id = try sValues.decode(String.self, forKey: .id)
    name = try sValues.decode(Array<String>.self, forKey: .name)
    latinName = try sValues.decode(Array<String>.self, forKey: .latinName)
    varieties = try sValues.decode(Array<Variety>.self, forKey: .varieties)
}
}

And the last code

var jsonResult = [Specie]()

jsonResult = try! JSONDecoder().decode(Array<Specie>.self, from: jsonDATA)

Someone can help me about my mistake.

Upvotes: 2

Views: 1541

Answers (1)

Sulthan
Sulthan

Reputation: 130072

For every optional value you have to use decodeIfPresent(:forKey) instead of decode(:forKey). decode(_:forKey) will fail your parsing when it finds a nil value ("No value associated with key ...")

However, the easier solution would be to let the compiler generate your decoding initializers:

struct Specie : Decodable {
    var id : String?
    var name : [String]?
    var latinName : [String]?
    var varieties : [Variety]?

    // keys
    enum CodingKeys: String, CodingKey {
        case id = "i"
        case name = "l"
        case latinName = "ll"
        case varieties = "v"
    }

    struct Variety : Decodable {
        var source : Int?
        var id : String?
        var color : String?
        var name : String?
        var photo : String?
        var harvest : Int?
        var semiShelter : Int?
        var semiOutside : Int?

        // keys
        enum CodingKeys: String, CodingKey {
            case id = "i"
            case source = "s"
            case color = "c"
            case photo = "p"
            case harvest = "h"
            case semiShelter = "ss"
            case semiOutside = "so"
            case name = "l"
        }
    }
}

The only thing necessary is to rename your key enums to CodingKeys so that the compiler can recognize them.

Upvotes: 3

Related Questions