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

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

Related

Decode JSON with variables in Swift

I am trying to decode this type of JSON-Data in Swift
{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
The name and number of ingredients is variable. Therefore I am only able to decode it in this type of structure:
struct Product: Codable {
var total_ingredients: [[String: String]]?
private enum CodingKeys : String, CodingKey {
case total_ingredients = "Total ingredients"
}
}
But I would like to be able to decode it in either one dictionary: var total_ingredients: [String: String]? or my preferred choice in an array of objects: var total_ingredients: [Ingredient]?
struct Ingredient: Codable {
var name: String
var percentage: String
}
I already tried to solve my problem with an extension but it isn't working and I don't think that's the correct approach:
extension Ingredient {
init(_ ingredient: [String: String]) {
var key: String = ""
var value: String = ""
for data in ingredient {
key = data.key
value = data.value
}
self = .init(name: key, percentage: value)
}
}
Thanks in advance :)
You have to implement init(from decoder and map the array of dictionaries to Ingredient instances
struct Product: Decodable {
let totalIngredients: [Ingredient]
private enum CodingKeys : String, CodingKey { case totalIngredients = "Total ingredients" }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let ingredientData = try container.decode([[String:String]].self, forKey: .totalIngredients)
totalIngredients = ingredientData.compactMap({ dict -> Ingredient? in
guard let key = dict.keys.first, let value = dict[key] else { return nil }
return Ingredient(name: key, percentage: value)
})
}
}
struct Ingredient {
let name, percentage: String
}
let jsonString = """
{"Total ingredients":[{"PE-LLD":"54.4 %"},{"PE-HD":"41.1 %"},{"TiO2":"4.5 %"}]}
"""
let data = Data(jsonString.utf8)
do {
let result = try JSONDecoder().decode(Product.self, from: data)
print(result)
} catch {
print(error)
}
The extension is not needed.

Unable to print specific nested json data with JSONSerialization

I'm making an api call to get some data, the json dataAsString looks like this...
guard let dataAsString = String(data: data, encoding: .utf8)else {return}
print(dataAsString)
JSON DATA
{"patch_report":{"name":"macOS Updates","patch_software_title_id":"1","total_computers":"5","total_versions":"1","versions":{"version":{"software_version":"10.15.3","computers":{"size":"5","computer":{"id":"467","name":"EPART1BGF8J9"}}}}}}
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with:data, options:[]) as? [String: Any] {
// try to read out a string array
if let patch_report = json["patch_report"] as? [String] {
print(patch_report)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
How could I use JSONSerialization to get only certain nested JSON values?
I've been told by many not to use JSONSerialization so I've provided an alternate solution. Below has the Codable solution as well.
Click Here For An Explination - Using Codable with nested JSON!
I was able to find the answers below...
Get nested JSON using JSONSerialization
do {
// make sure this JSON is in the format we expect
if let json = try JSONSerialization.jsonObject(with:data, options:[]) as? [String: Any] {
// try to read out a string array
if let patch_report = json["patch_report"] as? [String:Any] {
if let versions = patch_report["versions"] as? [String:Any] {
if let version = versions["version"] as? [String:Any] {
if let software_version = version["software_version"] as? String {
print(software_version)
}
}}
} else {print("Not Available")}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
Get nested Json using Decoder
struct PatchReport: Codable {
var patch_report:Patch_report?
enum CodingKeys: String, CodingKey{
case patch_report = "patch_report"
}
struct Patch_report: Codable {
var name:String?
var patch_software_title_id:String?
var total_computers:String?
var total_versions:String?
var versions: Versions?
enum CodingKeys: String, CodingKey {
case name = "name"
case patch_software_title_id = "patch_software_title_id"
case total_computers = "total_computers"
case total_versions = "total_versions"
case versions = "versions"
}
struct Versions: Codable {
var version: Version?
enum CodingKeys: String, CodingKey {
case version = "version"
}
struct Version: Codable {
var software_version:String?
var computers:Computers?
enum CodingKeys: String, CodingKey {
case software_version = "software_version"
case computers = "computers"
}
struct Computers: Codable{
var size:String?
var computer:Computer?
enum CodingKeys: String, CodingKey {
case size = "size"
case computer = "computer"
}
struct Computer: Codable{
var id:String?
var name:String?
enum CodingKeys: String, CodingKey {
case id = "id"
case name = "name"
}
}
}
}
}
}
}
let response = try! JSONDecoder().decode(PatchReport.self, from: data)
print(String(response.patch_report?.total_computers ?? ""))
You're trying to cast patch_report as a String which won't work. Try this instead:
if let patch_report = json["patch_report"] as? [String: Any] {
print(patch_report["name"])
}

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

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]

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)