Deserialization JSON swift 4.2 - json

I try to deserialize my JSON by using Decodable protocol, also i use enum with CodingKey, but it doesn't work. I need only nested array (start with "indicator"), and only few fields (all of them in struct). I tried a lot of different options, but unfortunately..
P.S. Also i tried to do it without CodingKey. Anyway response was: "Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "country", intValue: nil)" Ofc i read this, maybe array is a reason(i mean this strange intValue)?
JSON
[
{
"page":1,
"pages":2,
"per_page":50,
"total":59,
"sourceid":"2",
"lastupdated":"2019-03-21"
},
[
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2018",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
{
"indicator":{
"id":"IP.PAT.RESD",
"value":"Patent applications, residents"
},
"country":{
"id":"SS",
"value":"South Sudan"
},
"countryiso3code":"SSD",
"date":"2017",
"value":null,
"unit":"",
"obs_status":"",
"decimal":0
},
...
]
]
My code
struct CountryObject: Decodable{
var country: CountryInfo
var date: Int
var value: Int?
private enum RawValues: String, Decodable{
case date = "date"
case vallue = "value"
}
}
struct CountryInfo: Decodable{//Country names
var id: String?
var value: String?
private enum RawValues: String, Decodable{
case id = "id"
case value = "value"
}
}//
let urlString = "https://api.worldbank.org/v2/country/SS/indicator/IP.PAT.RESD?format=json"
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) {(data,response,error) in
guard let data = data else {return}
guard error == nil else {return}
do{
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let countryObject = try! decoder.decode([CountryObject].self, from: data)
print(countryObject)
}catch let error{
print(error)
}
}.resume()

Create a root struct and decode the array with unkeyedContainer
struct Root : Decodable {
let info : Info
let countryObjects : [CountryObject]
init(from decoder: Decoder) throws {
var arrayContrainer = try decoder.unkeyedContainer()
info = try arrayContrainer.decode(Info.self)
countryObject = try arrayContrainer.decode([CountryObject].self)
}
}
struct Info : Decodable {
let page, pages, perPage: Int
let lastupdated: String
}
struct CountryObject : Decodable {
let country: CountryInfo
let date: String
let value: Int?
}
struct CountryInfo : Decodable { //Country names
let id: String
let value: String
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(Root.self, from: data)
let countryObjects = root.countryObjects
print(countryObjects)
} catch { print(error) }
(De)serializing the JSON twice is unnecessarily expensive.

Related

Swift Data Model from JSON Response

I am running into an issue building the correct data model for the following JSON response.
{
"resources": [
{
"courseid": 4803,
"color": "Blue",
"teeboxtype": "Championship",
"slope": 121,
"rating": 71.4
},
{
"courseid": 4803,
"color": "White",
"teeboxtype": "Men's",
"slope": 120,
"rating": 69.6
},
{
"courseid": 4803,
"color": "Red",
"teeboxtype": "Women's",
"slope": 118,
"rating": 71.2
}
]
}
Here is the current model. No matter what I do I can't seem to get the model populated. Here is also my URL session retrieving the data. I am new to Swift and SwiftUI so please be gentle. I am getting data back however I am missing something.
import Foundation
struct RatingsResources: Codable {
let golfcourserating : [GolfCourseRating]?
}
struct GolfCourseRating: Codable {
let id: UUID = UUID()
let courseID: Int?
let teeColor: String?
let teeboxtype: String?
let teeslope: Double?
let teerating: Double?
enum CodingKeysRatings: String, CodingKey {
case courseID = "courseid"
case teeColor = "color"
case teeboxtype
case teeslope = "slope"
case teerating = "rating"
}
}
func getCoureRating(courseID: String?) {
let semaphore = DispatchSemaphore (value: 0)
print("GETTING COURSE TEE RATINGS..........")
let urlString: String = "https://api.golfbert.com/v1/courses/\(courseID ?? "4800")/teeboxes"
print ("API STRING: \(urlString) ")
let url = URLComponents(string: urlString)!
let request = URLRequest(url: url.url!).signed
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let response = try? JSONDecoder().decode([RatingsResources].self, from: data) {
DispatchQueue.main.async {
self.ratingresources = response
}
return
}
print("*******Data String***********")
print(String(data: data, encoding: .utf8)!)
print("***************************")
let ratingsData: RatingsResources = try! decoder.decode(RatingsResources.self, from: data)
print("Resources count \(ratingsData.golfcourserating?.count)")
semaphore.signal()
}
task.resume()
semaphore.wait()
} //: END OF GET COURSE SCORECARD
First of all, never use try? while decoding your JSON. This will hide all errors from you. Use try and an appropriate do/catch block. In the catch block at least print the error.
Looking at your model there seem to be three issues here.
You donĀ“t have an array of RatingsResources in your array. It is just a single instance.
let response = try JSONDecoder().decode(RatingsResources.self, from: data)
RatingsResources is not implemented correct.
let golfcourserating : [GolfCourseRating]?
should be:
let resources: [GolfCourseRating]?
Your coding keys are implemented wrong instead of:
enum CodingKeysRatings: String, CodingKey {
it should read:
enum CodingKeys: String, CodingKey {
You should add enum CodingKey with resources at struct RatingsResources
And decode:
if let response = try? JSONDecoder().decode(RatingsResources.self, from: data) {
// Your response handler
}

How to read local JSON file and output JSON in swift?

import Foundation
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> JSON
{
var json: JSON
if let path = Bundle.main.path(forResource: fileName, ofType: "json") {
do {
let fileUrl = URL(fileURLWithPath: path)
let data = try Data(contentsOf: fileUrl, options: .mappedIfSafe)
json = try? JSONSerialization.jsonObject(with: data)
} catch {
print("Something goes wrong when reading local json file.")
}
}
return json
}
}
I try to read the local json file and output json. But the line json = try? JSONSerialization.jsonObject(with: data) gives an error saying Cannot assign value of type 'Any?' to type 'JSON'.
My json data looks like
{
"leagues":
[
{ "name": "Hockey",
"image": "hockey",
"games":
[
{
"game_state": "Final",
"game_time": 1456662600,
"home_team_city": "Alberta",
"home_team_name": "Pigs",
"home_team_score": 1,
"home_team_logo": "pig",
"visit_team_city": "Montreal",
"visit_team_name": "Fishes",
"visit_team_score": 4,
"visit_team_logo": "fish"
}
]
}
]
}
When I change the output type to be Any? I print the output and it seems missing some elements.
{
leagues = (
{
games = (
{
"game_state" = Final;
"game_time" = 1456662600;
...
How can I fix it?
Check the solution below, I used Codable for the JSON decoding.
import Foundation
struct Sports: Codable {
let leagues: [League]
}
struct League: Codable {
let name, image: String
let games: [Game]
}
struct Game: Codable {
let gameState: String
let gameTime: Int
let homeTeamCity, homeTeamName: String
let homeTeamScore: Int
let homeTeamLogo, visitTeamCity, visitTeamName: String
let visitTeamScore: Int
let visitTeamLogo: String
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
class ReadLocalJSON {
static func readJSONFromFile(fileName: String) -> Sports?
{
let path = Bundle.main.path(forResource: fileName, ofType: "json")
let url = URL(fileURLWithPath: path!)
let sportsData = try? Data(contentsOf: url)
guard
let data = sportsData
else { return nil }
do {
let result = try JSONDecoder().decode(Sports.self, from: data)
print(result)
return result
} catch let error {
print("Failed to Decode Object", error)
return nil
}
}
}
ReadLocalJSON.readJSONFromFile(fileName: "test")
Step 1:- first make a modal class in your project
struct Welcome: Codable {
let leagues: [League]?
}
// MARK: - League
struct League: Codable {
let name, image: String?
let games: [Game]?
}
// MARK: - Game
struct Game: Codable {
let gameState: String?
let gameTime: Int?
let homeTeamCity, homeTeamName: String?
let homeTeamScore: Int?
let homeTeamLogo, visitTeamCity, visitTeamName: String?
let visitTeamScore: Int?
let visitTeamLogo: String?
enum CodingKeys: String, CodingKey {
case gameState = "game_state"
case gameTime = "game_time"
case homeTeamCity = "home_team_city"
case homeTeamName = "home_team_name"
case homeTeamScore = "home_team_score"
case homeTeamLogo = "home_team_logo"
case visitTeamCity = "visit_team_city"
case visitTeamName = "visit_team_name"
case visitTeamScore = "visit_team_score"
case visitTeamLogo = "visit_team_logo"
}
}
Step 2 : - After getting response write this line,
let decoder = JSONDecoder()
let obj = try! decoder.decode(Welcome.self, from: jsonData!)
IF you have still problem let me know

How to use decodable protocol with custom type values in Swift?

I have 2 types of response depending on my reuest: First one:
{
"status": "success"
"data": {
"user_id": 2,
"user_name": "John"
}
}
And second one is:
{
"status": "error",
"data": [],
}
I am using struct like that:
struct ValyutaListData:Decodable {
let status: String?
let data: [String]?
}
But if response is first type response, then an error occured. Because In first Type response data is not array. It is Json object. Then i use structure like that:
struct ValyutaListData:Decodable {
let status: String?
let data: Persondata?
}
struct Persondata: Decodable{
let user_id: Int?
let user_name: String?
}
If response is second type response, the error will be occured. What kind of of structure should use for dynamic type JSONs? Thanks.
One reasonable solution is an enum with associated type(s)
struct User : Decodable {
let userId: Int
let userName: String
}
enum Result : Decodable {
case success(User), failure
enum CodingKeys: String, CodingKey { case status, data }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let status = try container.decode(String.self, forKey: .status)
if status == "success" {
let userData = try container.decode(User.self, forKey: .data)
self = .success(userData)
} else {
self = .failure
}
}
}
And use it
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result = try decoder.decode(Result.self, from: data)
switch result {
case .success(let user): print(user)
case .failure: print("An error occurred")
}
} catch { print(error) }

Decoding unnamed JSON array swift 4 with structure

I have been trying to decode an array for an app where I have to decode JSON.
I just can't decode it with my actual structure and others which I tried before.
Here are my latest structures:
struct peticion: Decodable{
let datos_conexion: datos_conexion
let estado_lanzamiento: estado_lanzamiento
let usuario: usuario
}
struct datos_conexion: Decodable {
let conexion: datosConexion
}
struct datosConexion: Decodable{
let datos_conexion: String
}
struct estado_lanzamiento: Decodable{
let tiempo_restante: String
let etapa_actual: String
}
struct usuario: Decodable {
let Id: Int
let Nombre: String
let Password: String
let Imagen: String
let Puesto: String
let Departamento: String
}
JSON full example from Request
[
{
"datos_conexion": {
"conexion": "2019-05-27 17:05:45"
}
},
{
"estado_lanzamiento": {
"tiempo_restante": 240,
"etapa_actual": "Configuracion"
}
},
{
"usuario": [
{
"Id": "4",
"Nombre": "This is the Name",
"Email": "email#gmail.com",
"Password": "1234",
"Imagen": "default.jpg",
"Puesto": "",
"Departamento": "Etapa Final"
}
]
}
]
decoding code
URLSession.shared.dataTask(with: url2) { (data, resp, err) in
guard let data = data else{return}
let dataAsString = String(data: data, encoding: .utf8)
// print(dataAsString)
do {
let JSONDATA = try JSONDecoder().decode([peticion].self, from: data)
// print(data)
} catch let jsonErr {
print("cant decode", jsonErr)
}
Errors trying to do this:
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode dictionary but found an Array instead.", underlyingError: nil))
Based on your JSON, you are receiving an [[String: Any]] which is Array containing Dictionaries of Any values and String keys. This is tricky, since the dictionaries are containing values of Any which is not Decodable. Also, the container is Array, usually we would have a Dictionary as the container.
Normally, we would simplify this response.. But we cannot on this scenario.
One way to decode this is:
struct Peticion: Decodable{
let datosConexion: DatosConexion
let estadoLanzamiento: EstadoLanzamiento
let usuario: [Usuario]
init(from decoder : Decoder) throws {
//unkeyed because we are getting an array as container
var unkeyedContainer = try decoder.unkeyedContainer()
let datosConexionWrapper = try unkeyedContainer.decode(DatosConexionWrapper.self)
let estadoLanzamientoWrapper = try unkeyedContainer.decode(EstadoLanzamientoWrapper.self)
let usuarioWrapper = try unkeyedContainer.decode(UsuarioWrapper.self)
datosConexion = datosConexionWrapper.datosConexion
estadoLanzamiento = estadoLanzamientoWrapper.estadoLanzamiento
usuario = usuarioWrapper.usuario
}
}
//use wrappers to handle the outer dictionary
struct DatosConexionWrapper: Decodable{
let datosConexion: DatosConexion
enum CodingKeys: String, CodingKey{
case datosConexion = "datos_conexion"
}
}
struct DatosConexion: Decodable{
let conexion: String
}
struct EstadoLanzamientoWrapper: Decodable{
let estadoLanzamiento: EstadoLanzamiento
enum CodingKeys: String, CodingKey{
case estadoLanzamiento = "estado_lanzamiento"
}
}
struct EstadoLanzamiento: Decodable{
let tiempoRestante: Int
let etapaActual: String
enum CodingKeys: String, CodingKey {
case tiempoRestante = "tiempo_restante"
case etapaActual = "etapa_actual"
}
}
struct UsuarioWrapper: Decodable{
let usuario: [Usuario]
}
struct Usuario: Decodable{
let id: String
let nombre: String
let email: String
let password: String
let imagen: String
let puesto: String
let departamento: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case nombre = "Nombre"
case email = "Email"
case password = "Password"
case imagen = "Imagen"
case puesto = "Puesto"
case departamento = "Departamento"
}
}
Decoding code //i only changed the decoding type to my Peticion struct
URLSession.shared.dataTask(with: url2) { (data, resp, err) in
guard let data = data else{return}
let dataAsString = String(data: data, encoding: .utf8)
// print(dataAsString)
do {
let JSONDATA = try JSONDecoder().decode(Peticion.self, from: data)
// print(data)
} catch let jsonErr {
print("cant decode", jsonErr)
}
This was a fun exercise. This is also untested code, but I'm pretty sure it would work.
import Foundation
// MARK: - WelcomeElement
struct WelcomeElement: Codable {
let datosConexion: DatosConexion?
let estadoLanzamiento: EstadoLanzamiento?
let usuario: [Usuario]?
enum CodingKeys: String, CodingKey {
case datosConexion = "datos_conexion"
case estadoLanzamiento = "estado_lanzamiento"
case usuario
}
}
// MARK: - DatosConexion
struct DatosConexion: Codable {
let conexion: String
}
// MARK: - EstadoLanzamiento
struct EstadoLanzamiento: Codable {
let tiempoRestante: Int
let etapaActual: String
enum CodingKeys: String, CodingKey {
case tiempoRestante = "tiempo_restante"
case etapaActual = "etapa_actual"
}
}
// MARK: - Usuario
struct Usuario: Codable {
let id, nombre, email, password: String
let imagen, puesto, departamento: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case nombre = "Nombre"
case email = "Email"
case password = "Password"
case imagen = "Imagen"
case puesto = "Puesto"
case departamento = "Departamento"
}
}
typealias Welcome = [WelcomeElement]

How to filter invalid data Using swift 4.1 codable ( json decode)

I got to know struct "Codable" in swift 4.0, *.
So, I tried that when decode josn.
if let jsonData = jsonString.data(using: .utf8) {
let decodingData = try? JSONDecoder().decode(SampleModel.self, from: jsonData)
}
Example sample data model below.
struct SampleModel : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
And sample json data is .. below.
{
"data": {
"result" : 1
"total_count": 523,
"list": [
{
"no": 16398,
"category" : 23,
"template_seq" : 1
},
{
"no": -1,
"category" : 23,
"template_seq" : 1
}
]
}
}
But i want filtering wrong data.
If the value of "no" is less than or equal to 0, it is an invalid value.
Before not using codable...below.
(using Alamifre ison response )
guard let dictionaryData = responseJSON as? [String : Any] else { return nil }
guard let resultCode = dictionaryData["result"] as? Bool , resultCode == true else { return nil }
guard let theContainedData = dictionaryData["data"] as? [String:Any] else { return nil }
guard let sampleListData = theContainedData["list"] as? [[String : Any]] else { return nil }
var myListData = [MyEstimateListData]()
for theSample in sampleListData {
guard let existNo = theSample["no"] as? Int, existNo > 0 else {
continue
}
myListData.append( ... )
}
return myListData
how to filter wrong data or invalid data using swift 4.0 Codable ??
you can make codable for inital resonse
Here is your model:
import Foundation
struct Initial: Codable {
let data: DataModel?
}
struct DataModel: Codable {
let result, totalCount: Int
let list: [List]?
enum CodingKeys: String, CodingKey {
case result
case totalCount = "total_count"
case list
}
}
struct List: Codable {
let no, category, templateSeq: Int
enum CodingKeys: String, CodingKey {
case no, category
case templateSeq = "template_seq"
}
}
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And use it like that :
if let initail = try? Initial.init(data: data) , let list = initail.data?.list {
var myListData = list.filter { $0.no > 0 }
}
Yes, you have to use filters for that with codable:
1: Your struct should be according to your response like that:
struct SampleModel : Codable {
var result: Int?
var total_count: Int?
var list: [List]?
}
struct List : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
2: Parse your response using a codable struct like that:
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryData["data"] as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
let resultData = try JSONDecoder().decode(SampleModel.self, from: jsonData)
success(result as AnyObject)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
3: now you can filter invalid data simply:
let filterListData = resultData.list?.filter({$0.no > 0})
let invalidData = resultData.list?.filter({$0.no <= 0})