Swift Json Decoder, unable to decode nested objects array - json

I have the following json which contains a user, and the positions they hold in office. I can decode the user disregarding the positions just fine, but when I try to decode the array of positions it fails. I have try making seperate enums for the position and using nested containers but nothing is working.
The JSON
[
{
"firstName": "jon",
"id": "CE30BCF5-1335-4767-BD20-53F4EDE950CD",
"title": "citizen",
"username": "jon1",
"positions": [
{
"id": "3332BC0E-0DA5-4B90-A836-4CF91B872B05",
"name": "mayor",
"jurisdiction": {
"id": "A5986304-A301-431E-92A2-5B53BA58FC89"
}
},
{
"name": "governor",
"id": "199761E2-BCC2-4EC9-93CE-C4E7F4FB9277",
"jurisdiction": {
"id": "A5986304-A301-431E-92A2-5B53BA58FC89"
}
}
],
"imageURLString": "jon1image",
"civicRating": 5,
"lastName": "samson"
}
]
User Model
import Foundation
struct New_User: Codable {
var id: UUID?
var username: String
var firstName: String
var lastName: String
var title: String
var imageURLString: String?
var civicRating: Double
var positions: [New_Position] = []
private enum UserKeys: String, CodingKey {
case id
case username
case firstName
case lastName
case imageURLString
case civicRating
case title
case positions
}
}
extension New_User {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: UserKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.username = try container.decode(String.self, forKey: .username)
self.title = try container.decode(String.self, forKey: .title)
self.firstName = try container.decode(String.self, forKey: .firstName)
self.lastName = try container.decode(String.self, forKey: .lastName)
self.imageURLString = try container.decode(String?.self, forKey: .imageURLString)
self.civicRating = try container.decode(Double.self, forKey: .civicRating)
self.positions = try container.decode([New_Position].self, forKey: .positions)
}
}
Position Model
import Foundation
struct New_Position: Codable {
var id: UUID?
var jurisdiction: New_Jurisdiction
var name: String
private enum PositionKeys: String, CodingKey {
case id
case jurisdiction
case name
}
}
extension New_Position {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: PositionKeys.self)
self.name = try container.decode(String.self, forKey: .name)
self.jurisdiction = try container.decode(New_Jurisdiction.self, forKey: .jurisdiction)
print("decoded")
}
}
Jurisdiction Model
import Foundation
struct New_Jurisdiction: Codable {
var id: UUID?
var name: String
var scope: String
private enum JurisdictionKeys: String, CodingKey {
case id
case name
case scope
}
}
extension New_Jurisdiction {
init(from decoder:Decoder) throws {
let container = try decoder.container(keyedBy: JurisdictionKeys.self)
self.id = try container.decode(UUID.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.scope = try container.decode(String.self, forKey: .scope)
print("decoded")
}
}

Related

Why is this JSON decode failing?

I have done JSON decoding before, but for some reason I can't figure this out. In the playground, it simply crashes with no explanation so I decided to put it into a single view project to trace the problem.
The initializer isn't being called at all. Tried decoding with the super class and got the same thing. Would love another pair of eyes on this.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let json = """
{"key": 5, "accountKey": "checking", "amount": 100000, "type": "deposit", "date": "2019-03-05T15:29:32Z", "locationKey", "Payroll", isReconciled: false}
"""
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let transaction = try? decoder.decode(BasicTransaction.self, from: jsonData)
print(transaction?.locationKey)
}
}
Here are the supporting definitions:
enum TransactionType: CaseIterable {
case purchase
case deposit
case ccPayment
}
extension TransactionType: RawRepresentable {
typealias RawValue = String
init(rawValue: Self.RawValue) {
switch rawValue {
case "deposit": self = .deposit
case "payment": self = .ccPayment
default: self = .purchase
}
}
var rawValue: String {
switch self {
case .purchase: return "purchase"
case .deposit: return "deposit"
case .ccPayment: return "payment"
}
}
}
class MoneyTransaction: Decodable {
var key = UUID().uuidString
var type = TransactionType.purchase
var accountKey = ""
var ccAccountKey: String? = nil
var recurringTransactionKey: String? = nil
var isRecurring: Bool { !(recurringTransactionKey ?? "").isEmpty }
var locationKey: String? = nil
var addressKey: String? = nil
var categoryKey: String? = nil
var note: String? = nil
var amount: Double = 0
private enum MoneyTransactionKey: String, CodingKey {
case key, type, accountKey, ccAccountKey, recurringTransactionKey, locationKey, addressKey, categoryKey, note, amount, isNew
}
required init(from decoder: Decoder) throws {
print("initializing MoneyTransaction")
let container = try decoder.container(keyedBy: MoneyTransactionKey.self)
key = try container.decode(String.self, forKey: .key)
accountKey = try container.decode(String.self, forKey: .accountKey)
let rawAmount = try container.decode(Int.self, forKey: .amount)
amount = Double(rawAmount / 100)
type = try TransactionType(rawValue: container.decode(String.self, forKey: .type))
recurringTransactionKey = try container.decodeIfPresent(String.self, forKey: .recurringTransactionKey)
locationKey = try container.decodeIfPresent(String.self, forKey: .locationKey)
categoryKey = try container.decodeIfPresent(String.self, forKey: .categoryKey)
note = try container.decodeIfPresent(String.self, forKey: .note)
ccAccountKey = try container.decodeIfPresent(String.self, forKey: .ccAccountKey)
addressKey = try container.decodeIfPresent(String.self, forKey: .addressKey)
}
}
class BasicTransaction: MoneyTransaction {
var date = Date()
var checkNumber: Int? = nil
var isReconciled = false
private enum BasicTransactionKey: String, CodingKey {
case date, checkNumber, isReconciled
}
required init(from decoder: Decoder) throws {
print("initializing BasicTransaction")
try super.init(from: decoder)
let container = try decoder.container(keyedBy: BasicTransactionKey.self)
date = try container.decode(Date.self, forKey: .date)
checkNumber = try container.decodeIfPresent(Int.self, forKey: .checkNumber)
isReconciled = try container.decode(Bool.self, forKey: .isReconciled)
}
}
The json is incorrect it's like
{
"key": 5,
"accountKey": "checking",
"amount": 100000,
"type": "deposit",
"date": "2019-03-05T15:29:32Z",
"locationKey", << key with no value
"Payroll", << key with no value
isReconciled: false << key with no ""
}
It would be like
{
"key": 5,
"accountKey": "checking",
"amount": 100000,
"type": "deposit",
"date": "2019-03-05T15:29:32Z",
"locationKey":"",
"Payroll":"",
"isReconciled": false
}
then BasicTransaction
class BasicTransaction: Codable {
let key: Int
let accountKey: String
let amount: Int
let type: String
let date: Date
let locationKey, payroll: String
let isReconciled: Bool
enum CodingKeys: String, CodingKey {
case key, accountKey, amount, type, date, locationKey
case payroll = "Payroll"
case isReconciled
}
init(key: Int, accountKey: String, amount: Int, type: String, date: Date, locationKey: String, payroll: String, isReconciled: Bool) {
self.key = key
self.accountKey = accountKey
self.amount = amount
self.type = type
self.date = date
self.locationKey = locationKey
self.payroll = payroll
self.isReconciled = isReconciled
}
}

ignore null object in array when parse with Codable swift

i'm parsing this API with swift Codable
"total": 7,
"searchResult": [
null,
{
"name": "joe"
"family": "adam"
},
null,
{
"name": "martin"
"family": "lavrix"
},
{
"name": "sarah"
"family": "mia"
},
null,
{
"name": "ali"
"family": "abraham"
}
]
with this PaginationModel:
class PaginationModel<T: Codable>: Codable {
var total: Int?
var data: T?
enum CodingKeys: String, CodingKey {
case total
case data = "searchResult"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = try container.decodeIfPresent(Int.self, forKey: .total)
self.data = try container.decodeIfPresent(T.self, forKey: .data)
}
}
and User Model:
struct User: Codable {
var name: String?
var family: String?
}
i call jsonDecoder like this to parse API json:
let responseObject = try JSONDecoder().decode(PaginationModel<[User?]>.self, from: json)
now my problem is null in searchResult Array. it parsed correctly and when i access to data in paginationModel i found null in array.
how can i ignore all null when parsing API, and result will be an array without any null
In the first place, I would advise to always consider PaginationModel to be composed from arrays. You don't have to pass [User] as the generic type, you can just pass User. Then the parser can use the knowledge that it parses arrays and handle null automatically:
class PaginationModel<T: Codable>: Codable {
var total: Int?
var data: [T]?
enum CodingKeys: String, CodingKey {
case total
case data = "searchResult"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = try container.decodeIfPresent(Int.self, forKey: .total)
self.data = (try container.decodeIfPresent([T?].self, forKey: .data))?.compactMap { $0 }
}
}
You might want to remove optionals here and use some default values instead:
class PaginationModel<T: Codable>: Codable {
var total: Int = 0
var data: [T] = []
enum CodingKeys: String, CodingKey {
case total
case data = "searchResult"
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = (try container.decodeIfPresent(Int.self, forKey: .total)) ?? 0
self.data = ((try container.decodeIfPresent([T?].self, forKey: .data)) ?? []).compactMap { $0 }
}
}
Simple solution, filter data after decoding
let responseObject = try JSONDecoder().decode(PaginationModel<[User?]>.self, from: data)
responseObject.data = responseObject.data?.filter{$0 != nil}
You may add an array type check within decode :
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.total = try container.decodeIfPresent(Int.self, forKey: .total)
self.data = try container.decodeIfPresent(T.self, forKey: .data)
//add the following:
if let array = self.data as? Array<Any?> {
self.data = ( array.compactMap{$0} as? T)
}
}
Note, you can just define the decodable variable that may be null/nil as [Float?] (or whatever type), with the optional '?' inside the array brackets.

Decoding two different JSON responses in one model class using Codable

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

How to handle Empty array in codable decode process

It works well when "address" array is not empty. But it fails when "address" array is empty. Any help will be highly appreciated. I have struct for Address object. Basically "address" is array of object of type "Address" but when address is empty it fails.
{
"success": "1",
"message": "You have succesfully verified your mobile no",
"details": {
"customer_id": 825,
"is_delivery_available": "0",
"is_registration_complete": "0",
"is_customer_verified": "0",
"customer_status": "0",
"cart_count": "0",
"name_type": "mr",
"firstname": "",
"lastname": "",
"full_name": "",
"pincode": "",
"profile_pic": "",
"mobile": "8583846677",
"email": "",
"address": [
],
"referral_code": ""
}
}
Above is the JSON I am trying to decode using Codable in Swift4.
import Foundation
struct Signup: Codable {
var success:String?
var message:String?
var details:Details?
private enum CodingKeys: String, CodingKey { case success, message, details }
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
success = try values.decode(String.self, forKey: .success)
message = try values.decode(String.self, forKey: .message)
if let _details = try? values.decode(Details.self, forKey: .details) {
details = _details
}
}
}
struct Details: Codable {
var address:[Address]?
var cart_count:String?
var customer_id:String?
var customer_status:String?
var email:String?
var firstname:String?
var full_name:String?
var is_customer_verified:String?
var is_delivery_available:String?
var is_registration_complete:String?
var lastname:String?
var mobile:String?
var name_type:String?
var pincode:String?
var profile_pic:String?
var referral_code:String?
private enum CodingKeys: String, CodingKey { case address, cart_count, customer_id, customer_status, email, firstname, full_name, is_customer_verified, is_delivery_available, is_registration_complete, lastname, mobile, name_type, pincode, profile_pic, referral_code }
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
****-->>> Issue is in this line. How to handle empty array while decoding. <<<--****
address = try? values.decode([Address].self, forKey: .address)
cart_count = try values.decode(String.self, forKey: .cart_count)
customer_id = try values.decode(String.self, forKey: .customer_id)
customer_status = try values.decode(String.self, forKey: .customer_status)
email = try values.decode(String.self, forKey: .email)
firstname = try values.decode(String.self, forKey: .firstname)
full_name = try values.decode(String.self, forKey: .full_name)
is_customer_verified = try values.decode(String.self, forKey: .is_customer_verified)
is_delivery_available = try values.decode(String.self, forKey: .is_delivery_available)
is_registration_complete = try values.decode(String.self, forKey: .is_registration_complete)
lastname = try values.decode(String.self, forKey: .lastname)
mobile = try values.decode(String.self, forKey: .mobile)
name_type = try values.decode(String.self, forKey: .name_type)
pincode = try values.decode(String.self, forKey: .pincode)
profile_pic = try values.decode(String.self, forKey: .profile_pic)
referral_code = try values.decode(String.self, forKey: .referral_code)
}
}
struct Address: Codable {
var address_default:String?
var address_id:String?
var address_type:String?
var city:String?
var customer_id:String?
var flat_or_house_or_office_no:String?
var full_address:String?
var full_name:String?
var landmark:String?
var lat:String?
var lng:String?
var mobile:String?
var name_type:String?
var pincode:String?
var street_or_society_or_office_name:String?
}
I am stuck into this. How to handle empty array while decoding the json data in Swift4 Codable.
I came across this problem and I did as below to solve this one.
if let _address = try? values.decode([Address].self, forKey: .address) {
address = _address
} else {
address = []
}
You can give it a try.
2022.06.08 updated.
according to #Amin Benarieb
let _address = try? values.decode([Address].self, forKey: .address) ?? []
The real issue is that customer_id in Details is Int not String. Decoding an empty array is supposed to work.
Your code is too complicated. Enjoy the magic of Codable and omit all CodingKeys and initializers and declare all struct members as non-optional and as constants
struct Signup: Decodable {
let success : String
let message : String
let details : Details
}
struct Details : Decodable {
let address : [Address]
let cart_count : String
let customer_id : Int
let customer_status, email, firstname, full_name : String
let is_customer_verified, is_delivery_available, is_registration_complete : String
let lastname, mobile, name_type, pincode, profile_pic, referral_code : String
}
struct Address: Decodable {
let address_default, address_id, address_type, city, customer_id : String
let flat_or_house_or_office_no, full_address, full_name : String
let landmark, lat, lng, mobile, name_type, pincode, street_or_society_or_office_name : String
}
You can even name the members camelCased
let cartCount : String
let customerId : Int
...
let isRegistrationComplete : String
and pass the convertFromSnakeCase key decoding strategy
decoder.keyDecodingStrategy = .convertFromSnakeCase
Still no extra code!

Parsing Met Office JSON

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)