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)