2

I have a JSON with a response body that appears as follows:

{
    "Count": 116,
    "Message": "Result returned successfully",
    "SearchCriteria": "Search Criteria",
    "Results": [
        {
            "Value": "",
            "ValueId": "",
            "Variable": "Suggested VIN",
            "VariableId": 142
        },
        < 115 more like this >
    ]
}

Initial Attempt

I initially attempted to create coding keys, as follows:

struct ResponseBody: Codable {
    var count: Int
    var message: String
    var searchCriteria: String
    var results: [Result]

    enum ResponseKeys: String, CodingKey {
        case count = "Count"
        case message = "Message"
        case searchCriteria = "SearchCriteria"
        case results = "Results"
    }
}

struct Result: Codable {
    var value: String?
    var valueId: String?
    var variable: String
    var variableId: Int

    enum ResultKeys: String, CodingKey {
        case value = "Value"
        case valueID = "ValueId"
        case variable = "Variable"
        case variableID = "VariableId"
    }
}

With no complaints from the compiler, I tried to decode it with this code:

// network request code
    let decoder = JSONDecoder()
    var responseData: ResponseBody?

    do {
      responseData = try decoder.decode(ResponseBody.self, from: data)
      guard let responseData = responseData else { return }
      let vehicle = self.createVehicleStruct(from: responseData)
      self.dispatchGroup.notify(queue: .main, execute: {
        completion(vehicle)
      })
    } catch {
      print(error, error.localizedDescription)
    }

which yielded this error:

keyNotFound(CodingKeys(stringValue: "count", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"count\", intValue: nil) (\"count\").", underlyingError: nil)) The data couldn’t be read because it is missing.

Working, but hideous attempt

Eager to "get on down the road", I tried the following and I was able to parse the JSON, but the non-conformance with Swift naming convention drives me batty:

struct ResponseBody: Codable {
    var Count: Int
    var Message: String
    var SearchCriteria: String
    var Results: [Result]
}

struct Result: Codable {
    var Value: String?
    var ValueId: String?
    var Variable: String
    var VariableId: Int
}

Now, I'm back to attempting to get nice, clean names in my structs now that I know the networking part works. Upon further further investigation, I needed to provide an init(from decoder:), so I refactored as follows:

struct ResponseBody: Decodable {
    var count: Int
    var message: String
    var searchCriteria: String
    var results: [Result]

    private enum CodingKeys: String, CodingKey {
        case count = "Count"
        case message = "Message"
        case searchCriteria = "SearchCriteria"
        case results = "Results"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let count: Int = try container.decode(Int.self, forKey: .count)
        let message: String = try container.decode(String.self, forKey: .message)
        let searchCriteria: String = try container.decode(String.self, forKey: .searchCriteria)
        let results: [Result] = try container.decode([Result].self, forKey: .results)

        // FIXME: Extra argument 'message' in call
        self.init(count: count, message: message, searchCriteria: searchCriteria, results: [results])
    }
}

struct Result: Decodable {
    var value: String?
    var valueID: String?
    var variable: String
    var variableID: Int

    private enum CodingKeys: String, CodingKey {
        case value = "Value"
        case valueID = "ValueId"
        case variable = "Variable"
        case variableID = "VariableId"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        let value: String = try container.decode(String.self, forKey: .value)
        let valueID: String = try container.decode(String.self, forKey: .valueID)
        let variable: String = try container.decode(String.self, forKey: .variable)
        let variableID: Int = try container.decode(Int.self, forKey: .variableID)

        // FIXME: Extra argument 'valueID' in call
        self.init(value: value?, valueID: valueID?, variable: variable, variableID: variableID)
    }
}

I'm getting an error in init(from decoder:) when I call the init:

Extra argument 'valueID' in call

I can't figure out where my mistake lies. If you have suggestions, I welcome your input. Thank you for reading.

4
  • Which Swift version are you using? Commented Apr 5, 2018 at 14:39
  • Not sure if this helps, but looks like they have made a few updates for Encodable protocol with swift 4.1 - benscheirman.com/2018/02/swift-4-1-keydecodingstrategy Commented Apr 5, 2018 at 14:39
  • @LucaAngeletti 4.1 Commented Apr 5, 2018 at 14:39
  • @LucaAngeletti It's in the title of the question. Commented Apr 5, 2018 at 14:39

1 Answer 1

11

Your enums must be called CodingKeys, and their case names must match the names of the struct properties (e.g. variableID and variableId are not the same, but they need to be).

As soon as you fix that, all will be well.

Thus I was able to parse the JSON that you provided, no problem, using these two structs:

struct ResponseBody: Codable {
    var count: Int
    var message: String
    var searchCriteria: String
    var results: [Result]

    enum CodingKeys: String, CodingKey {
        case count = "Count"
        case message = "Message"
        case searchCriteria = "SearchCriteria"
        case results = "Results"
    }
}

struct Result: Codable {
    var value: String?
    var valueId: String?
    var variable: String
    var variableId: Int

    enum CodingKeys: String, CodingKey {
        case value = "Value"
        case valueId = "ValueId"
        case variable = "Variable"
        case variableId = "VariableId"
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! I should have walked away for 5 minutes.
Why does my code run ok when I don't use "CodingKeys" specifically? I use custom names like MessageKeys, ChatKeys etc.
@tymac because you are also supplying an init(from:).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.