Swift 5 Parsing strange json format - json

I'm trying to parse JSON but keep getting incorrect format error. The JSON I get back from FoodData Central (the USDA's Nutrition API) is as follows:
{
dataType = "Survey (FNDDS)";
description = "Chicken thigh, NS as to cooking method, skin not eaten";
fdcId = 782172;
foodNutrients = (
{
amount = "24.09";
id = 9141826;
nutrient = {
id = 1003;
name = Protein;
number = 203;
rank = 600;
unitName = g;
};
type = FoodNutrient;
},
{
amount = "10.74";
id = "9141827";
nutrient = {
id = 1004;
name = "Total lipid (fat)";
number = 204;
rank = 800;
unitName = g;
};
type = FoodNutrient;
}
);
}
My Structs:
struct Root: Decodable {
let description: String
let foodNutrients: FoodNutrients
}
struct FoodNutrients: Decodable {
// What should go here???
}
From the JSON, it looks like foodNutrients is an array of unnamed objects, each of which has the values amount: String, id: String, and nutrient: Nutrient (which has id, name etc...) However, forgetting the Nutrient object, I can't even parse the amounts.
struct FoodNutrients: Decodable {
let amounts: [String]
}
I don't think its an array of string, but I have no idea what the () in foodNutrients would indicate.
How would I go about parsing this JSON. I'm using Swift 5 and JSONDecoder. To get the JSON I use JSONSerializer, then print out the JSON above.

This is not a JSON. This is a property list in the openStep format.
This is how it can be modelled (use String instead of Int):
struct Root: Decodable {
let description: String
let foodNutrients: [FoodNutrient]
}
struct FoodNutrient: Decodable {
let id: String
let amount: String
let nutrient: Nutrient
}
struct Nutrient: Decodable {
let name: String
let number: String
let rank: String
let unitName: String
}
And then decode it like this:
try PropertyListDecoder().decode(Root.self, from: yourStr)

The () in foodNutrients indicates that it holds an array of objects - in that case FoodNutrient objects. Therefore your root object should look like this:
struct Root: Decodable {
let description: String
let foodNutrients: [FoodNutrient]
}
Now the foodNutrient is except for the nutrient object straightforward:
struct FoodNutrient: Decodable {
let id: Int // <-- in your example it is an integer and in the second object a string, choose the fitting one from the API
let amount: String
let nutrient: Nutrient
}
And the nutrient object should look like this:
struct Nutrient: Decodable {
let name: String
let number: Int
let rank: Int
let unitName: String
}
Using Decodable is a good and easy way to serialize JSON. Hope that helps. Happy coding :)

Related

I can't pull Json data while doing MVVM design project with swift

I am making a project in Swift with MVVM design. I want to get coin name, current price, Rank and Symbol from a Crypto site. I can't show the json data I get on the console. The model is in another folder because I did it with MVVM. How can I create a struct to get the data here? You can find screenshots of my project below. I would be glad if you help.
Below are the codes I wrote in my web service file
import Foundation
class WebService {
func downloadCurrencies(url: URL, completion: #escaping ([DataInfo]?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
completion(nil)
} else if let data = data {
let cryptoList = try? JSONDecoder().decode([DataInfo].self, from: data)
print(cryptoList)
if let cryptoList = cryptoList {
completion(cryptoList)
}
}
}
.resume()
}
}
Below are the codes I wrote in my model file
import Foundation
struct DataInfo : Decodable {
var name: String
var symbol: String
var cmc_rank: String
var usd: Double
}
Finally, here is the code I wrote to print the data in the viewController to my console. But unfortunately I can't pull the data.
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?start=1&limit=10&convert=USD&CMC_PRO_API_KEY=5ac24b80-27a1-4d01-81bd-f19620533480")!
WebService().downloadCurrencies(url: url) { cryptos in
if let cryptos = cryptos {
print(cryptos)
}
}
}
I've seen your URL and tested it ON Postman and also i've got your code and tried to put it in a shape, the code is good, but it can't MAP JSON Data against your struct because this is the json Data from Postman
Postman Data
While Looking at your Struct, It Doesnt Match the format of JSON Data you're receiving,
Struct
Well, To Make your structure match the JSON String you need to create Nested String: Any Dictionary. But there's another issue with the logic, you need to decode data outside of the webservice call because it can contain errors which wont be mapped in the struct and can handle if you get other statusCode.
If you try to implement all these things manually, the code will become complex and hard to understand. I would rather recommend you to use Alamofire with SwiftyJSON and it can make your work a lot shorter and easier and understandable.
Sorry for the bad english.
Your api-key is not valid for me.
Your data must be inside of object or invalid keys and you are surely missing it thats why it is not parsing correctly.
My suggestion is to put your json response in this website
"https://app.quicktype.io/"
and replace your struct with new one, you will be good to go hopefully.
Your models does not have 1 to 1 correspondence with the response object. The root object is not a [DataInfo], but another structure that contains an array of DataInfos. Here are the correct models
struct Response: Codable {
let status: Status
let data: [CurrencyData]
}
struct Status: Codable {
let creditCount: Int
let elapsed: Int
let timestamp: String
let totalCount: Int
let errorCode: Int?
let errorMessage: String?
let notice: String?
enum CodingKeys: String, CodingKey {
case notice
case timestamp
case elapsed
case creditCount = "credit_count"
case errorCode = "error_code"
case errorMessage = "error_message"
case totalCount = "total_count"
}
}
enum Currency: String, Codable, Hashable {
case usd = "USD"
}
struct CurrencyData: Codable {
let circulatingSupply: Double?
let cmcRank: Int
let dateAdded: String?
let id: Int
let lastUpdated: String?
let maxSupply: Int?
let name: String
let numMarketPairs: Int
let platform: Platform?
let quote: [String: Price]
let selfReportedCirculatingSupply: String?
let selfReportedMarketCap: String?
let slug: String
let symbol: String
let tags: [String]?
let totalSupply: Double
func price(for currency: Currency) -> Double? {
return quote[currency.rawValue]?.price
}
enum CodingKeys: String, CodingKey {
case id
case name
case platform
case quote
case slug
case symbol
case tags
case circulatingSupply = "circulating_supply"
case cmcRank = "cmc_rank"
case dateAdded = "date_added"
case lastUpdated = "last_updated"
case maxSupply = "max_supply"
case selfReportedCirculatingSupply = "self_reported_circulating_supply"
case selfReportedMarketCap = "self_reported_market_cap"
case totalSupply = "total_supply"
case numMarketPairs = "num_market_pairs"
}
}
struct Price: Codable {
let fullyDilutedMarketCap: Double?
let lastUpdated: String?
let marketCap: Double?
let marketCapDominance: Double?
let percentChange1h: Double?
let percentChange24h: Double?
let percentChange30d: Double?
let percentChange60d: Double?
let percentChange7d: Double?
let percentChange90d: Double?
let price: Double?
let volume24h: Double?
let volumeChange24h: Double?
enum CodingKeys: String, CodingKey {
case price
case fullyDilutedMarketCap = "fully_diluted_market_cap"
case lastUpdated = "last_updated"
case marketCap = "market_cap"
case marketCapDominance = "market_cap_dominance"
case percentChange1h = "percent_change_1h"
case percentChange24h = "percent_change_24h"
case percentChange30d = "percent_change_30d"
case percentChange60d = "percent_change_60d"
case percentChange7d = "percent_change_7d"
case percentChange90d = "percent_change_90d"
case volume24h = "volume_24h"
case volumeChange24h = "volume_change_24h"
}
}
struct Platform: Codable {
let id: Int
let name: String
let symbol: String
let slug: String
let tokenAddress: String?
enum CodingKeys: String, CodingKey {
case id
case name
case symbol
case slug
case tokenAddress = "token_address"
}
}
and you can retrieve the cryptoList in your completion handler like this:
let cryptoList = (try? JSONDecoder().decode([Response].self, from: data))?.data
Also it's not safe to expose your personal data to the internet (API_KEY, etc.)

parsing nested JSON in Swift 5

Can't figure out how to build the struct for this nested JSON. I'm so close but missing something..
I'm trying to verify I'm loading correctly... the first two work great the nested data fails
print(json.pagination.items) // this works
print(json.releases[3].date_added) // this works
print(json.releases[3].basicInformation?.year) //NOT WORKING, returns nil
here is the struct in built
struct Response: Codable {
let pagination: MyResult
let releases: [MyReleases]
}
struct MyResult: Codable {
var page: Int
var per_page: Int
var items: Int
}
struct MyReleases: Codable {
var date_added: String
let basicInformation: BasicInformation?
}
struct BasicInformation: Codable {
let title: String
let year: Int
enum CodingKeys: String, CodingKey {
case title, year
}
}
My JSON is
{
"pagination":{
"page":1,
"pages":2,
"per_page":50,
"items":81,
"urls":{
"last":"https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?page=2&per_page=50",
"next":"https://api.discogs.com/users/douglasbrown/collection/folders/0/releases?page=2&per_page=50"
}
},
"releases":[
{
"id":9393649,
"instance_id":656332897,
"date_added":"2021-03-28T10:54:09-07:00",
"rating":2,
"basic_information":{
"id":9393649,
"master_id":353625,
"master_url":"https://api.discogs.com/masters/353625",
"resource_url":"https://api.discogs.com/releases/9393649",
"thumb":"",
"cover_image":"",
"title":"Ten Summoner's Tales",
"year":2016
}
}
]
}
Any help would be greatly appreciated. I'm so close but missing something... :(
First of all
json.releases[3].date_added
doesn't work with the given JSON, it will crash because there is only one release.
In MyReleases you have to add CodingKeys to map the snake_case name(s)
struct MyReleases: Codable {
var dateAdded: String
let basicInformation: BasicInformation?
private enum CodingKeys: String, CodingKey {
case basicInformation = "basic_information", dateAdded = "date_added"
}
}
or add the .convertFromSnakeCase key decoding strategy.
You can even decode dateAdded as Date with the .iso8601 date decoding strategy.

Parse JSON with swiftyJSON and more

Hi I've tried to parse my JSON but I couldn't get my data from it,
(I used SwiftyJSON)
how can I parse this ugly JSON?
//Mark: parser functions:
private func parseProvincesResult(provincesJSON: JSON, completion: #escaping(_ :ProvincesV2) -> Void) {
print(provincesJSON)
let errorCode: Int = provincesJSON["ErrorCode"].intValue
let errorDescriptions: String = provincesJSON["ErrorString"].stringValue
let newMacKey: String = provincesJSON["NewMacKey"].stringValue
let newPinKey: String = provincesJSON["NewPinKey"].stringValue
let version: Int = provincesJSON["Ver"].intValue
var provinceList: [ProvinceListResult] = []
for i in provincesJSON["ProvinceListResult"].arrayValue {
let id: Int = i["Id"].intValue
let name: String = i["Name"].stringValue
let proList = ProvinceListResult(id: id, name: name)
provinceList.append(proList)
}
let model = ProvincesV2(errorCode: errorCode, errorDescriptions: errorDescriptions, newMacKey: newMacKey, newPinKey: newPinKey, version: version, provinceList: provinceList)
completion(model)
}
and my JSON is:
{"ErrorCode":"8",
"ErrorString":"عملیات با موفقیت انجام شد.",
"NewMacKey":"vph+eLFgxa6LVq90QfsNUA==",
"NewPinKey":"evJiM9W6S9RWEClR6csxEQ==",
"Ver":201,
"ProvinceListResult":[{"Id":1,"Name":"آذربايجان شرقي"},
{"Id":2,"Name":"آذربايجان غربي"},
{"Id":3,"Name":"اردبيل"},
{"Id":4,"Name":"اصفهان"},
{"Id":5,"Name":"البرز"},
{"Id":6,"Name":"ايلام"},
{"Id":7,"Name":"بوشهر"},
{"Id":8,"Name":"تهران"},
{"Id":9,"Name":"چهارمحال و بختياري"},
{"Id":10,"Name":"خراسان جنوبي"},{"Id":11,"Name":"خراسان رضوي"},{"Id":12,"Name":"خراسان شمالي"},{"Id":13,"Name":"خوزستان"},{"Id":14,"Name":"زنجان"},{"Id":15,"Name":"سمنان"},{"Id":16,"Name":"سيستان و بلوچستان"},{"Id":17,"Name":"فارس"},{"Id":18,"Name":"قزوين"},{"Id":19,"Name":"قم"},{"Id":20,"Name":"کردستان"},{"Id":21,"Name":"کرمان"},{"Id":22,"Name":"کرمانشاه"},{"Id":23,"Name":"کهکيلويه و بويراحمد"},{"Id":24,"Name":"گلستان"},{"Id":25,"Name":"گيلان"},{"Id":26,"Name":"لرستان"},{"Id":27,"Name":"مازندران"},{"Id":28,"Name":"مرکزي"},{"Id":29,"Name":"هرمزگان"},{"Id":30,"Name":"همدان"},{"Id":31,"Name":"يزد"},{"Id":44,"Name":"کیش"}]}
how can I parse It?
tnx
Using Codable, you could do this:
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let errorCode, errorString, newMACKey, newPinKey: String
let ver: Int
let provinceListResult: [ProvinceListResult]
enum CodingKeys: String, CodingKey {
case errorCode = "ErrorCode"
case errorString = "ErrorString"
case newMACKey = "NewMacKey"
case newPinKey = "NewPinKey"
case ver = "Ver"
case provinceListResult = "ProvinceListResult"
}
}
// MARK: - ProvinceListResult
struct ProvinceListResult: Codable {
let id: Int
let name: String
enum CodingKeys: String, CodingKey {
case id = "Id"
case name = "Name"
}
}
(generated by https://app.quicktype.io)
And getting a value out might look like:
let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)
print(welcome?.provinceListResult[1])
print(welcome?.provinceListResult[1])
If your original data is in String form, it can be converted to Data by doing this:
myStringJSON.data(using: .utf8)!
create two models for parse JSON
first for the province:
public struct ProvinceModel {
var id: Int?
var name: String?
init(json: JSON) {
self.id = json["Id"].int
self.name = json["Name"].string
}
}
with a constructor, you can parse JSON and set data to variables.
and second model:
public struct MyJSONModel {
var errorString: String?
var newMacKey: String?
var newPinKey: String?
var Ver: Int?
var Provinces: [ProvinceModel]?
init(json: JSON) {
// simple parse
self.errorString = json["ErrorCode"].string
self.newMacKey = json["NewMacKey"].string
self.newPinKey = json["NewPinKey"].string
self.Ver = json["Ver"].int
// array
self.Provinces = [] // empty array
if let jsonArray = json["ProvinceListResult"].array {
for json in jsonArray {
let ProvinceObject = ProvinceModel.init(json: json)
self.Provinces?.append(ProvinceObject)
}
}
}
}
for parse province items in the array, you must create a loop and create an object then set in variables
I prefer to use a constructor for parse JSON instead of a custom function.

swift 4 - Parsing json to struct (codable), but with an extra variable which is not from json

I am currently getting all the data I want from my json, but I want to add extra variable which is not from the json.
struct LoanUser: Codable {
let name: String
let use: String
let location: Location
let loan_amount: Int
let image: Image
var favorite: Bool = false
}
The var favorite: bool = false is not a json string. This is the extra variable I want added
You have to specify coding keys by yourself and do not include favorite
struct LoanUser: Codable {
let name: String
let use: String
let location: Location
let loan_amount: Int
let image: Image
var favorite: Bool = false
enum CodingKeys: String, CodingKey {
case name, use, location, loan_amount, image
}
}

JSON decodable swift 4

I'm having a terrible time creating a public struct for the following JSON. I'm trying to extract the temp and humidity. I've tried to to extract the following JSON but I think I'm having a problem with way I'm identifying each of the variables.
Here's the JSON:
{"coord":{"lon":-82.26,"lat":27.76},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04n"}],"base":"stations","main":{"temp":66.24,"pressure":1021,"humidity":63,"temp_min":62.6,"temp_max":69.8},"visibility":16093,"wind":{"speed":8.05,"deg":80},"clouds":{"all":90},"dt":1523500500,"sys":{"type":1,"id":726,"message":0.0051,"country":"US","sunrise":1523531237,"sunset":1523577156},"id":420006939,"name":"Brandon","cod":200}
And here's the struct
public struct Page: Decodable {
private enum CodingKeys : String, CodingKey {
case coord = "coord", weather = "weather", mainz = "main", visibility = "visibility", wind = "wind", clouds = "clouds", dt = "dt", sys = "sys", id = "id", name = "name", cod = "cod"}
let coord: String
let weather: [String]
let mainz: String
let visibility: Int
let wind: String
let clouds: String
let dt: Int
let sys: String
let id: Int
let name: String
let cod: Int
public struct Main: Decodable {
private enum CodingKeys : String, CodingKey {
case temp = "temp", pressure = "pressure",humidity = "humidity", temp_min = "temp_min", temp_max = "temp_max"
}
let temp: Int
let pressure: Int
let humidity: Int
let temp_min: Int
let temp_max: Int
}
...and the code I'm using the decode...
// Make the POST call and handle it in a completion handler
let task = session.dataTask(with: components.url!, completionHandler: {(data, response, error) in
guard let data = data else { return }
do
{
let result = try JSONDecoder().decode(Page.self, from: data)
for main in result.mainz {
print(main.humidity)
}
}catch
{print("Figure it out")
}
})
task.resume()
Create structures to capture whatever you want. For example, if you're interested in the temperatures and the coordinates, create structures for those:
struct WeatherTemperature: Codable {
let temp: Double
let pressure: Double
let humidity: Double
let tempMin: Double
let tempMax: Double
}
struct WeatherCoordinate: Codable {
let latitude: Double
let longitude: Double
enum CodingKeys: String, CodingKey {
case latitude = "lat"
case longitude = "lon"
}
}
Then create a structure for the whole response:
struct ResponseObject: Codable {
let coord: WeatherCoordinate
let main: WeatherTemperature
}
Note, I only create properties for those keys I care about.
And then decode it:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let responseObject = try decoder.decode(ResponseObject.self, from: data)
print(responseObject.coord)
print(responseObject.main)
} catch {
print(error)
}
That resulted in:
WeatherCoordinate #1(latitude: 27.760000000000002, longitude: -82.260000000000005)
WeatherTemperature #1(temp: 66.239999999999995, pressure: 1021.0, humidity: 63.0, tempMin: 62.600000000000001, tempMax: 69.799999999999997)
Clearly, add whatever other structures/properties you care about, but hopefully this illustrates the idea.
Note, I'd rather not let the JSON dictate my property names, so, for example, I specified a key decoding strategy for my decoder to convert from JSON snake_case to Swift camelCase.
I also like longer property names, so where I wanted more expressive names (latitude and longitude rather than lat and lon), I defined CodingKeys for those. But do whatever you want on this score.