Parsing Met Office JSON - 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)
Related
Codable Parsing issue
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.
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) } }
Expected to decode Dictionary<String, Any> but found an array instead with Nested Containers
So, I am trying to parse this JSON using the Codable protocols: https://randomuser.me/api/?results=100 That are basically 100 random users. Here's my User class initializer from decoder, that I need because the User is an entity in a Core Data Model: required convenience public init(from decoder: Decoder) throws { let managedObjectContext = CoreDataStack.sharedInstance.persistentContainer.viewContext guard let entity = NSEntityDescription.entity(forEntityName: "User", in: managedObjectContext) else { fatalError("Failed to decode User") } self.init(entity: entity, insertInto: managedObjectContext) let container = try decoder.container(keyedBy: CodingKeys.self) let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results) let name = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .name) firstName = try name.decode(String.self, forKey: .firstName) lastName = try name.decode(String.self, forKey: .lastName) let location = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .location) let street = try location.decode(String.self, forKey: .street) let city = try location.decode(String.self, forKey: .city) let postcode = try location.decode(String.self, forKey: .postcode) address = street + ", " + city + ", " + postcode email = try results.decode(String.self, forKey: .email) let pictures = try results.nestedContainer(keyedBy: CodingKeys.self, forKey: .pictures) pictureURL = try pictures.decode(String.self, forKey: .pictureURL) } This is the defective line: let results = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .results) Here's the complete error: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)) I think it's due to the structure of the JSON, that is an array of 100 elements under the key "results", and I think the problem could be in doing them all together. How should I handle this?
The error is clear: The value for results is an array and nestedContainers expects a dictionary. To decode the user array you need an umbrella struct outside of the Core Data classes. struct Root : Decodable { let results: [User] } While decoding Root the init method in User is called for each array item. To use nestedContainers you have to separate the CodingKeys. This is the init method without the Core Data stuff. postcode can be String or Int private enum CodingKeys: String, CodingKey { case name, location, email, picture } private enum NameCodingKeys: String, CodingKey { case first, last } private enum LocationCodingKeys: String, CodingKey { case street, city, postcode } private enum PictureCodingKeys: String, CodingKey { case large, medium, thumbnail } required convenience public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let nameContainer = try container.nestedContainer(keyedBy: NameCodingKeys.self, forKey: .name) let firstName = try nameContainer.decode(String.self, forKey: .first) let lastName = try nameContainer.decode(String.self, forKey: .last) let locationContainer = try container.nestedContainer(keyedBy: LocationCodingKeys.self, forKey: .location) let street = try locationContainer.decode(String.self, forKey: .street) let city = try locationContainer.decode(String.self, forKey: .city) let postcode : String do { let postcodeInt = try locationContainer.decode(Int.self, forKey: .postcode) postcode = String(postcodeInt) } catch DecodingError.typeMismatch { postcode = try locationContainer.decode(String.self, forKey: .postcode) } let address = street + ", " + city + ", " + postcode let email = try container.decode(String.self, forKey: .email) let pictureContainer = try container.nestedContainer(keyedBy: PictureCodingKeys.self, forKey: .picture) let pictureURL = try pictureContainer.decode(URL.self, forKey: .large) }
This is a very simplified version but it handles your Json data correctly struct Result : Codable { let results: [User] } struct User: Codable { let gender: String let name: Name } struct Name: Codable { let title: String let first: String let last: String } let decoder = JSONDecoder() let data = jsonString.data(using: .utf8) //Replace with url download do { let json = try decoder.decode(Result.self, from: data!) } catch { print(error) }
Handling JSON Array Containing Multiple Types - Swift 4 Decodable
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) } } }
Decoding two different JSON responses with one struct using Codable
I am getting data from two different endpoints. One endpoints returns an order like this: { "price":"123.49", "quantity":"4", "id":"fkuw-4834-njk3-4444", "highPrice":"200", "lowPrice":"100" } and the other endpoint returns the order like this: { "p":"123.49", //price "q":"4", //quantity "i":"fkuw-4834-njk3-4444" //id } I want to use the same Codable struct to decode both JSON responses. How would I do that? Is it possible to do that using one struct or would I have to create a second struct? Here is what the struct looks like right now for the first JSON return: struct SimpleOrder:Codable{ var orderPrice:String var orderQuantity:String var id:String var highPrice:String private enum CodingKeys: String, CodingKey { case orderPrice = "price" case orderQuantity = "quantity" case id case highPrice } }
You can do that but you have to declare all properties as optional and write a custom initializer struct SimpleOrder : Decodable { var orderPrice : String? var orderQuantity : String? var id : String? var highPrice : String? private enum CodingKeys: String, CodingKey { case orderPrice = "price" case orderQuantity = "quantity" case id case highPrice case i, q, p } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) orderPrice = try container.decodeIfPresent(String.self, forKey: .orderPrice) orderPrice = try container.decodeIfPresent(String.self, forKey: .p) orderQuantity = try container.decodeIfPresent(String.self, forKey: .orderQuantity) orderQuantity = try container.decodeIfPresent(String.self, forKey: .q) id = try container.decodeIfPresent(String.self, forKey: .id) id = try container.decodeIfPresent(String.self, forKey: .i) highPrice = try container.decodeIfPresent(String.self, forKey: .highPrice) } } Alternatively use two different key sets, check the occurrence of one of the keys and choose the appropriate key set. The benefit is that price, quantity and id can be declared as non-optional struct SimpleOrder : Decodable { var orderPrice : String var orderQuantity : String var id : String var highPrice : String? private enum CodingKeys: String, CodingKey { case orderPrice = "price" case orderQuantity = "quantity" case id case highPrice } private enum AbbrevKeys: String, CodingKey { case i, q, p } init(from decoder: Decoder) throws { let cContainer = try decoder.container(keyedBy: CodingKeys.self) if let price = try cContainer.decodeIfPresent(String.self, forKey: .orderPrice) { orderPrice = price orderQuantity = try cContainer.decode(String.self, forKey: .orderQuantity) id = try cContainer.decode(String.self, forKey: .id) highPrice = try cContainer.decode(String.self, forKey: .highPrice) } else { let aContainer = try decoder.container(keyedBy: AbbrevKeys.self) orderPrice = try aContainer.decode(String.self, forKey: .p) orderQuantity = try aContainer.decode(String.self, forKey: .q) id = try aContainer.decode(String.self, forKey: .i) } } }
There is no need to create a custom initializer for your Codable structure, all you need is to make the properties optional. What I recommend is to create a read only computed property that would return the prices and quantities using a nil coalescing operator so it will always return one or another: struct Order: Codable { let price: String? let quantity: String? let id: String? let highPrice: String? let lowPrice: String? let p: String? let q: String? let i: String? } extension Order { var orderPrice: Double { return Double(price ?? p ?? "0") ?? 0 } var orderQuantity: Int { return Int(quantity ?? q ?? "0") ?? 0 } var userID: String { return id ?? i ?? "" } } Testing: let ep1 = Data(""" { "price":"123.49", "quantity":"4", "id":"fkuw-4834-njk3-4444", "highPrice":"200", "lowPrice":"100" } """.utf8) let ep2 = Data(""" { "p":"123.49", "q":"4", "i":"fkuw-4834-njk3-4444" } """.utf8) do { let order = try JSONDecoder().decode(Order.self, from: ep2) print(order.orderPrice) // "123.49\n" print(order.orderQuantity) // "4\n" print(order.userID) // "fkuw-4834-njk3-4444\n" } catch { print(error) }