Decoding this simple JSON struct is not working - swift

I have a simple model which I defined to decode a struct.
But it is failing at decoding.
Can any one tell me what i am doing wrong?
struct Model: Codable {
let firstName: String
let lastName: String
let age: Int
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let session = URLSession.shared
let url = URL(string: "https://learnappmaking.com/ex/users.json")!
let task = session.dataTask(with: url) { (data, response, error) in
let decoder = JSONDecoder()
let d = try! decoder.decode([Model].self, from: data!) //fails here
print(d)
}
task.resume()
}
}
I double checked to see if the json was correct, but it still fails to decode.
Error shown
Thread 5: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "firstName",
intValue: nil), Swift.DecodingError.Context(codingPath:
[_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No
value associated with key CodingKeys(stringValue: \"firstName\",
intValue: nil) (\"firstName\").", underlyingError: nil))
It keeps searching for firstName but i specifically have a enum to check for first_name.
This is the JSON Payload
[
{
"first_name": "Ford",
"last_name": "Prefect",
"age": 5000
},
{
"first_name": "Zaphod",
"last_name": "Beeblebrox",
"age": 999
},
{
"first_name": "Arthur",
"last_name": "Dent",
"age": 42
},
{
"first_name": "Trillian",
"last_name": "Astra",
"age": 1234
}
]
I know I can add decoder.keyDecodingStrategy = .convertFromSnakeCase but I want to know why the existing code is not working?

The code is correct, but apparently there is some problem with your model (although convertFromSnakeCase does work)
I retyped the struct and the error went away. Please copy and paste this
struct Model : Decodable {
let firstName : String
let lastName : String
let age : Int
private enum CodingKeys : String, CodingKey { case firstName = "first_name", lastName = "last_name", age }
}

Some of the values are optional, to be safe make all let as optional, It will work for sure.
struct Model: Codable {
let firstName: String?
let lastName: String?
let age: Int?
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}

Related

SwiftUI - No value associated with key CodingKeys

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?
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)

Serialize json for its sample as a string

i'm trying to serialize a json but i have an error could someone give me a guide please i'm doing wrong i'm new to swift[enter image description here][1]
let code = "00001"
let firstName = "Joe"
let lastName = "Doe"
let middleName = "Mc."
let age = 100
let weight = 45
let jsonObject: [String: [String:Any]] = [
"code": code, <---- Cannot convert value of type 'String' to expected dictionary value type '[String : Any]'
"attributeMap": [
"first_name": firstName,
"middle_name": middleName,
"last_name": lastName,
"age": age,
"weight": weight
]
]
if let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
let str = String(data: data, encoding: .utf8) {
print("===> \(str)")
}
--I would like a result like this JSON format
{
"code": "00001",
"attributeMap": {
"first_name": "Joe",
"middle_name": "Mc",
"last_name": "Doe",
"age": "23",
"weight": "home.zul"
}
}
If it repeatable operation I will prefer to use more "swifty" way. Let's define two structs
Person
struct Person: Encodable {
// Keys
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case middleName = "middle_name"
case lastName = "last_name"
case age
case weight
}
// Properties
let firstName: String
let middleName: String
let lastName: String
let age: Int
let weight: Double
}
The next one
Entity
struct Entity: Encodable {
// Keys
enum CodingKeys: String, CodingKey {
case code
case person = "attributeMap"
}
// Properties
let code: String
let person: Person
}
Usage
let person = Person(
firstName: "Joe",
middleName: "Mc.",
lastName: "Doe",
age: 100,
weight: 45
)
let entity = Entity(
code: "200",
person: person
)
if
let data = try? JSONEncoder().encode(entity),
let json = String(data: data, encoding: .utf8)
{
// Do something
}
Result
{"attributeMap":{"age":100,"last_name":"Doe","middle_name":"Mc.","weight":45,"first_name":"Joe"},"code":"200"}

Swift Decoding JSON from Amadeus

I have a problem with decoding JSON from API.
I get JSON:
{
"meta": {
"count": 1,
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations?subType=AIRPORT&keyword=barcel&sort=analytics.travelers.score&view=LIGHT&page%5Boffset%5D=0&page%5Blimit%5D=10"
}
},
"data": [
{
"type": "location",
"subType": "AIRPORT",
"name": "AIRPORT",
"detailedName": "BARCELONA/ES:AIRPORT",
"id": "ABCN",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/ABCN",
"methods": [
"GET"
]
},
"iataCode": "BCN",
"address": {
"cityName": "BARCELONA",
"countryName": "SPAIN"
}
}
]
}
My code looks like this:
struct Airport: Decodable {
let meta: MetaAirport
let data: AirportInfo
}
struct Address: Decodable{
let countryName: String
let cityName: String
}
struct AirportInfo: Decodable{
let address: Address
let subType: String
let id: String
let type: String
let detailedName: String
let name: String
let iataCode: String
}
struct MetaAirport : Decodable{
let count: Int
let links: Links
}
struct Links : Decodable{
let info: String
private enum CodingKeys : String, CodingKey {
case info = "self"
}
}
I'm trying to decode json using JSONDecoder on an AirportInfo object
do {
let airport = try JSONDecoder().decode(Airport.self, from: data!)
print(airport.data.name)
}
catch let err {
print(err)
}
I get an error
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
What am I doing wrong?
In json data, address is a object. Change your code:
let address: [Address] -> let address: Address
Your Model should be like that to parse
struct DataModel: Codable {
let meta: Meta?
let data: [Datum]?
}
struct Datum: Codable {
let type, subType, name, detailedName: String?
let id: String?
let datumSelf: SelfClass?
let iataCode: String?
let address: Address?
enum CodingKeys: String, CodingKey {
case type, subType, name, detailedName, id
case datumSelf = "self"
case iataCode, address
}
}
struct Address: Codable {
let cityName, countryName: String?
}
struct SelfClass: Codable {
let href: String?
let methods: [String]?
}
struct Meta: Codable {
let count: Int?
let links: Links?
}
struct Links: Codable {
let linksSelf: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
}
}
I see you have edited your question from the original, so it feels hard to correctly identify at a given moment in time where the issue is.
However, Codable is great and one easy way for you to work through it is by constructing your object little by little. There is no requirement for an object to identically represent your JSON IF you make things optional. So when debugging this kind of issues, make different properties optional and see what got parsed. This would allow you to identify little by little where you're doing things wrong, without causing crashes and keeping the correctly formatted parts.
Also, pro tip: If you plan to keep this code for anything more than 1 week, stop using force unwrapping ! and get used to handle optionals correctly. Otherwise this is a hard habit to stop and others will hate the code you've created ;)

Swift: How to convert Array of Dictionary to structure?

I have:
array = [["name": String, "lastName": String],
["name": String, "lastName": String],
["name": String, "lastName": String]]
(a: [Сlass.[String:String]]) -> [Class.SomeStruct] {}
How to make a structure with its properties from this array?
Like this:
struct SomeStruct {
let name: String
let lastName: String
}
You can use map or compactMap to transform your dictionaries into structs.
let array = [["name": "String", "lastName": "String"],
["name": "String", "lastName": "String"],
["name": "String", "lastName": "String"]]
struct SomeStruct {
let name: String
let lastName: String
}
let values = array.compactMap { data -> SomeStruct? in
guard let name = data["name"], let lastName = data["lastName"] else {
return nil
}
return SomeStruct(name: name, lastName: lastName)
}
Note: The compactMap will silently ignore all dictionaries that doesn't contains a name or a lastName key.
Use Codable
// MARK: - SomeStructElement
struct SomeStructElement: Codable, Equatable {
let name: Int?
let lastName: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case lastName = "lastName"
}
}
And then use JSONDecoder:
let someStructArray: Array<SomeStructElement> = try? JSONDecoder().decode(Array<SomeStructElement>.self, from: data) ?? []
Source: Encoding and Decoding Custom Types (Apple developer)

Codable. How decode dictionary to property [duplicate]

How does the Swift 4 Decodable protocol cope with a dictionary containing a key whose name is not known until runtime? For example:
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
Here we have an array of dictionaries; the first has keys categoryName and Trending, while the second has keys categoryName and Comedy. The value of the categoryName key tells me the name of the second key. How do I express that using Decodable?
The key is in how you define the CodingKeys property. While it's most commonly an enum it can be anything that conforms to the CodingKey protocol. And to make dynamic keys, you can call a static function:
struct Category: Decodable {
struct Detail: Decodable {
var category: String
var trailerPrice: String
var isFavorite: Bool?
var isWatchlist: Bool?
}
var name: String
var detail: Detail
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(intValue: Int) { self.intValue = intValue; self.stringValue = "\(intValue)" }
init?(stringValue: String) { self.stringValue = stringValue }
static let name = CodingKeys.make(key: "categoryName")
static func make(key: String) -> CodingKeys {
return CodingKeys(stringValue: key)!
}
}
init(from coder: Decoder) throws {
let container = try coder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.detail = try container.decode([Detail].self, forKey: .make(key: name)).first!
}
}
Usage:
let jsonData = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourite": null,
"isWatchlist": null
}
]
}
]
""".data(using: .utf8)!
let categories = try! JSONDecoder().decode([Category].self, from: jsonData)
(I changed isFavourit in the JSON to isFavourite since I thought it was a mispelling. It's easy enough to adapt the code if that's not the case)
You can write a custom struct that functions as a CodingKeys object, and initialize it with a string such that it extracts the key you specified:
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
Thus, once you know what the desired key is, you can say (in the init(from:) override:
let key = // whatever the key name turns out to be
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
So what I ended up doing is making two containers from the decoder — one using the standard CodingKeys enum to extract the value of the "categoryName" key, and another using the CK struct to extract the value of the key whose name we just learned:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here, then, is my entire Decodable struct:
struct ResponseData : Codable {
let categoryName : String
let unknown : [Inner]
struct Inner : Codable {
let category : String
let trailerPrice : String
let isFavourit : String?
let isWatchList : String?
}
private enum CodingKeys : String, CodingKey {
case categoryName
}
private struct CK : CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CodingKeys.self)
self.categoryName = try! con.decode(String.self, forKey:.categoryName)
let key = self.categoryName
let con2 = try! decoder.container(keyedBy: CK.self)
self.unknown = try! con2.decode([Inner].self, forKey: CK(stringValue:key)!)
}
}
And here's the test bed:
let json = """
[
{
"categoryName": "Trending",
"Trending": [
{
"category": "Trending",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
},
{
"categoryName": "Comedy",
"Comedy": [
{
"category": "Comedy",
"trailerPrice": "",
"isFavourit": null,
"isWatchlist": null
}
]
}
]
"""
let myjson = try! JSONDecoder().decode(
[ResponseData].self,
from: json.data(using: .utf8)!)
print(myjson)
And here's the output of the print statement, proving that we've populated our structs correctly:
[JustPlaying.ResponseData(
categoryName: "Trending",
unknown: [JustPlaying.ResponseData.Inner(
category: "Trending",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)]),
JustPlaying.ResponseData(
categoryName: "Comedy",
unknown: [JustPlaying.ResponseData.Inner(
category: "Comedy",
trailerPrice: "",
isFavourit: nil,
isWatchList: nil)])
]
Of course in real life we'd have some error-handling, no doubt!
EDIT Later I realized (in part thanks to CodeDifferent's answer) that I didn't need two containers; I can eliminate the CodingKeys enum, and my CK struct can do all the work! It is a general purpose key-maker:
init(from decoder: Decoder) throws {
let con = try! decoder.container(keyedBy: CK.self)
self.categoryName = try! con.decode(String.self, forKey:CK(stringValue:"categoryName")!)
let key = self.categoryName
self.unknown = try! con.decode([Inner].self, forKey: CK(stringValue:key)!)
}
Here's what I eventually came up for this json:
let json = """
{
"BTC_BCN":{
"last":"0.00000057",
"percentChange":"0.03636363",
"baseVolume":"47.08463318"
},
"BTC_BELA":{
"last":"0.00001281",
"percentChange":"0.07376362",
"baseVolume":"5.46595029"
}
}
""".data(using: .utf8)!
We make such a structure:
struct Pair {
let name: String
let details: Details
struct Details: Codable {
let last, percentChange, baseVolume: String
}
}
then decode:
if let pairsDictionary = try? JSONDecoder().decode([String: Pair.Details].self, from: json) {
var pairs: [Pair] = []
for (name, details) in pairsDictionary {
let pair = Pair(name: name, details: details)
pairs.append(pair)
}
print(pairs)
}
It is also possible to call not pair.details.baseVolume, but pair.baseVolume:
struct Pair {
......
var baseVolume: String { return details.baseVolume }
......
Or write custom init:
struct Pair {
.....
let baseVolume: String
init(name: String, details: Details) {
self.baseVolume = details.baseVolume
......