I am trying to parse the API response using Codable class.
Below is the response :
{"status":200,"message":"","success":1,"data":[{"event_id":"26","event_name":"Mens Night","event_desc":"Hot Mens Night","from_date":"2019-02-08","to_date":"2019-03-09","bar_id":"62","bar_names":"Autumn Bar & Bistro","bar_ids":"62","offer_image":"https:\/\/www.tippler.app\/manager\/uploads\/events\/mens_night.jpg","from_time":"19:00:00","to_time":"01:30:00"},{"event_id":"36","event_name":"Karaoke Night","event_desc":"Karaoke NIght with Brian Rub","from_date":"2019-02-08","to_date":"2019-02-09","bar_id":"146","bar_names":"Amuse Resto Bar","bar_ids":"146","offer_image":"https:\/\/www.tippler.app\/manager\/uploads\/events\/Screenshot_20190208-155223__011.jpg","from_time":"21:00:00","to_time":"01:00:00"},{"event_id":"37","event_name":"Sufi Nights","event_desc":"Singers From mumbai","from_date":"2019-02-08","to_date":"2019-02-09","bar_id":"66","bar_names":"Cavalry The Lounge","bar_ids":"66","offer_image":"https:\/\/www.tippler.app\/manager\/uploads\/events\/SUFI-FEATURED.jpg","from_time":"20:00:00","to_time":"01:30:00"},{"event_id":"39","event_name":"BOLLYWOOD NIGHT","event_desc":"BHANGRA AND LIVE DHOL","from_date":"2019-02-09","to_date":"2019-02-10","bar_id":"103","bar_names":"B Desi","bar_ids":"103","offer_image":"https:\/\/www.tippler.app\/manager\/uploads\/events\/bollywood-nights-1.jpg","from_time":"21:00:00","to_time":"01:00:00"}],"error_dev":""}
I Codable class is :
struct Events: Codable {
var event_id: String
var event_name: String
var event_desc: String
var from_date: String
var to_date: String
var bar_id: String
var bar_names: String
var bar_ids: String
var offer_image: String
var from_time: String
var to_time: String
enum CodingKeys: String, CodingKey {
case event_id = "event_id"
case event_name = "event_name"
case event_desc = "event_desc"
case from_date = "from_date"
case to_date = "to_date"
case bar_id = "bar_id"
case bar_names = "bar_names"
case bar_ids = "bar_ids"
case offer_image = "offer_image"
case from_time = "from_time"
case to_time = "to_time"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(event_id, forKey: .event_id)
try container.encode(event_name, forKey: .event_name)
try? container.encode(event_desc, forKey: .event_desc)
try? container.encode(from_date, forKey: .from_date)
try container.encode(to_date, forKey: .to_date)
try container.encode(bar_id, forKey: .bar_id)
try container.encode(bar_names, forKey: .bar_names)
try container.encode(bar_ids, forKey: .bar_ids)
try container.encode(offer_image, forKey: .offer_image)
try container.encode(from_time, forKey: .from_time)
try container.encode(to_time, forKey: .to_time)
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
event_id = try values.decode(String.self, forKey: .event_id)
event_name = try values.decode(String.self, forKey: .event_name)
event_desc = try values.decode(String.self, forKey: .event_desc)
from_date = try values.decode(String.self, forKey: .from_date)
to_date = try values.decode(String.self, forKey: .to_date)
bar_id = try values.decode(String.self, forKey: .bar_id)
bar_names = try values.decode(String.self, forKey: .bar_names)
bar_ids = try values.decode(String.self, forKey: .bar_ids)
offer_image = try values.decode(String.self, forKey: .offer_image)
from_time = try values.decode(String.self, forKey: .from_time)
to_time = try values.decode(String.self, forKey: .to_time)
}
}
It is returning with the response error
keyNotFound(CodingKeys(stringValue: "event_id", intValue: nil),
Swift.DecodingError.Context(codingPath:
[CodingKeys>(stringValue: "data", intValue: nil),
_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"event_id\",
intValue: nil) (\"event_id\").", underlyingError: nil))
please suggest me the solution.
Your codable structure not matching with response as they have status , message success and errorDev key
Try this
struct Response: Codable {
let status: Int?
let message: String?
let success: Int?
let data: [Events]?
let errorDev: String?
enum CodingKeys: String, CodingKey {
case status, message, success, data
case errorDev = "error_dev"
}
}
struct Events: Codable {
let eventID, eventName, eventDesc, fromDate: String?
let toDate, barID, barNames, barIDs: String?
let offerImage: String?
let fromTime, toTime: String?
enum CodingKeys: String, CodingKey {
case eventID = "event_id"
case eventName = "event_name"
case eventDesc = "event_desc"
case fromDate = "from_date"
case toDate = "to_date"
case barID = "bar_id"
case barNames = "bar_names"
case barIDs = "bar_ids"
case offerImage = "offer_image"
case fromTime = "from_time"
case toTime = "to_time"
}
}
EDIT
I can provide you more generic solution. Suppose all API has the same response but only data key is different then
struct GeneralResponse<T:Codable>: Codable {
let status: Int?
let message: String?
let success: Int?
let data: T?
let errorDev: String?
enum CodingKeys: String, CodingKey {
case message = "msg"
case code = "code"
case data = "data"
// add more keys
}
public init(from decoder:Decoder) throws {
let contaienr = try decoder.container(keyedBy: CodingKeys.self)
message = try contaienr.decode(String.self, forKey: .message)
// Decode Other keys
do {
let object = try contaienr.decodeIfPresent(T.self, forKey: .data)
data = object
} catch {
data = nil
}
}
}
Now For every Response You Can
use GenericResponse<[Event]>
or GenericResponse<Login>
or GenericResponse<[UserList]>
Hope it is helpful
struct Events : Codabel {
let status: Int?
let message: String?
let success: Int?
let data: [Event]?
let errorDev: String?
}
struct Datum: Codable {
let eventID, eventName, eventDesc, fromDate, offerImage, fromTime, toTime : String?
let toDate, barID, barNames, barIDs: String?
}
no need to write down CodingKeys for snake cases,
While decoding, you just write
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let responseModel = try decoder.decode(Events.self, from: data) // data from response
There are couple of errors in your code as follow...
event_id is of type String in your code but is is Int type.
bar_id is of type String in your code but is is Int type.
bar_ids is of type String in your code but is is Int type.
You need to correct these variable types.
Note: Wherever in response you find a number then it will be of Int type, a number with a decimal points like 12.123 then if will be of Double type by default. Directly assuming it as string is not acceptable in swift.
Based on the requirement I got two different kinds of response from api. That is
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
another response
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
Here both json values are decoding in same model class. Kindly help me to resolve this concern.
is it is possible to set value in single variable like qty and quantity both are stored in same variable based on key param availability
Here's an approach that lets you have only one property in your code, instead of two Optionals:
Define a struct that contains all the properties you need, with the names that you'd like to use in your code. Then, define two CodingKey enums that map those properties to the two different JSON formats and implement a custom initializer:
let json1 = """
{
"shopname":"xxx",
"quantity":4,
"id":1,
"price":200.00,
}
""".data(using: .utf8)!
let json2 = """
{
"storename":"xxx",
"qty":4,
"id":1,
"amount":200.00,
}
""".data(using: .utf8)!
struct DecodingError: Error {}
struct Model: Decodable {
let storename: String
let quantity: Int
let id: Int
let price: Double
enum CodingKeys1: String, CodingKey {
case storename = "shopname"
case quantity
case id
case price
}
enum CodingKeys2: String, CodingKey {
case storename
case quantity = "qty"
case id
case price = "amount"
}
init(from decoder: Decoder) throws {
let container1 = try decoder.container(keyedBy: CodingKeys1.self)
let container2 = try decoder.container(keyedBy: CodingKeys2.self)
if let storename = try container1.decodeIfPresent(String.self, forKey: CodingKeys1.storename) {
self.storename = storename
self.quantity = try container1.decode(Int.self, forKey: CodingKeys1.quantity)
self.id = try container1.decode(Int.self, forKey: CodingKeys1.id)
self.price = try container1.decode(Double.self, forKey: CodingKeys1.price)
} else if let storename = try container2.decodeIfPresent(String.self, forKey: CodingKeys2.storename) {
self.storename = storename
self.quantity = try container2.decode(Int.self, forKey: CodingKeys2.quantity)
self.id = try container2.decode(Int.self, forKey: CodingKeys2.id)
self.price = try container2.decode(Double.self, forKey: CodingKeys2.price)
} else {
throw DecodingError()
}
}
}
do {
let j1 = try JSONDecoder().decode(Model.self, from: json1)
print(j1)
let j2 = try JSONDecoder().decode(Model.self, from: json2)
print(j2)
} catch {
print(error)
}
Handling different key names in single model
Below are two sample json(dictionaries) that have some common keys (one, two) and a few different keys (which serve the same purpose of error).
Sample json:
let error_json:[String: Any] = [
"error_code": 404, //different
"error_message": "file not found", //different
"one":1, //common
"two":2 //common
]
let failure_json:[String: Any] = [
"failure_code": 404, //different
"failure_message": "file not found", //different
"one":1, //common
"two":2 //common
]
CommonModel
struct CommonModel : Decodable {
var code: Int?
var message: String?
var one:Int //common
var two:Int? //common
private enum CodingKeys: String, CodingKey{ //common
case one, two
}
private enum Error_CodingKeys : String, CodingKey {
case code = "error_code", message = "error_message"
}
private enum Failure_CodingKeys : String, CodingKey {
case code = "failure_code", message = "failure_message"
}
init(from decoder: Decoder) throws {
let commonValues = try decoder.container(keyedBy: CodingKeys.self)
let errors = try decoder.container(keyedBy: Error_CodingKeys.self)
let failures = try decoder.container(keyedBy: Failure_CodingKeys.self)
///common
self.one = try commonValues.decodeIfPresent(Int.self, forKey: .one)!
self.two = try commonValues.decodeIfPresent(Int.self, forKey: .two)
/// different
if errors.allKeys.count > 0{
self.code = try errors.decodeIfPresent(Int.self, forKey: .code)
self.message = try errors.decodeIfPresent(String.self, forKey: .message)
}
if failures.allKeys.count > 0{
self.code = try failures.decodeIfPresent(Int.self, forKey: .code)
self.message = try failures.decodeIfPresent(String.self, forKey: .message)
}
}
}
Below extension will help you to convert your dictionary to data.
public extension Decodable {
init(from: Any) throws {
let data = try JSONSerialization.data(withJSONObject: from, options: .prettyPrinted)
let decoder = JSONDecoder()
self = try decoder.decode(Self.self, from: data)
}
}
Testing
public func Test_codeble(){
do {
let err_obj = try CommonModel(from: error_json)
print(err_obj)
let failed_obj = try CommonModel(from: failure_json)
print(failed_obj)
}catch let error {
print(error.localizedDescription)
}
}
Use like
struct modelClass : Codable {
let amount : Float?
let id : Int?
let price : Float?
let qty : Int?
let quantity : Int?
let shopname : String?
let storename : String?
enum CodingKeys: String, CodingKey {
case amount = "amount"
case id = "id"
case price = "price"
case qty = "qty"
case quantity = "quantity"
case shopname = "shopname"
case storename = "storename"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
amount = try values.decodeIfPresent(Float.self, forKey: .amount)
id = try values.decodeIfPresent(Int.self, forKey: .id)
price = try values.decodeIfPresent(Float.self, forKey: .price)
qty = try values.decodeIfPresent(Int.self, forKey: .qty)
quantity = try values.decodeIfPresent(Int.self, forKey: .quantity)
shopname = try values.decodeIfPresent(String.self, forKey: .shopname)
storename = try values.decodeIfPresent(String.self, forKey: .storename)
}
}
I am trying to use Swift 4 Decodable to parse an array that contains two different types of objects. The data looks something like this, with the included array being the one that contains both Member and ImageMedium objects:
{
"data": [{
"id": "8f7cbbac-c133-4b5e-a2ec-1f32353018fa",
"type": "post",
"title": "Test Post 1",
"owner-id": "8986563c-438c-4d77-8115-9e5de2b6e477",
"owner-type": "member"
}, {
"id": "f6b3c640-a58b-449f-93c7-f6cb7b569a9c",
"type": "post",
"title": "Test Post 2",
"owner-id": "38d845a4-db66-48b9-9c15-d857166e255e",
"owner-type": "member"
}],
"included": [{
"id": "8986563c-438c-4d77-8115-9e5de2b6e477",
"type": "member",
"first-name": "John",
"last-name": "Smith"
}, {
"id": "d7218ca1-de53-4832-bb8f-dbceb6747e98",
"type": "image-medium",
"asset-url": "https://faketest.com/fake-test-1.png",
"owner-id": "f6b3c640-a58b-449f-93c7-f6cb7b569a9c",
"owner-type": "post"
}, {
"id": "c59b8c72-13fc-44fd-8ef9-4b0f8fa486a0",
"type": "image-medium",
"asset-url": "https://faketest.com/fake-test-2.png",
"owner-id": "8f7cbbac-c133-4b5e-a2ec-1f32353018fa",
"owner-type": "post"
}, {
"id": "38d845a4-db66-48b9-9c15-d857166e255e",
"type": "member",
"first-name": "Jack",
"last-name": "Doe"
}]
}
I have tried a bunch of different ways to solve this cleanly using Decodable, but so far the only thing that has worked for me is making one struct for Included that contains all the properties of both objects as optionals, like this:
struct Root: Decodable {
let data: [Post]?
let included: [Included]?
}
struct Post: Decodable {
let id: String?
let type: String?
let title: String?
let ownerId: String?
let ownerType: String?
enum CodingKeys: String, CodingKey {
case id
case type
case title
case ownerId = "owner-id"
case ownerType = "owner-type"
}
}
struct Included: Decodable {
let id: String?
let type: String?
let assetUrl: String?
let ownerId: String?
let ownerType: String?
let firstName: String?
let lastName: String?
enum CodingKeys: String, CodingKey {
case id
case type
case assetUrl = "asset-url"
case ownerId = "owner-id"
case ownerType = "owner-type"
case firstName = "first-name"
case lastName = "last-name"
}
}
This can work by implementing a method to create Member and ImageMedium objects from the Included struct based off what its type property is, however it's obviously less than ideal. I'm hoping there is a way to accomplish this using a custom init(from decoder: Decoder), but I haven't gotten it to work yet. Any ideas?
I figured out how to decode the mixed included array into two arrays of one type each. Using two Decodable structs is easier to deal with, and more versatile, than having one struct to cover multiple types of data.
This is what my final solution looks like for anyone who's interested:
struct Root: Decodable {
let data: [Post]?
let members: [Member]
let images: [ImageMedium]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
data = try container.decode([Post].self, forKey: .data)
var includedArray = try container.nestedUnkeyedContainer(forKey: .included)
var membersArray: [Member] = []
var imagesArray: [ImageMedium] = []
while !includedArray.isAtEnd {
do {
if let member = try? includedArray.decode(Member.self) {
membersArray.append(member)
}
else if let image = try? includedArray.decode(ImageMedium.self) {
imagesArray.append(image)
}
}
}
members = membersArray
images = imagesArray
}
enum CodingKeys: String, CodingKey {
case data
case included
}
}
struct Post: Decodable {
let id: String?
let type: String?
let title: String?
let ownerId: String?
let ownerType: String?
enum CodingKeys: String, CodingKey {
case id
case type
case title
case ownerId = "owner-id"
case ownerType = "owner-type"
}
}
struct Member: Decodable {
let id: String?
let type: String?
let firstName: String?
let lastName: String?
enum CodingKeys: String, CodingKey {
case id
case type
case firstName = "first-name"
case lastName = "last-name"
}
}
struct ImageMedium: Decodable {
let id: String?
let type: String?
let assetUrl: String?
let ownerId: String?
let ownerType: String?
enum CodingKeys: String, CodingKey {
case id
case type
case assetUrl = "asset-url"
case ownerId = "owner-id"
case ownerType = "owner-type"
}
}
This is based on initial edit and it has some redundant code, but general idea should be understandable:
enum Post: Codable {
case post(id: UUID, title: String, ownerId: UUID, ownerType: PostOwner)
case member(id: UUID, firstName: String, lastName: String)
case imageMedium(id: UUID, assetURL: URL, ownerId: UUID, ownerType: ImageOwner)
enum PostType: String, Codable {
case post
case member
case imageMedium = "image-medium"
}
enum PostOwner: String, Codable {
case member
}
enum ImageOwner: String, Codable {
case post
}
enum CodingKeys: String, CodingKey {
case id
case type
case title
case assetUrl = "asset-url"
case ownerId = "owner-id"
case ownerType = "owner-type"
case firstName = "first-name"
case lastName = "last-name"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(UUID.self, forKey: .id)
let type = try container.decode(PostType.self, forKey: .type)
switch type {
case .post:
let title = try container.decode(String.self, forKey: .title)
let ownerId = try container.decode(UUID.self, forKey: .ownerId)
let ownerType = try container.decode(PostOwner.self, forKey: .ownerType)
self = .post(id: id, title: title, ownerId: ownerId, ownerType: ownerType)
case .member:
let firstName = try container.decode(String.self, forKey: .firstName)
let lastName = try container.decode(String.self, forKey: .lastName)
self = .member(id: id, firstName: firstName, lastName: lastName)
case .imageMedium:
let assetURL = try container.decode(URL.self, forKey: .assetUrl)
let ownerId = try container.decode(UUID.self, forKey: .ownerId)
let ownerType = try container.decode(ImageOwner.self, forKey: .ownerType)
self = .imageMedium(id: id, assetURL: assetURL, ownerId: ownerId, ownerType: ownerType)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .post(let id, let title, let ownerId, let ownerType):
try container.encode(PostType.post, forKey: .type)
try container.encode(id, forKey: .id)
try container.encode(title, forKey: .title)
try container.encode(ownerId, forKey: .ownerId)
try container.encode(ownerType, forKey: .ownerType)
case .member(let id, let firstName, let lastName):
try container.encode(PostType.member, forKey: .type)
try container.encode(id, forKey: .id)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
case .imageMedium(let id, let assetURL, let ownerId, let ownerType):
try container.encode(PostType.imageMedium, forKey: .type)
try container.encode(id, forKey: .id)
try container.encode(assetURL, forKey: .assetUrl)
try container.encode(ownerId, forKey: .ownerId)
try container.encode(ownerType, forKey: .ownerType)
}
}
}
let jsonDecoder = JSONDecoder()
let result = try jsonDecoder.decode([String: [Post]].self, from: yourJSONData)
print(result)
It has zero optionals for fields not used in the current post type, and UUIDs are typed as UUID, and URLs as URL instead of Strings everywhere.
ownerType are typed as PostOwner and ImageOwner for .post and .imageMedium for extra type safety.
EDIT: Ok, i checked edit of the question: In your json only ".post"s go into "data", and rest goes into "included". In mine answer Posts and Includeds are merged into one single type.
So it should be like this:
struct Post: Codable {
let id: UUID
let title: String
let ownerId: UUID
let ownerType: PostOwner
enum PostOwner: String, Codable {
case member
}
enum CodingKeys: String, CodingKey {
case id
case title
case ownerId = "owner-id"
case ownerType = "owner-type"
}
}
enum Included: Codable {
case member(id: UUID, firstName: String, lastName: String)
case imageMedium(id: UUID, assetURL: URL, ownerId: UUID, ownerType: ImageOwner)
enum PostType: String, Codable {
case member
case imageMedium = "image-medium"
}
enum ImageOwner: String, Codable {
case post
}
enum CodingKeys: String, CodingKey {
case id
case type
case title
case assetUrl = "asset-url"
case ownerId = "owner-id"
case ownerType = "owner-type"
case firstName = "first-name"
case lastName = "last-name"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let id = try container.decode(UUID.self, forKey: .id)
let type = try container.decode(PostType.self, forKey: .type)
switch type {
case .member:
let firstName = try container.decode(String.self, forKey: .firstName)
let lastName = try container.decode(String.self, forKey: .lastName)
self = .member(id: id, firstName: firstName, lastName: lastName)
case .imageMedium:
let assetURL = try container.decode(URL.self, forKey: .assetUrl)
let ownerId = try container.decode(UUID.self, forKey: .ownerId)
let ownerType = try container.decode(ImageOwner.self, forKey: .ownerType)
self = .imageMedium(id: id, assetURL: assetURL, ownerId: ownerId, ownerType: ownerType)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .member(let id, let firstName, let lastName):
try container.encode(PostType.member, forKey: .type)
try container.encode(id, forKey: .id)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
case .imageMedium(let id, let assetURL, let ownerId, let ownerType):
try container.encode(PostType.imageMedium, forKey: .type)
try container.encode(id, forKey: .id)
try container.encode(assetURL, forKey: .assetUrl)
try container.encode(ownerId, forKey: .ownerId)
try container.encode(ownerType, forKey: .ownerType)
}
}
}
Post type parsing/validating could/should be added by manually coding init(from: ).
My suggestion is to use a single type Post for all items. To distinguish the different types decode the type key as enum and decode the properties depending on the case.
That requires to declare all non-global properties as var.
struct Root : Decodable {
let data : [Post]
let included : [Post]
}
enum PostType : String, Decodable {
case member, post, imageMedium = "image-medium"
}
struct Post : Decodable {
let id: String
let type: PostType
var title: String?
var assetUrl: String?
var ownerId: String?
var ownerType: String?
var firstName: String?
var lastName: String?
enum CodingKeys: String, CodingKey {
case id, type, title
case assetUrl = "asset-url"
case ownerId = "owner-id"
case ownerType = "owner-type"
case firstName = "first-name"
case lastName = "last-name"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
type = try container.decode(PostType.self, forKey: .type)
switch type {
case .member:
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
case .post:
title = try container.decode(String.self, forKey: .title)
ownerId = try container.decode(String.self, forKey: .ownerId)
ownerType = try container.decode(String.self, forKey: .ownerType)
case .imageMedium:
assetUrl = try container.decode(String.self, forKey: .assetUrl)
ownerId = try container.decode(String.self, forKey: .ownerId)
ownerType = try container.decode(String.self, forKey: .ownerType)
}
}
}
I am wanting to parse the weather data from the Met Office for Plymouth. The structure that I have is the following:
struct WeatherRoot: Codable {
var siteRep: SiteRep
private enum CodingKeys: String, CodingKey {
case siteRep = "SiteRep"
}
}
struct SiteRep: Codable {
var dataWx: DataWx
var dataDV: DataDV
private enum CodingKeys: String, CodingKey {
case dataWx = "Wx"
case dataDV = "DV"
}
}
struct DataWx: Codable {
var param: [Param]?
private enum CodingKeys: String, CodingKey {
case param = "Param"
}
}
struct Param: Codable {
var headings: WeatherDataHeadings
private enum CodingKeys: String, CodingKey {
case headings = "Param"
}
}
struct WeatherDataHeadings: Codable {
var name: String
var unit: String
var title: String
private enum CodingKeys: String, CodingKey {
case name = "name"
case unit = "units"
case title = "$"
}
}
struct DataDV: Codable {
var dataDate: String
var type: String
var location: LocationDetails
private enum CodingKeys: String, CodingKey {
case dataDate = "dataType"
case type = "type"
case location = "Location"
}
}
struct LocationDetails: Codable {
var id: String
var latitude: String
var longitude: String
var name: String
var country: String
var continent: String
var elevation: String
var period: [Period]
private enum CodingKeys: String, CodingKey {
case id = "i"
case latitude = "lat"
case longitude = "lon"
case name
case country
case continent
case elevation
case period = "Period"
}
}
struct Period: Codable {
var type: String
var value: String
var rep: [Rep]
private enum CodingKeys: String, CodingKey {
case type = "type"
case value = "value"
case rep = "Rep"
}
}
struct Rep: Codable {
var windDirection: String
var feelsLikeTemperature: String
var windGust: String
var humidity: String
var precipitation: String
var windSpeed: String
var temperature: String
var visibility: String
var weatherType: String
var uvIndex: String
var time: String
private enum CodingKeys: String, CodingKey {
case windDirection = "D"
case feelsLikeTemperature = "F"
case windGust = "G"
case humidity = "H"
case precipitation = "Pp"
case windSpeed = "S"
case temperature = "T"
case visibility = "V"
case weatherType = "W"
case uvIndex = "U"
case time = "$"
}
}
extension WeatherRoot {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
siteRep = try values.decode(SiteRep.self, forKey: .siteRep)
}
}
extension SiteRep {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dataWx = try values.decode(DataWx.self, forKey: .dataWx)
dataDV = try values.decode(DataDV.self, forKey: .dataDV)
}
}
extension DataWx {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
param = try values.decodeIfPresent([Param].self, forKey: .param)
}
}
extension Param {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
headings = try values.decode(WeatherDataHeadings.self, forKey: .headings)
}
}
extension WeatherDataHeadings {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
unit = try values.decode(String.self, forKey: .unit)
title = try values.decode(String.self, forKey: .title)
}
}
extension DataDV {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dataDate = try values.decode(String.self, forKey: .dataDate)
type = try values.decode(String.self, forKey: .type)
location = try values.decode(LocationDetails.self, forKey: .location)
}
}
extension LocationDetails {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
id = try values.decode(String.self, forKey: .id)
latitude = try values.decode(String.self, forKey: .latitude)
longitude = try values.decode(String.self, forKey: .longitude)
name = try values.decode(String.self, forKey: .name)
country = try values.decode(String.self, forKey: .country)
continent = try values.decode(String.self, forKey: .continent)
elevation = try values.decode(String.self, forKey: .elevation)
period = try [values.decode(Period.self, forKey: .period)]
}
}
extension Period {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try values.decode(String.self, forKey: .type)
value = try values.decode(String.self, forKey: .value)
rep = try [values.decode(Rep.self, forKey: .rep)]
}
}
extension Rep {
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
windDirection = try values.decode(String.self, forKey: .windDirection)
feelsLikeTemperature = try values.decode(String.self, forKey: .feelsLikeTemperature)
windGust = try values.decode(String.self, forKey: .windGust)
humidity = try values.decode(String.self, forKey: .humidity)
precipitation = try values.decode(String.self, forKey: .precipitation)
windSpeed = try values.decode(String.self, forKey: .windSpeed)
temperature = try values.decode(String.self, forKey: .temperature)
visibility = try values.decode(String.self, forKey: .visibility)
weatherType = try values.decode(String.self, forKey: .weatherType)
uvIndex = try values.decode(String.self, forKey: .uvIndex)
time = try values.decode(String.self, forKey: .time)
}
}
The data that I am trying to parse is:
{"SiteRep":{"Wx":{"Param":[{"name":"F","units":"C","$":"Feels Like Temperature"},{"name":"G","units":"mph","$":"Wind Gust"},{"name":"H","units":"%","$":"Screen Relative Humidity"},{"name":"T","units":"C","$":"Temperature"},{"name":"V","units":"","$":"Visibility"},{"name":"D","units":"compass","$":"Wind Direction"},{"name":"S","units":"mph","$":"Wind Speed"},{"name":"U","units":"","$":"Max UV Index"},{"name":"W","units":"","$":"Weather Type"},{"name":"Pp","units":"%","$":"Precipitation Probability"}]},"DV":{"dataDate":"2018-03-16T19:00:00Z","type":"Forecast","Location":{"i":"3844","lat":"50.7366","lon":"-3.40458","name":"EXETER AIRPORT 2","country":"ENGLAND","continent":"EUROPE","elevation":"27.0","Period":[{"type":"Day","value":"2018-03-16Z","Rep":[{"D":"SE","F":"8","G":"16","H":"78","Pp":"6","S":"11","T":"11","V":"EX","W":"7","U":"1","$":"900"},{"D":"SE","F":"6","G":"11","H":"88","Pp":"6","S":"9","T":"8","V":"MO","W":"7","U":"1","$":"1080"},{"D":"E","F":"5","G":"13","H":"92","Pp":"5","S":"4","T":"7","V":"GO","W":"7","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-17Z","Rep":[{"D":"E","F":"5","G":"16","H":"90","Pp":"86","S":"7","T":"7","V":"GO","W":"12","U":"0","$":"0"},{"D":"ENE","F":"5","G":"13","H":"93","Pp":"82","S":"7","T":"7","V":"GO","W":"15","U":"0","$":"180"},{"D":"ENE","F":"2","G":"22","H":"91","Pp":"40","S":"11","T":"6","V":"MO","W":"9","U":"0","$":"360"},{"D":"NE","F":"-2","G":"29","H":"84","Pp":"44","S":"16","T":"3","V":"VG","W":"12","U":"1","$":"540"},{"D":"ENE","F":"-4","G":"29","H":"75","Pp":"17","S":"16","T":"2","V":"VG","W":"8","U":"2","$":"720"},{"D":"ENE","F":"-4","G":"29","H":"72","Pp":"20","S":"16","T":"2","V":"VG","W":"8","U":"1","$":"900"},{"D":"NE","F":"-6","G":"25","H":"73","Pp":"17","S":"13","T":"0","V":"VG","W":"8","U":"1","$":"1080"},{"D":"NE","F":"-7","G":"22","H":"81","Pp":"16","S":"11","T":"-1","V":"VG","W":"8","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-18Z","Rep":[{"D":"NE","F":"-8","G":"22","H":"86","Pp":"51","S":"11","T":"-2","V":"VG","W":"24","U":"0","$":"0"},{"D":"NE","F":"-8","G":"22","H":"87","Pp":"60","S":"11","T":"-2","V":"GO","W":"24","U":"0","$":"180"},{"D":"NE","F":"-8","G":"25","H":"88","Pp":"66","S":"13","T":"-1","V":"MO","W":"24","U":"0","$":"360"},{"D":"ENE","F":"-8","G":"29","H":"92","Pp":"84","S":"16","T":"-1","V":"PO","W":"27","U":"1","$":"540"},{"D":"ENE","F":"-5","G":"31","H":"84","Pp":"63","S":"16","T":"1","V":"MO","W":"24","U":"2","$":"720"},{"D":"ENE","F":"-5","G":"29","H":"83","Pp":"26","S":"16","T":"1","V":"MO","W":"8","U":"1","$":"900"},{"D":"ENE","F":"-6","G":"25","H":"80","Pp":"24","S":"13","T":"0","V":"GO","W":"8","U":"1","$":"1080"},{"D":"ENE","F":"-7","G":"25","H":"78","Pp":"18","S":"13","T":"-1","V":"GO","W":"8","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-19Z","Rep":[{"D":"NE","F":"-8","G":"25","H":"78","Pp":"12","S":"11","T":"-2","V":"VG","W":"7","U":"0","$":"0"},{"D":"NE","F":"-8","G":"25","H":"78","Pp":"10","S":"13","T":"-2","V":"VG","W":"7","U":"0","$":"180"},{"D":"NE","F":"-8","G":"22","H":"77","Pp":"11","S":"11","T":"-2","V":"VG","W":"7","U":"0","$":"360"},{"D":"NE","F":"-7","G":"27","H":"69","Pp":"3","S":"13","T":"0","V":"VG","W":"3","U":"1","$":"540"},{"D":"ENE","F":"-3","G":"29","H":"57","Pp":"2","S":"16","T":"3","V":"VG","W":"3","U":"3","$":"720"},{"D":"NE","F":"0","G":"29","H":"49","Pp":"1","S":"16","T":"5","V":"VG","W":"1","U":"1","$":"900"},{"D":"NE","F":"-1","G":"20","H":"59","Pp":"1","S":"11","T":"4","V":"VG","W":"1","U":"1","$":"1080"},{"D":"NNE","F":"-4","G":"22","H":"73","Pp":"1","S":"11","T":"2","V":"VG","W":"0","U":"0","$":"1260"}]},{"type":"Day","value":"2018-03-20Z","Rep":[{"D":"NNE","F":"-4","G":"18","H":"81","Pp":"5","S":"9","T":"1","V":"VG","W":"7","U":"0","$":"0"},{"D":"N","F":"-3","G":"18","H":"86","Pp":"5","S":"9","T":"2","V":"GO","W":"7","U":"0","$":"180"},{"D":"N","F":"-3","G":"18","H":"88","Pp":"5","S":"9","T":"2","V":"GO","W":"7","U":"0","$":"360"},{"D":"N","F":"0","G":"20","H":"78","Pp":"5","S":"9","T":"4","V":"VG","W":"7","U":"1","$":"540"},{"D":"NNE","F":"3","G":"22","H":"68","Pp":"1","S":"11","T":"7","V":"VG","W":"3","U":"3","$":"720"},{"D":"N","F":"5","G":"22","H":"62","Pp":"5","S":"11","T":"8","V":"VG","W":"7","U":"1","$":"900"},{"D":"NNW","F":"3","G":"13","H":"72","Pp":"5","S":"7","T":"6","V":"VG","W":"7","U":"1","$":"1080"},{"D":"NNW","F":"1","G":"11","H":"82","Pp":"5","S":"4","T":"4","V":"GO","W":"7","U":"0","$":"1260"}]}]}}}}
However when I decode the JSON to the structure I get the error:
Error Serializing Json: keyNotFound(Clothing_Prediction_iOS_Application.Param.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).headings, Swift.DecodingError.Context(codingPath: [Clothing_Prediction_iOS_Application.WeatherRoot.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).siteRep, Clothing_Prediction_iOS_Application.SiteRep.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).dataWx, Clothing_Prediction_iOS_Application.DataWx.(CodingKeys in _99F6E563F35EF627A75B06F8891FEB0F).param, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0))], debugDescription: "No value associated with key headings (\"Param\").", underlyingError: nil))
I have tried looking at optionals however I can not see why I am getting this error.
It looks like you're duplicating Param in your data structures. DataWx has case param = "Param", which is OK. But then Param has case headings = "Param", which is not in your JSON. So your JSON starts off with
{"SiteRep":{"Wx":{"Param":[{"name"...
But your data structures expect something like
{"SiteRep":{"Wx":{"Param":{"Param":...
The error isn't very clear but this seems to be what it's trying to tell you.
If you parse through the error message (messy, I know), you will find that it's telling you 2 things:
Key not found: headings
At key path: siteRep/dataWx/param[0]
The key path is the name of the properties in your data model. If you convert them back to how you mapped it in your various CodingKeyss, you will get the JSON path: SiteRep/Wx/Param[0]. There's no headings to found there.
How to fix it:
Remove your current Param struct
Rename WeatherDataHeadings to Param
You also have another mapping error in DataDV:
struct DataDV: Codable {
// Better parse date as Date, and not as String. This
// requires set the dateDecodingStrategy. See below.
var dataDate: Date
var type: String
var location: LocationDetails
private enum CodingKeys: String, CodingKey {
case dataDate = "dataDate" // not "dataType"
case type = "type"
case location = "Location"
}
}
And you wrote way more code than needed. You can delete all these extension. The compiler can synthesize them for you.
Here's how you decode it:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let weatherRoot = try decoder.decode(WeatherRoot.self, from: jsonData)