Cannot extract all the values from the parsed JSON in Swift - json

Its seems that I cannot extract all the values from from JSON. I can see the output just fine but I cannot put it in a array to present it in labels.
What is it that Im doing wrong?
struct EfectivO: Codable {
public var counted: Int
public var registered: Int
}
struct TotalsByPaymentType: Codable {
public var Efectivo: EfectivO
}
struct Results: Codable {
var locale: String
var date: String
var totalsByPaymentType: TotalsByPaymentType
}
struct Loan: Codable {
var results: [Results]
var petitions: [Loan] = []
}
do {
let courses = try JSONDecoder().decode(Loan.self, from: data)
for item in courses.results {
self.petitions.append(courses.self)
print(item)
}
return
} catch let jsonErr {
print("Error serializing json:", jsonErr)
return
}
I havent post the raw JSON but I can see that there correct response.
This is actual JSON:
{
"results": [
{
"locale": "GB",
"date": "2020-01-29",
"totalsByPaymentType": {
"Efectivo": {
"counted": 108130,
"registered": 106135
},
"Tarjeta de Credito": {
"counted": 209720,
"registered": 209720
},
"Cheque Gourmet": {
"counted": 1800,
"registered": 1800
},
"Ticket Restaurantes": {
"counted": 3800,
"registered": 3800
},
"Resto-In": {
"counted": 0,
"registered": 0
},
"Sodexo": {
"counted": 1921,
"registered": 1921
},
"Friend": {
"counted": 0,
"registered": 0
}
}
}
]
}
As you can seem its very complicated structure and I have a nice response.

The root is has just a single array that is [Loan]
- turns out this is not what you have. Your JSON is a top-level object with one property results, which contains an array of objects. Also, the data structure does not look like it's recursive, as the data model in your question suggests.
Given the example JSON, you could either name each of the results explicitly, like so:
struct Results: Codable {
let results: [Result]
}
struct Result: Codable {
let locale, date: String
let totalsByPaymentType: TotalsByPaymentType
}
struct TotalsByPaymentType: Codable {
let efectivo, tarjetaDeCredito, chequeGourmet, ticketRestaurantes,
restoIn, sodexo, friend: Total
enum CodingKeys: String, CodingKey {
case efectivo = "Efectivo"
case tarjetaDeCredito = "Tarjeta de Credito"
case chequeGourmet = "Cheque Gourmet"
case ticketRestaurantes = "Ticket Restaurantes"
case restoIn = "Resto-In"
case sodexo = "Sodexo"
case friend = "Friend"
}
}
struct Total: Codable {
let counted, registered: Int
}
or collect all the payment totals in a dictionary:
struct Results: Codable {
let results: [Result]
}
struct Result: Codable {
let locale, date: String
let totalsByPaymentType: [String: Total]
}
Which approach to use depends on how dynamic the "payment types" values change. Is it a fixed list? Then go for properties. Does is change every couple of weeks or months? Then use a dictionary.
In any case, you parse that using
do {
let results = try JSONDecoder().decode(Results.self, from: data)
for result in results.results {
// handle each result
}
} catch {
print(error)
}
(NB you should probably avoid generic names like Result in this case, but I don't understand your use case well enough to make appropriate suggestions)

Related

Swift : Codable Struct Always Nil

I Have the following JSON Response
{
"status_code": 1000,
"data": {
"user_id": 1000,
"bid": "E5PPD5E3",
"province": 0,
"location": "123,123"
},
"message": "Verified"
}
And This is my Struct
struct Basicresponse : Codable{
var statusCode : Int!
var message : String?
var data : data?
enum CodingKeys: String, CodingKey {
case statusCode = "status_code"
}
}
struct data : Codable{
var province : Int
var userID : Int
var location : String
var bid : String
enum CodingKeys: String, CodingKey {
case province, location , bid
case userID = "user_id"
}
}
And
do {
let jsonData = try JSONDecoder().decode(Basicresponse.self, from: data!)
if(jsonData.statusCode == 1000){
print(jsonData)
}else{
self.alert.show(target: self.view, message: jsonData.message!)
}
}
catch let jsonerr {
print("error serrializing error",jsonerr)
}
But the result as below,
Basicresponse(statusCode: Optional(2000), message: nil, data: nil)
I don't know why both the data and the message are always nil ?! I tried the end point with Post man and its works fine but in my app its always nil, Am i missing something here ?
Any help will be much appreciated
The issue is that you’ve excluded message and data from your CodingKeys. But you can add them like so:
struct Basicresponse: Codable {
var statusCode : Int!
var message : String?
var data : data?
enum CodingKeys: String, CodingKey {
case statusCode = "status_code"
case message, data
}
}
The other alternative is to not supply CodingKeys at all and tell your decoder to do the snake case conversion for you.
let data = """
{
"status_code": 1000,
"data": {
"user_id": 1000,
"bid": "E5PPD5E3",
"province": 0,
"location": "123,123"
},
"message": "Verified"
}
""".data(using: .utf8)!
struct BasicResponse: Codable {
var statusCode: Int
var message: String?
var data: Bid?
}
struct Bid: Codable {
var province: Int
var userId: Int
var location: String
var bid: String
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let jsonData = try decoder.decode(BasicResponse.self, from: data)
if jsonData.statusCode == 1000 {
print(jsonData)
} else {
print(jsonData.message ?? "No message")
}
} catch let jsonError {
print("error serializing error", jsonError)
}
I hope you don’t mind, but I’ve renamed your data type to be Bid (as data doesn’t conform to standard class naming conventions of starting with upper case letter and it’s too easily confused with the existing Data type). I don’t know if Bid is the right name, so use whatever you think is appropriate, but hopefully it illustrates the idea.

TypeMismatch error in filling List from JSON

i am trying to fill a list from JSON in SwiftUI, which has this format:
{
"books": [{
"id": "87",
"title": "2001 odissea nello spazio",
"author_id": null,
"author": "arthur c. clarke",
"editor_id": null,
"editor": "longanesi",
"price": "0.00",
"isbn": "",
"note": ""
}, ......]
}
i created this struct for the Book object:
struct Book: Decodable, Identifiable {
public var id: Int;
public var title: String;
public var isbn: String;
enum CodingKeys: String, CodingKey {
case id = "id";
case title = "title";
case isbn = "isbn";
}
}
then i created this class to get the remote json:
import Foundation
public class GetBooks: ObservableObject {
#Published var books = [Book]();
init() {
load();
}
func load() {
let url = URL(string: "https://www.mattepuffo.com/api/book/get.php")!;
URLSession.shared.dataTask(with: url) {
(data, response, error) in
do {
if let d = data {
let decodedLists = JSONDecoder();
decodedLists.keyDecodingStrategy = .convertFromSnakeCase;
let dec = try decodedLists.decode([Book].self, from: d);
DispatchQueue.main.async {
self.books = dec;
}
} else {
print("Non ci sono libri");
}
} catch {
print(error)
}
}.resume();
}
}
but i get an error: typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
I think I understand what the problem is, but I don't understand how to solve it.
in the sense that the problem is that the json starts with an object (books) and not with an array.
but I don't understand how I have to modify the code!
I also tried to modify this line in this way, getting the error you see in the comment:
let dec = try decodedLists.decode(Book.self, from: d);
DispatchQueue.main.async {
self.books = dec; // Cannot assign value of type 'Book' to type '[Book]'
}
Your problem is that your JSON is not an Array of Book.
You need an upper level struct:
struct BookList: Decodable {
let books : [Book]
}
and then decode this structure instead of the array:
let dec = try decodedLists.decode(BookList.self, from: d);
DispatchQueue.main.async {
self.books = dec.books;
}
There are two major issues in your code:
You are ignoring the root object of the JSON, the dictionary with key books. This causes the error.
The type of key id is a string, in JSON everything in double quotes is String.
Further you don't need CodingKeys if all struct member names match the JSON keys and if the struct members are not going to be modified declare them as constants (let). Finally this is Swift: No trailing objective-c-ish semicolons.
struct Root: Decodable {
public let books: [Book]
}
struct Book: Decodable, Identifiable {
public let id: String
public let title: String
public let isbn: String
}
let result = try decodedLists.decode(Root.self, from: d)
DispatchQueue.main.async {
self.books = result.books
}

Swift : The data couldn’t be read because it isn’t in the correct format

I try to call the POST Api with Alamofire but it's showing me an error of incorrect format.
This is my JSON response:
[
{
"_source": {
"nome": "LOTERIAS BELEM",
"endereco": "R DO COMERCIO, 279",
"uf": "AL",
"cidade": "BELEM",
"bairro": "CENTRO"
},
"_id": "010177175"
},
{
"_source": {
"nome": "Bel Loterias"
},
"_id": "80224903"
},
{
"_source": {
"nome": "BELLEZA LOTERIAS",
"endereco": "R RIVADAVIA CORREA, 498",
"uf": "RS",
"cidade": "SANTANA DO LIVRAMENTO",
"bairro": "CENTRO"
},
"_id": "180124986"
}
]
class Album: Codable {
var _source : [_source]
}
class _source: Codable {
var nome : String
var endereco : String
var uf : String
var cidade : String
var bairro : String
}
var arrList = [Album]()
And this is how i try to Decoding with Alamofire.
func request() {
let urlString = URL(string: "My Url")
// Alamofire.request(url!).responseJSON {(response) in
Alamofire.request(urlString!, method: .post, parameters: ["name": "belem"],encoding: JSONEncoding.default, headers: nil).responseJSON {
(response) in
switch (response.result) {
case .success:
if let data = response.data {
do {
let response = try JSONDecoder().decode([Album].self, from: data)
DispatchQueue.main.async {
self.arrList = response
}
}
catch {
print(error.localizedDescription)
}
}
case .failure( let error):
print(error)
}
}
}
Just your Album model is incorrect.
struct Album: Codable {
var source : Source
var id : String
enum CodingKeys: String, CodingKey {
case source = "_source"
case id = "_id"
}
}
struct Source: Codable {
var nome : String
var endereco : String?
var uf : String?
var cidade : String?
var bairro : String?
}
If you don't want _id altogether then simply remove the related parts.
As for your Alamofire related code, that part is good.
Notable improvements:
Have avoided underscored variable name in model by customizing CodingKeys for key mapping purpose
Typenames should always start with a Capital letter (so _source is Source)
Similarly, variable names should always start with a lowercase letter
Made some variables optional (based on your updated response)
Keeping a variable non-optional means it must be present in the response for the model to be created
Making a variable optional means that key may or may not be present in the response and it not being there won't prevent the model from being created
I would like to recommend you to use json4swift.com. You just have to copy your json and paste there. It will automatically create modal struct or class from your json.
Coming back to your question, Your class Album doesn't have array of [_source]. That's the reason you are getting following error "The data couldn’t be read because it isn’t in the correct format".
Try below given format of album class,
class Album: Codable
{
var source: Source?
var id: String?
}
Please try to avoid using underscore in Swift.

How to decode a nested JSON struct with dynamic variable from API call

When calling to this specific API, one of the variables changes based on the results.
Example:
{
"map": {
"1945206": {
"installBaseNUMB": 0,
"serialNumber": "KB1",
...
}
}
}
1945206 will change to another number and I don't know how to properly decode this.
My Codable struct is as follows:
struct Map: Codable {
let the1945206: The1945206?
enum CodingKeys: String, CodingKey {
case the1945206 = "1945206"
}
}
struct The1945206: Codable {
let installBaseNUMB: Int?
let serialNumber, woStatus: String?
let workOrderNumber: Int?
let woNotes: [String]?
let woParts: [WoPart]?
}
If 1945206 changes to another value, it breaks and won't show any results. How do I use a dynamic variable in decoding the data?
You can try
struct Root : Codable {
let map: [String:The1945206]
}
let res = try? JSONDecoder().decode(Root.self,from:data)
print(res?.map.values)
{
"map": {
"1945204": {
"installBaseNUMB": 0,
"serialNumber": "KB1",
...
},
"1945205": {
"installBaseNUMB": 0,
"serialNumber": "KB1",
...
},
"1945206": {
"installBaseNUMB": 0,
"serialNumber": "KB1",
...
}
}
}
I will suggest setting different names than The1945206 as it is less readable. I will use MapElement instead. If you know that map will have only one element, and you don't want to check what key is associated with this value, you can use computed property var value:MapElement? to get it. It returns an optional, because let map:[String:MapElement] dictionary can be empty.
let json = """
{
"map": {
"1945206": {
"installBaseNUMB": 0,
"serialNumber": "KB1"
}
}
}
""".data(using: .utf8)!
struct Container: Codable {
let map:[String:MapElement]
var value:MapElement? {
return map.values.first
}
}
struct MapElement: Codable {
let installBaseNUMB: Int
let serialNumber: String
}
let res = try? JSONDecoder().decode(Container.self,from:json)
print(res?.value as Any)
Update:
If you want to use multiple MapElement you can change var value:MapElement to var values:[MapElement] like that:
struct Container: Codable {
let map:[String:MapElement]
var values:[MapElement] {
return map.values
}
}
But keep in mind that you are storing data as a dictionary so the order of elements is not guaranteed.
Try this:
struct Install: Decodable {
let installBaseNUMB: Int, serialNumber: String
}
let installData = try? JSONDecoder().decode([String:[String:Install]].self, from: jsonData2!)
let install = installData?.values.first?.values.first

Extracting data from JSON array with swift Codable

I have a JSON response like this:
I have currently designed my decodable struct to be as follows:
struct PortfolioResponseModel: Decodable {
var dataset: Dataset
struct Dataset: Decodable {
var data: Array<PortfolioData> //I cannot use [Any] here...
struct PortfolioData: Decodable {
//how to extract this data ?
}
}
}
The question is, how do I extract the data inside the array, which can have a value Double or String.
Here is the sample string to make this work on playground:
let myJSONArray =
"""
{
"dataset": {
"data": [
[
"2018-01-19",
181.29
],
[
"2018-01-18",
179.8
],
[
"2018-01-17",
177.6
],
[
"2018-01-16",
178.39
]
]
}
}
"""
Extracting the data:
do {
let details2: PortfolioResponseModel = try JSONDecoder().decode(PortfolioResponseModel.self, from: myJSONArray.data(using: .utf8)!)
//print(details2)
//print(details2.dataset.data[0]) //somehow get "2018-01-19"
} catch {
print(error)
}
I cannot use [Any] here.
Never use Any when decoding JSON because usually you do know the type of the contents.
To decode an array you have to use an unkeyedContainer and decode the values in series
struct PortfolioResponseModel: Decodable {
var dataset: Dataset
struct Dataset: Decodable {
var data: [PortfolioData]
struct PortfolioData: Decodable {
let date : String
let value : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
date = try container.decode(String.self)
value = try container.decode(Double.self)
}
}
}
}
You can even decode the date strings as Date
struct PortfolioData: Decodable {
let date : Date
let value : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
date = try container.decode(Date.self)
value = try container.decode(Double.self)
}
}
if you add a date formatter to the decoder
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
let details2 = try decoder.decode(PortfolioResponseModel.self, from: Data(myJSONArray.utf8))
To add to this, there is a very good example of complex JSON parsing with arrays in particular here. I hope this helps those who are trying to use Codeable with larger, more realistic JSON data.
The overview is this: Imagine you had the following JSON format:
{
"meta": {
"page": 1,
"total_pages": 4,
"per_page": 10,
"total_records": 38
},
"breweries": [
{
"id": 1234,
"name": "Saint Arnold"
},
{
"id": 52892,
"name": "Buffalo Bayou"
}
]
}
This is a common format with the array nested inside. You could create a struct that encapsulates the entire response, accommodating arrays for the "breweries" key, similar to what you were asking above:
struct PagedBreweries : Codable {
struct Meta : Codable {
let page: Int
let totalPages: Int
let perPage: Int
let totalRecords: Int
enum CodingKeys : String, CodingKey {
case page
case totalPages = "total_pages"
case perPage = "per_page"
case totalRecords = "total_records"
}
}
struct Brewery : Codable {
let id: Int
let name: String
}
let meta: Meta
let breweries: [Brewery]
}