Using the quicktype.io I reflected the following JSON...
{
"message": "The data were fetched successfully.",
"data": {
"date": "2022-07-22",
"day": 149,
"resource": "https://www.someWebsite.com",
"stats": {
"personnel_units": 39000,
"tanks": 1704,
"armoured_fighting_vehicles": 3920,
"artillery_systems": 863,
"mlrs": 251,
"aa_warfare_systems": 113,
"planes": 221,
"helicopters": 188,
"vehicles_fuel_tanks": 2803,
"warships_cutters": 15,
"cruise_missiles": 167,
"uav_systems": 713,
"special_military_equip": 72,
"atgm_srbm_systems": 4
},
"increase": {
"personnel_units": 150,
"tanks": 0,
"armoured_fighting_vehicles": 8,
"artillery_systems": 4,
"mlrs": 0,
"aa_warfare_systems": 0,
"planes": 0,
"helicopters": 0,
"vehicles_fuel_tanks": 22,
"warships_cutters": 0,
"cruise_missiles": 0,
"uav_systems": 3,
"special_military_equip": 0,
"atgm_srbm_systems": 0
}
}
}
into the classic Swift struct ...
struct DataModel: Codable {
var message: String
var data: ObjectsModel
}
struct ObjectsModel: Codable {
var date: String
var day: Int
var resource: String
var stats, increase: [String: Int]
}
My issue is about stats and increase properties.
I simply cannot find a way to either set the CodingKeys for the keys that come in those properties or set the decoding strategy to convert them to CamelCase.
Calling decoder.keyDecodingStrategy = .convertFromSnakeCase doesn't seem to work and the keys are still displayed in SnakeCase...
As much as I understand, you need a new struct for both stats and increase, or it would be a better approach.
struct DictionaryItem: Codable {
let personnelUnits: Int
let tanks: Int
let armouredFightingVehicles: Int
enum CodingKeys: String, CodingKey {
case personnelUnits = "personnel_units"
case tanks
...
}
That way you can use it defining increases and stats as DictionaryItem
Related
So, the issue is - I am trying to display the Mars weather from the Mars Insight API. Here is a link Insight Weather, the data is returning in JSON format and has three levels. The keys have names that change depending on the current date (sols). How to make the structure of mutable properties?... when the property names change every day. Do we have any instruments to parse such JSON?
{
"815": {
"First_UTC": "2021-03-12T14:54:38Z",
"Last_UTC": "2021-03-13T15:34:09Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 728.378,
"ct": 153082,
"mn": 708.4211,
"mx": 744.9279
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"818": {
"First_UTC": "2021-03-15T20:01:49Z",
"Last_UTC": "2021-03-16T17:32:54Z",
"Month_ordinal": 12,
"Northern_season": "late winter",
"PRE": {
"av": 727.696,
"ct": 109855,
"mn": 710.223,
"mx": 743.946
},
"Season": "winter",
"Southern_season": "late summer",
"WD": {
"most_common": null
}
},
"819": {
....
,
"sol_keys": [
"815",
"818",
"819",
"820",
"821"
],
"validity_checks": {
"815": {
"PRE": {
"sol_hours_with_data": [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23
],
"valid": true
}
}
So, Structure:
import Foundation
struct WeatherData: Decodable {
let season: String?
let pre: pre?
let solKeys: [String]
enum CodingKeys: String, CodingKey {
case season = "season"
case pre = "PRE"
case solKeys = "sol_keys"
}
struct pre: Decodable {
let av: Double?
let mn: Double?
let mx: Double?
enum CodingKeys: String, CodingKey {
case av = "av"
case mn = "mn"
case mx = "mx"
}
}
}
Parsing:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
guard let url = URL(string: urlString) else { return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let safeData = data else { return }
do {
let solWeather = try JSONDecoder().decode([String:WeatherData].self, from: safeData)
if let keys = solWeather["sol_keys"] {
for key in keys { //error: For-in loop requires 'WeatherData' to conform to 'Sequence'
let report = solWeather [key]
}
}
print(solWeather)
}
catch let error {
print(error)
}
}.resume()
}
}
Got an error: For-in loop requires 'WeatherData' to conform to 'Sequence'
Don't understand why :(( Can someone help me?
Thanks!
You cannot decode [String:WeatherData].self because the dictionary contains other values which are not WeatherData, for example the [String] value of sol_keys.
The only way to decode this JSON with JSONDecoder is to implement init(with decoder, decode the sol_keys and create your own temporary CodingKeys to be able to decode the arbitrary dictionary keys.
First declare the custom CodingKey
public struct SolKeys: CodingKey {
public let stringValue: String
public init?(stringValue: String) { self.stringValue = stringValue }
public var intValue: Int? { return nil }
public init?(intValue: Int) { return nil }
}
The Decodable structs are
struct SolData : Decodable {
let firstUTC, lastUTC : Date
let pre : Pre
private enum CodingKeys : String, CodingKey {
case firstUTC = "First_UTC", lastUTC = "Last_UTC", pre = "PRE"
}
}
struct Pre: Decodable {
let av, mn, mx : Double
}
struct WeatherData: Decodable {
let solKeys: [String]
var soldata = [String:SolData]()
enum CodingKeys: String, CodingKey {
case solKeys = "sol_keys"
}
init(from decoder : Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.solKeys = try container.decode([String].self, forKey: .solKeys)
let customContainer = try decoder.container(keyedBy: SolKeys.self)
for key in solKeys {
let solKey = SolKeys(stringValue: key)!
let data = try customContainer.decode(SolData.self, forKey: solKey)
soldata[key] = data
}
}
}
And the code to receive and decode the data
let urlString = "https://api.nasa.gov/insight_weather/?api_key=DEMO_KEY&feedtype=json&ver=1.0"
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error { print(error); return }
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let solWeather = try decoder.decode(WeatherData.self, from: data!)
let keys = solWeather.solKeys
for key in keys {
let report = solWeather.soldata[key]!
print(report)
}
}
catch {
print(error)
}
}.resume()
func getKoreacountryChart() {
AF.request("https://api.corona-19.kr/korea/country/new/?", headers: headers).responseJSON { response in
let result = response.data
if result != nil {
let json = JSON(result!)
let seoul = json["seoul"]["totalCase"].intValue
let busan = json["busan"]["totalCase"].intValue
let daegu = json["daegu"]["totalCase"].intValue
let incheon = json["incheon"]["totalCase"].intValue
let gwangju = json["gwangju"]["totalCase"].intValue
let daejeon = json["daejeon"]["totalCase"].intValue
let ulsan = json["ulsan"]["totalCase"].intValue
let sejong = json["sejong"]["totalCase"].intValue
let gyeonggi = json["gyeonggi"]["totalCase"].intValue
let gangwon = json["gangwon"]["totalCase"].intValue
let chungbuk = json["chungbuk"]["totalCase"].intValue
let chungnam = json["chungnam"]["totalCase"].intValue
let jeonbuk = json["jeonbuk"]["totalCase"].intValue
let jeonnam = json["jeonnam"]["totalCase"].intValue
let gyeongbuk = json["gyeongbuk"]["totalCase"].intValue
let gyeongnam = json["gyeongnam"]["totalCase"].intValue
let jeju = json["jeju"]["totalCase"].intValue
let quarantine = json["quarantine"]["totalCase"].intValue
self.KoreaCountryData = KCountryData(seoul: seoul, busan: busan, daegu: daegu, incheon: incheon, gwangju: gwangju, daejeon: daejeon, ulsan: ulsan, sejong: sejong, gyeonggi: gyeonggi, gangwon: gangwon, chungbuk: chungbuk, chungnam: chungnam, jeonbuk: jeonbuk, jeonnam: jeonnam, gyeongbuk: gyeongbuk, gyeongnam: gyeongnam, jeju: jeju, quarantine: quarantine)
} else {
self.KoreaCountryData = KcountrytestDate
}
}
struct KCountryData {
let seoul: Int
let busan: Int
let daegu: Int
let incheon: Int
let gwangju: Int
let daejeon: Int
let ulsan: Int
let sejong: Int
let gyeonggi: Int
let gangwon: Int
let chungbuk: Int
let chungnam: Int
let jeonbuk: Int
let jeonnam: Int
let gyeongbuk: Int
let gyeongnam: Int
let jeju: Int
let quarantine: Int
}
I'm using Swiftui to create a covid app.
I got a json form from api and I was pasing
"totalCase": "26,732",
"recovered": "24,395", The json format contains a comma, so it is not accurate output.
ex) totalCase: 26, recovered: 24 I want to erase the comma and express all the numbers.
{
"resultCode": "0",
"resultMessage": "정상 처리되었습니다.",
"korea": {
"countryName": "합계",
"newCase": "97",
"totalCase": "26,732",
"recovered": "24,395",
"death": "468",
"percentage": "51.56",
"newCcase": "79",
"newFcase": "18"
},
"seoul": {
"countryName": "서울",
"newCase": "25",
"totalCase": "6,081",
"recovered": "5,500",
"death": "78",
"percentage": "62.47",
"newCcase": "20",
"newFcase": "5"
}
You can get your string remove the commas (thousand separator) and the enclosing quotes as well. One you have done that you can decode your json string and treat the properties as integers. You can still use SwiftyJSON if you would like to or using Codable as shown bellow:
struct Root: Codable {
let resultCode: Int
let resultMessage: String
let korea, seoul: Country
}
struct Country: Codable {
let countryName: String
let newCase, totalCase, recovered, death: Int
let percentage: Double
let newCcase, newFcase: Int
}
if let result = response.data {
do {
let cleaned = result.replacingOccurrences(of: ",(?=\\d{3})", with: "", options: .regularExpression)
.replacingOccurrences(of: #"\"(\d+)\""#, with: "$1", options: .regularExpression)
.replacingOccurrences(of: #"\"(\d{1,2}.\d{1,2})\""#, with: "$1", options: .regularExpression)
print(cleaned) // { "resultCode": 0, "resultMessage": "정상 처리되었습니다.", "korea": { "countryName": "합계", "newCase": 97, "totalCase": 26732, "recovered": 24395, "death": 468, "percentage": 51.56, "newCcase": 79, "newFcase": 18 }, "seoul": { "countryName": "서울", "newCase": 25, "totalCase": 6081, "recovered": 5500, "death": 78, "percentage": 62.47, "newCcase": 20, "newFcase": 5 }}
let root = try JSONDecoder().decode(Root.self, from: Data(cleaned.utf8))
print(root) // "Root(resultCode: 0, resultMessage: "정상 처리되었습니다.", korea: __lldb_expr_11.Country(countryName: "합계", newCase: 97, totalCase: 26732, recovered: 24395, death: 468, percentage: 51.56, newCcase: 79, newFcase: 18), seoul: __lldb_expr_11.Country(countryName: "서울", newCase: 25, totalCase: 6081, recovered: 5500, death: 78, percentage: 62.47, newCcase: 20, newFcase: 5))\n"
} catch {
print(error)
}
}
You can use a number formatter to convert this format to an int
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "en_US")
numberFormatter.numberStyle = .decimal
Example
let string = "6,081"
if let value = numberFormatter.number(from: string)?.intValue {
print(value)
}
I am very new to using JSON and I'm trying to figure it out the best solution to parse the below data into my app using swift. So far I've been successfully creating structures to parse the data into my apps but with this API I'm getting a bit confused.
The path of the value i'm looking returns: 0.price
Below the JSON i'm working on - i need a structure to retrieve the 'price' value - i understand it is a swift dictionary nested into an array but I really don't know how to create this structure to get the 0.price value.
[
{
"id": "BTC",
"currency": "BTC",
"symbol": "BTC",
"name": "Bitcoin",
"logo_url": "https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/btc.svg",
"price": "9397.86203481",
"price_date": "2020-07-09T00:00:00Z",
"price_timestamp": "2020-07-09T05:10:00Z",
"circulating_supply": "18427462",
"max_supply": "21000000",
"market_cap": "173178745528",
"rank": "1",
"high": "19343.01808710",
"high_timestamp": "2017-12-16T00:00:00Z",
"1d": {
"volume": "19780482959.23",
"price_change": "55.47211721",
"price_change_pct": "0.0059",
"volume_change": "1974758604.83",
"volume_change_pct": "0.1109",
"market_cap_change": "1030908096.99",
"market_cap_change_pct": "0.0060"
},
"30d": {
"volume": "663105718213.57",
"price_change": "-472.08068928",
"price_change_pct": "-0.0478",
"volume_change": "-451402235448.26",
"volume_change_pct": "-0.4050",
"market_cap_change": "-8416909381.05",
"market_cap_change_pct": "-0.0463"
}
}
]
Thanks,
Max
Use Codable to parse the above JSON response.
Codable Models:
Option-1 : If you want to parse all data
struct Response: Codable {
let id, currency, symbol, name: String
let logoUrl: String
let price: String
let priceDate, priceTimestamp: String
let circulatingSupply, maxSupply, marketCap, rank: String
let high: String
let highTimestamp: String
let the1D, the30D: The1_D
enum CodingKeys: String, CodingKey {
case id, currency, symbol, name, logoUrl, price, priceDate, priceTimestamp, circulatingSupply, maxSupply, marketCap, rank, high, highTimestamp
case the1D = "1d"
case the30D = "30d"
}
}
struct The1_D: Codable {
let volume, priceChange, priceChangePct, volumeChange: String
let volumeChangePct, marketCapChange, marketCapChangePct: String
}
Option-2: In case you just need the price value and ignore everything else,
struct Response: Codable {
let price: String
}
Parse the JSON data like so,
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try decoder.decode([Response].self, from: data)
let price = response.first?.price //Get the value of price here..
} catch {
print(error)
}
When I try to decode this json:
"polls": [
{
"title": "title",
"date": "date",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
},
}
}, /*<and about 76 of this>*/ ]
with this Codable:
struct poll: Codable {
var stats: stats
var title: String?
var date: String?
var summary: String?
struct stats: Codable {
var total: total
struct total: Codable {
var nays: Int
var yays: Int
var nas: Int
var abstentions: Int
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I get the following error
keyNotFound(CodingKeys(stringValue: "dagegen gestimmt", intValue: nil)(if you need the full error text tell me)
I tried some of the answer from similar questions but nothing worked.
You apparently have occurrences of total where dagegen gestimmt is absent. So, make that an Optional, e.g. Int?:
struct Poll: Codable {
let stats: Stats
let title: String?
let date: Date?
let summary: String?
struct Stats: Codable {
let total: Total
struct Total: Codable {
let nays: Int?
let yays: Int?
let nas: Int?
let abstentions: Int?
private enum CodingKeys: String, CodingKey {
case yays = "dafür gestimmt"
case nays = "dagegen gestimmt"
case nas = "nicht beteiligt"
case abstentions = "enthalten"
}
}
}
}
I’d also suggest the following, also reflected in the above:
Start type names (e.g. your struct names) with uppercase letter;
Use let instead of var as we should always favor immutability unless you really are going to be changing these values within this struct; and
If your date is in a consistent format, I’d suggest making the date a Date type, and then you can supply the JSONDecoder a dateDecodingStrategy that matches (see sample below).
For example:
let data = """
{
"polls": [
{
"title": "New Years Poll",
"date": "2019-01-01",
"summary": "summary",
"stats": {
"total": {
"dagegen gestimmt": 139,
"nicht beteiligt": 114,
"dafür gestimmt": 454,
"enthalten": 2
}
}
},{
"title": "Caesar's Last Poll",
"date": "2019-03-15",
"summary": "summary2",
"stats": {
"total": {
"dafür gestimmt": 42
}
}
}
]
}
""".data(using: .utf8)!
struct Response: Codable {
let polls: [Poll]
}
do {
let decoderDateFormatter = DateFormatter()
decoderDateFormatter.dateFormat = "yyyy-MM-dd"
decoderDateFormatter.locale = Locale(identifier: "en_US_POSIX")
let userInterfaceDateFormatter = DateFormatter()
userInterfaceDateFormatter.dateStyle = .long
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(decoderDateFormatter)
let response = try decoder.decode(Response.self, from: data)
let polls = response.polls
for poll in polls {
print(poll.title ?? "No title")
print(" date:", poll.date.map { userInterfaceDateFormatter.string(from: $0) } ?? "No date supplied")
print(" yays:", poll.stats.total.yays ?? 0)
print(" nays:", poll.stats.total.nays ?? 0)
}
} catch {
print(error)
}
That produces:
New Years Poll
date: January 1, 2019
yays: 454
nays: 139
Caesar's Last Poll
date: March 15, 2019
yays: 42
nays: 0
Set your model as per following format. Also check datatype as per your response.
struct PollsModel:Codable{
var polls : [PollsArrayModel]
enum CodingKeys:String, CodingKey{
case polls
}
struct PollsArrayModel:Codable{
var title : String?
var date : String?
var summary : String?
var stats : PollsStatsModel
enum CodingKeys:String, CodingKey{
case title
case date
case summary
case stats
}
struct PollsStatsModel:Codable{
var total : PollsStatsTotalModel
enum CodingKeys:String, CodingKey{
case total
}
struct PollsStatsTotalModel:Codable{
var dagegen_gestimmt : Int?
var nicht_beteiligt : Int?
var dafür_gestimmt : Int?
var enthalten : Int?
enum CodingKeys:String, CodingKey{
case dagegen_gestimmt = "dagegen gestimmt"
case nicht_beteiligt = "nicht beteiligt"
case dafür_gestimmt = "dafür gestimmt"
case enthalten = "enthalten"
}
}
}
}
}
I'm doing my own decked out "hello world" and I've decided to handle an JSON response from an URL.
I've read a lot of posts on how do handle JSON with Codable structs but I can't figure out how to create the Codable structs for this nested JSON.
{
"acumulado": "sim",
"cidades": [],
"data": "2018-05-02",
"ganhadores": [
0,
91,
6675
],
"numero": 2036,
"proximo_data": "2018-05-05",
"proximo_estimativa": 22000000,
"rateio": [
0,
21948.81,
427.46
],
"sorteio": [
7,
8,
19,
23,
27,
58
],
"valor_acumulado": 18189847.7
}
This is a sample of a JSON returned from the API, how do I create a Codable struct to handle it?
Ps: I know that there are a lot of posts out there that cover this, but I can't figure out how to make it work with my sample.
First of all, your JSON is not really nested:
struct MyObject: Codable {
let acumulado: String
let cidades: [String] // ?? hard to know what data type is there
let numero: Int
let proximo_data: String
let proximo_estimativa: Int
let rateio: [Double]
let sorteio: [Int]
let valor_acumulado: Double
}
Every value that can be omitted in the dictionary should be an optional (e.g. let proximo_data: String?)
You can also use CodingKeys to rename the variables:
struct MyObject: Codable {
let acumulado: String
let cidades: [String] // ?? hard to know what data type is there
let numero: Int
let proximoData: String
let proximoEstimativa: Int
let rateio: [Double]
let sorteio: [Int]
let valorAcumulado: Double
enum CodingKeys: String, CodingKey {
case acumulado
case cidades
case proximoData = "proximo_data"
case proximoEstimativa = "proximo_estimativa"
case rateio
case sorteio
case valorAcumulado
}
}