Decoding unnamed JSON array swift 4 with structure - json

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]

Related

Swift decode json when one property name / key is dynamic

Json response from this call https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/usd/eur.min.json is pretty basic
{
"date": "2022-12-27",
"eur": 0.939751
}
first property is always named "date" and it's always String second is Double but it's name/key is dynamic, it can be "usd", "eur" etc.
I have tried
struct RateResponse: Decodable {
let date: String
let rate: Double
enum CodingKeys: CodingKey {
case date
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
date = try container.decode(String.self, forKey: CodingKeys.date)
let singleValueContainer = try decoder.singleValueContainer()
rate = try singleValueContainer.decode(Double.self)
}
}
got this "Expected to decode Double but found a dictionary instead." I know what error says but not to sure how to solve it.
My suggestion is a custom KeyDecodingStrategy.
First you need a neutral CodingKey struct to replace the currency key.
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
The RateResponse struct can be reduced to
struct RateResponse: Decodable {
let date: String
let rate: Double
}
The key decoding strategy passes the date key and replaces anything else with rate
let jsonString = """
{
"date": "2022-12-27",
"eur": 0.939751
}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({
let value = $0.last!.stringValue
switch value {
case "date": return $0.last!
default: return AnyKey(stringValue: "rate")!
}
})
let result = try decoder.decode(RateResponse.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}

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

Deserialization JSON swift 4.2

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.

Swift, How to Parse/Decode the JSON using Decodable and Codable, When key are unknow/dynamic

Below is my JSON, and I am not able to decode(using CodingKeys)
The data within the regions key is a Dictionary ("IN-WB", "IN-DL" & so on....), as the keys are dynamic, it can be changed more or less.
Please help me parsing the same using Decodable and Codable.
All the data should be within the single model.
{
"provider_code": "AIIN",
"name": "Jio India",
"regions": [
{
"IN-WB": "West Bengal"
},
{
"IN-DL": "Delhi NCR"
},
{
"IN-TN": "Tamil Nadu"
},
{
"IN": "India"
}
]
}
Just use a Dictionary for the regions.
struct Locations: Codable {
let providerCode: String
let name: String
let regions: [[String: String]]
enum CodingKeys: String, CodingKey {
case providerCode = "provider_code"
case name, regions
}
}
You cannot create a specific model for the regions as you wont know the property names
One of possible approach, without using dictionary. But still we have to found key at first )
I like this style as we can use Regions from beginning.
// example data.
let string = "{\"provider_code\":\"AIIN\",\"name\":\"Jio India\",\"regions\":[{\"IN-WB\":\"West Bengal\"},{\"IN-DL\":\"Delhi NCR\"},{\"IN-TN\":\"Tamil Nadu\"},{\"IN\":\"India\"}]}"
let data = string.data(using: .utf8)!
// little helper
struct DynamicGlobalKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
// model
struct Location: Decodable {
let providerCode: String
let name: String
let regions: [Region]
}
extension Location {
struct Region: Decodable {
let key: String
let name: String
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: DynamicGlobalKey.self)
key = container.allKeys.first!.stringValue
name = try container.decode(String.self, forKey: container.allKeys.first!)
}
}
}
// example of decoding.
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let location = try decoder.decode(Location.self, from: data)

How to parse json in swift 4 an array inside an array

I'm having trouble accessing the StatusList array from this API response. How would I get that information?
my current code is and does not work.
let parsedData = try JSONSerialization.jsonObject(with: data!) as! [String:Any]
for list in (parsedData["StatusList"] as? [String])!{
for shipmentstatus in list["Details"]{
//doesn't work
}
}
here is the JSON
{
"MobileAPIError":"",
"StatusList":{
"ErrorMessage":"",
"Details":[
{
"Pro":"000000000",
"BlNumber":"000000",
"ReferenceNumber":"",
"Scac":"CNWY",
"Carrier":"XPO LOGISTICS FREIGHT, INC.",
"ShipperCode":"xx999",
"ShipperName":"W B EQUIPMENT",
"ShipperCity":"WOOD RIDGE",
"ShipperState":"NJ"
},
{
"Pro":"0000000",
"BlNumber":"#00000-R",
"ReferenceNumber":"",
"Scac":"CNWY",
"Carrier":"XPO LOGISTICS FREIGHT, INC.",
"ShipperCode":"xx999",
"ShipperName":"W B EQUIPMENT",
"ShipperCity":"WOOD RIDGE",
"ShipperState":"NJ"
},
]
}
}
EDIT: I would like to try to use JSONDecoder, as it looks like a decent solution.
Would this work?
struct ShipmentStatusList: Decodable {
let MobileAPIError: String
let StatusList: StatusListItems
enum CodingKeys : String, CodingKey {
case MobileAPIError
case StatusList
}
}
struct StatusListItems{
let ErrorMessage: String
let Details: [Details]
}
struct Details {
let Pro: String
let BLNumber: String
let ReferenceNumber: String
}
The value for key StatusList is a dictionary, please note the {}, the array is the value for key Details in statusList
if let parsedData = try JSONSerialization.jsonObject(with: data!) as? [String:Any],
let statusList = parsedData["StatusList"] as? [String:Any],
let details = statusList["Details"] as? [[String:Any]] {
for detail in details {
print(detail["Pro"])
}
}
}
And don't do things like (... as? ...)!, never!
The corresponding Codable structs are
struct Status: Codable {
let mobileAPIError: String
let statusList: StatusList
enum CodingKeys: String, CodingKey { case mobileAPIError = "MobileAPIError", statusList = "StatusList" }
}
struct StatusList: Codable {
let errorMessage: String
let details: [Detail]
enum CodingKeys: String, CodingKey { case errorMessage = "ErrorMessage", details = "Details" }
}
struct Detail: Codable {
let pro, blNumber, referenceNumber, scac: String
let carrier, shipperCode, shipperName, shipperCity: String
let shipperState: String
enum CodingKeys: String, CodingKey {
case pro = "Pro", blNumber = "BlNumber", referenceNumber = "ReferenceNumber"
case scac = "Scac", carrier = "Carrier", shipperCode = "ShipperCode"
case shipperName = "ShipperName", shipperCity = "ShipperCity", shipperState = "ShipperState"
}
}
do {
let result = try JSONDecoder().decode(Status.self, from: data!)
print(result)
} catch { print(error) }