Home > Enterprise >  SwiftUI - No value associated with key CodingKeys
SwiftUI - No value associated with key CodingKeys

Time:10-16

I am currently trying to write a universal Request object in my SwiftUI application that aligns with an API I am building. I have written out something simple with the JSON payload that I have as an example and am running into an issue with the decoding of the object. Below is the Request object.

let json = """
{
    "object": "bottle",
    "has_more": false,
    "data": [
        {
          "id": "5ffa81e7-1d91-43b5-83cc-9ee1ab634c7b",
          "name": "14 Hands Hot to Trot White Blend",
          "price": "$8.99",
          "image": "https://cdn11.bigcommerce.com/s-7a906/images/stencil/1000x1000/products/8186/10996/14-Hands-Hot-to-Trot-White-Blend__56901.1488985626.jpg?c=2",
          "sku": "088586004490",
          "size": "750ML",
          "origination": "USA, Washington",
          "varietal": "White Wine",
          "information": "14 Hands Hot to Trot White Blend",
          "proof": 121.5,
          "brand_id": "1",
          "rating": 1,
          "review_count": 5
        }
    ]
}
"""

struct Request: Decodable {
    let object: String
    let has_more: Bool
    let data: [RequestData]
}

struct Bottle: Decodable {
    let id: String
    let name: String
    let price: String
    let image: String
    let sku: String
    let size: String
    let origination: String
    let varietal: String
    let information: String
    let proof: Float
    let brand_id: String
    let rating: Int
    let review_count: Int
}

enum ObjectType: String, Decodable {
    case bottle
}

enum BottleData: Decodable {
    case bottle(Bottle)
}

struct RequestData: Decodable {
    let hasMore: String
    let object: ObjectType
    let innerObject: BottleData

    enum CodingKeys: String, CodingKey {
        case hasMore
        case object
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.object = try container.decode(ObjectType.self, forKey: .object)
        self.hasMore = try container.decode(String.self, forKey: .hasMore)

        switch object {
        case .bottle:
            self.innerObject = .bottle(
                try Bottle(from: decoder)
            )
        }
    }
}

let decoder = JSONDecoder()
let requestData = try decoder.decode(Request.self, from: json.data(using: .utf8)!)

for data in requestData.data {
    switch data.innerObject {
    case .bottle(let bottle):
        print(bottle.name)
    }
}

I am recieving the following error when trying to test this object.

DecodingError
   keyNotFound : 2 elements
    - .0 : CodingKeys(stringValue: "object", intValue: nil)
     .1 : Context
       codingPath : 2 elements
        - 0 : CodingKeys(stringValue: "data", intValue: nil)
         1 : _JSONKey(stringValue: "Index 0", intValue: 0)
          - stringValue : "Index 0"
           intValue : Optional<Int>
            - some : 0
      - debugDescription : "No value associated with key CodingKeys(stringValue: \"object\", intValue: nil) (\"object\")."
      - underlyingError : nil

Given that I only have one object at the moment, I'm a little confused as to why this is not working as I expect (without any errors). Can anybody spot what I may be doing wrong here?

CodePudding user response:

Please read the error carefully.

The CodingPath components indicate the exact location of the error. It's

CodingKeys(stringValue: "object", intValue: nil)
CodingKeys(stringValue: "data", intValue: nil)
_JSONKey(stringValue: "Index 0", intValue: 0)
CodingKeys(stringValue: "object", intValue: nil).
 

which – translated to a key path – is

object.data[0].object

The actual error message

"No value associated with key CodingKeys(stringValue: "object", intValue: nil) ("object")."

states that there is no key object in the RequestData object which it is true.


I guess it's just a typo. Replace

let data: [RequestData]

with

let data: [Bottle]

and remove RequestData and the associated structs. They are pointless.


Edit:

You can do something like this, but as the JSON contains only one type the solution only decodes this particular JSON

let json = """
{
    "object": "bottle",
    "has_more": false,
    "data": [
        {
          "id": "5ffa81e7-1d91-43b5-83cc-9ee1ab634c7b",
          "name": "14 Hands Hot to Trot White Blend",
          "price": "$8.99",
          "image": "https://cdn11.bigcommerce.com/s-7a906/images/stencil/1000x1000/products/8186/10996/14-Hands-Hot-to-Trot-White-Blend__56901.1488985626.jpg?c=2",
          "sku": "088586004490",
          "size": "750ML",
          "origination": "USA, Washington",
          "varietal": "White Wine",
          "information": "14 Hands Hot to Trot White Blend",
          "proof": 121.5,
          "brand_id": "1",
          "rating": 1,
          "review_count": 5
        }
    ]
}
"""

enum RequestData {
    case bottle([Bottle])
}

enum ObjectType: String, Decodable {
    case bottle
}

struct Request: Decodable {
    let object: ObjectType
    let hasMore: Bool
    let data: RequestData
    
    enum CodingKeys: String, CodingKey { case hasMore = "has_more", object, data}
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.object = try container.decode(ObjectType.self, forKey: .object)
        self.hasMore = try container.decode(Bool.self, forKey: .hasMore)
        switch object {
            case .bottle:
                let bottleData = try container.decode([Bottle].self, forKey: .data)
                data = RequestData.bottle(bottleData)
        }
    }
}

struct Bottle: Decodable {
    let id: String
    let name: String
    let price: String
    let image: String
    let sku: String
    let size: String
    let origination: String
    let varietal: String
    let information: String
    let proof: Float
    let brand_id: String
    let rating: Int
    let review_count: Int
}

let decoder = JSONDecoder()
let requestData = try decoder.decode(Request.self, from: Data(json .utf8))
print(requestData)
  • Related