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"
}
}
}
}
}
Related
I have tried following a variety of tutorials, and I am unable to progress on getting data from this API. I did manage to succeed on a simpler JSON ], but this one is eating up my time.
First, the JSON:
{
"object": {
"array": [
{
"id": 48,
"name": "Job No.# 48",
"description": "blah",
"start_at": "2021-03-05T13:15:00.000+11:00",
"end_at": "2021-03-05T14:15:00.000+11:00",
"map_address": "blah road"
},
{
"id": 56,
"name": "Job No.# 56",
"description": "Do it",
"start_at": "2021-06-22T11:30:00.000+10:00",
"end_at": "2021-06-22T13:30:00.000+10:00",
"map_address": " blah"
}
],
"person": {
"id": 52,
"first_name": "Bob",
"last_name": "Newby",
"mobile": "0401111111",
"email": "bob#mail.com"
}
}
}
And now my attempt at decoding it:
struct api_data: Codable {
let object : Object
}
struct Object: Codable {
let array : [array]
let person : Person
}
struct array: Codable, Identifiable {
let id : Int?
let start_at, end_at : Date?
let duration : Float?
let cancellation_type : String?
let name, description, address, city, postcode, state : String?
}
struct Person: Codable, Identifiable {
let id : Int?
let first_name, last_name, mobile, email : String?
}
class FetchShifts: ObservableObject {
#Published var shifts = [Shifts]()
init() {
let url = URL(string: "realURLhiddenForPrivacy")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("myToken", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) {(data, response, error) in
do {
if let array_data = data {
let array_data = try JSONDecoder().decode([array].self, from: array_data)
DispatchQueue.main.async {
self.array = array_data
}
} else {
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
And how I attempt to present it:
#ObservedObject var fetch = FetchArray()
var body: some View {
VStack {
List(array.shifts) { shft in
VStack(alignment: .leading) {
Text(shft.name!)
}
}
}
}
}
}
Any help is appreciated, not sure where it is I go wrong here, been at it for 5-7 hours going through tutorials.
I always recommend using app.quicktype.io to generate models from JSON if you're unfamiliar with it. Here's what it yields:
// MARK: - Welcome
struct Welcome: Codable {
let status: String
let payload: Payload
}
// MARK: - Payload
struct Payload: Codable {
let shifts: [Shift]
let worker: Worker
}
// MARK: - Shift
struct Shift: Codable, Identifiable {
let id: Int
let name, shiftDescription, startAt, endAt: String
let mapAddress: String
enum CodingKeys: String, CodingKey {
case id, name
case shiftDescription = "description"
case startAt = "start_at"
case endAt = "end_at"
case mapAddress = "map_address"
}
}
// MARK: - Worker
struct Worker: Codable {
let id: Int
let firstName, lastName, mobile, email: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
case mobile, email
}
}
Then, to decode, you'd do:
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shift_data)
let shifts = decoded.payload.shifts
} catch {
print(error)
}
Note that in Swift, it's common practice to use camel case for naming, not snake case, so you'll see that CodingKeys does some conversion for that (there are automated ways of doing this as well).
Update, based on comments:
Your code would be:
if let shiftData = data {
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shiftData)
DispatchQueue.main.async {
self.shifts = decoded.payload.shifts
}
} catch {
print(error)
}
}
Instead of defining custom keys, you can automatically use
keyDecodingStrategy = .convertFromSnakeCase for your JSON decoder, and could specify custom date format or even throw a custom error in your decoder implementation.
struct Worker: Codable {
let id: Int
let firstName: String?
let lastName: String?
let mobile: String?
let email: String?
}
struct Shift: Codable, Identifiable {
let id: Int
let name: String?
let description: String?
let startAt: Date?
let endAt: Date?
let mapAddress: String?
}
struct Payload: Codable {
let shifts: [Shift]?
let worker: Worker?
}
struct Response: Codable {
let status: String
let payload: Payload?
}
class MyCustomDecoder: JSONDecoder {
override init() {
super.init()
self.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
self.dateDecodingStrategy = .formatted(dateFormatter)
}
}
// Usage
if let data = json.data(using: .utf8) {
let response = try MyCustomDecoder().decode(Response.self, from: data)
print(response)
}
having looked at several posts I didn't find what I was looking for. I hope this post will help me.
I use the api of CoinDesk, what I'm trying to do now is to retrieve in the answer all the codes (EUR, USD, GBP) but I can't get all the assets.
{
"time": {
"updated": "Feb 20, 2021 19:48:00 UTC",
"updatedISO": "2021-02-20T19:48:00+00:00",
"updateduk": "Feb 20, 2021 at 19:48 GMT"
},
"disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
"chartName": "Bitcoin",
"bpi": {
"USD": {
"code": "USD",
"symbol": "$",
"rate": "57,014.5954",
"description": "United States Dollar",
"rate_float": 57014.5954
},
"GBP": {
"code": "GBP",
"symbol": "£",
"rate": "40,681.1111",
"description": "British Pound Sterling",
"rate_float": 40681.1111
},
"EUR": {
"code": "EUR",
"symbol": "€",
"rate": "47,048.5582",
"description": "Euro",
"rate_float": 47048.5582
}
}
}
here's how I'm going to get the data
public class NetworkManager {
static public func fetchBPI() {
let url = "https://api.coindesk.com/v1/bpi/currentprice.json"
Alamofire.request(url).responseJSON { response in
switch response.result {
case .success:
print("✅ Success ✅")
if let json = response.data {
do {
let data = try JSON(data: json)
print(data)
let context = PersistentContainer.context
let entity = NSEntityDescription.entity(forEntityName: "BPI", in: context)
let newObject = NSManagedObject(entity: entity!, insertInto: context)
//Date Formatter
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let date = dateFormatter.date(from: data["time"]["updated"].rawValue as! String)
dateFormatter.timeZone = NSTimeZone.local
let timeStamp = dateFormatter.string(from: date ?? Date())
newObject.setValue(timeStamp, forKey: "time")
newObject.setValue(data["chartName"].rawValue, forKey: "chartName")
newObject.setValue(data["bpi"]["EUR"]["symbol"].rawValue, forKey: "symbol")
newObject.setValue(data["bpi"]["EUR"]["rate"].rawValue, forKey: "rate")
newObject.setValue(data["bpi"]["EUR"]["code"].rawValue, forKey: "code")
do {
try context.save()
print("✅ Data saved ✅")
} catch let error {
print(error)
print("❌ Saving Failed ❌")
}
}
catch {
print("❌ Error ❌")
}
}
case .failure(let error):
print(error)
}
}
}
}
I would like to get in the bpi key all the codes and put them in a list to use them.
The best way to handle the json here in my opinion is to treat the content under "bpi" as a dictionary instead.
struct CoinData: Codable {
let time: Time
let chartName: String
let bpi: [String: BPI]
}
struct BPI: Codable {
let code: String
let rate: Double
enum CodingKeys: String, CodingKey {
case code
case rate = "rate_float"
}
}
struct Time: Codable {
let updated: Date
enum CodingKeys: String, CodingKey {
case updated = "updatedISO"
}
}
I have removed some unnecessary (?) properties and also note that I made updatedISO in Time into a Date if that might be useful since it's so easy to convert it.
To properly decode this use try with a do/catch so you handle errors properly.
Here is an example of that where I also loop over the different currencies/rates
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let result = try decoder.decode(CoinData.self, from: data)
print(result.time.updated)
let coins = result.bpi.values
for coin in coins {
print(coin)
}
} catch {
print(error)
}
Output:
2021-02-20 19:48:00 +0000
BPI(code: "GBP", rate: 40681.1111)
BPI(code: "USD", rate: 57014.5954)
BPI(code: "EUR", rate: 47048.5582)
You should use objects to represent the data from the server. It would be something like this:
struct CoinData: Codable {
let time: Time
let disclaimer, chartName: String
let bpi: BPI
}
struct BPI: Codable {
let usd, gbp, eur: Eur
enum CodingKeys: String, CodingKey {
case usd = "USD"
case gbp = "GBP"
case eur = "EUR"
}
}
struct Eur: Codable {
let code, symbol, rate, eurDescription: String
let rateFloat: Double
enum CodingKeys: String, CodingKey {
case code, symbol, rate
case eurDescription = "description"
case rateFloat = "rate_float"
}
}
struct Time: Codable {
let updated: String
let updatedISO: String
let updateduk: String
}
Than when you download the data you parse it like this:
let coinData = try? JSONDecoder().decode(CoinData.self, from: jsonData)
coinData?.bpi.eur // Access EUR for instance
UPDATE:
Simple demo to demonstrate the parsing using the data you get from your server:
let dataFromServer = "{\"time\":{\"updated\":\"Feb 20, 2021 21:02:00 UTC\",\"updatedISO\":\"2021-02-20T21:02:00+00:00\",\"updateduk\":\"Feb 20, 2021 at 21:02 GMT\"},\"disclaimer\":\"This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org\",\"chartName\":\"Bitcoin\",\"bpi\":{\"USD\":{\"code\":\"USD\",\"symbol\":\"$\",\"rate\":\"56,689.8367\",\"description\":\"United States Dollar\",\"rate_float\":56689.8367},\"GBP\":{\"code\":\"GBP\",\"symbol\":\"£\",\"rate\":\"40,449.3889\",\"description\":\"British Pound Sterling\",\"rate_float\":40449.3889},\"EUR\":{\"code\":\"EUR\",\"symbol\":\"€\",\"rate\":\"46,780.5666\",\"description\":\"Euro\",\"rate_float\":46780.5666}}}"
do {
let json = try JSONDecoder().decode(CoinData.self, from: dataFromServer.data(using: .utf8)!)
print(json.bpi.eur) // Will print EUR object
} catch let error {
print(error)
}
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.
I have been working with the Codable protocol
Here is my JSON file :
{
"Adress":[
],
"Object":[
{
"next-date":"2017-10-30T11:00:00Z",
"text-sample":"Some text",
"image-path":[
"photo1.png",
"photo2.png"
],
"email":"john.doe#test.com",
"id":"27"
},
{
"next-date":"2017-10-30T09:00:00Z",
"text-sample":"Test Test",
"image-path":[
"image1.png"
],
"email":"name.lastename#doe.com",
"id":"28"
}
]
}
I only have to focus on the Object array, and the "image-path" array can contain 0, 1, or 2 strings.
So here is my implementation:
struct Result: Codable {
let Object: [MyObject]
}
struct MyObject: Codable {
let date: String
let text: String
let image: [String]
let email: String
let id: String
enum CodingKeys: String, CodingKey {
case date = "next-date"
case text = "text-sample"
case image = "image-path"
case email = "email"
case id = "id"
}
init() {
self.date = ""
self.text = ""
self.image = []
self.email = ""
self.id = ""
}
}
I call it from my service class after requesting and getting the JSON data this way:
if let data = response.data {
let decoder = JSONDecoder()
let result = try! decoder.decode(Result, from: data)
dump(result.Object)
}
Everything is working except the [String] for the image property
But it can't compile, or I get an "Expected to decode..." error.
How should I handle the nil/no data scenario?
I have made a small change in your MyObject struct, i.e.,
1. Marked all properties as optionals
2. Removed init() (I don't think there is any requirement of init() here.)
3. Use Result.self instead of Result in decoder.decode(...) method
struct MyObject: Codable
{
let date: String?
let text: String?
let image: [String]?
let email: String?
let id: String?
enum CodingKeys: String, CodingKey
{
case date = "next-date"
case text = "text-sample"
case image = "image-path"
case email = "email"
case id = "id"
}
}
To test the above, I have used the below code and it is working fine.
let jsonString = """
{"Adress": [],
"Object": [{"next-date": "2017-10-30T11:00:00Z",
"text-sample": "Some text",
"image-path": ["photo1.png", "photo2.png"],
"email": "john.doe#test.com",
"id": "27"},
{"next-date": "2017-10-30T09:00:00Z",
"text-sample": "Test Test",
"image-path": ["image1.png"],
"email": "name.lastename#doe.com",
"id": "28"}
]
}
"""
if let data = jsonString.data(using: .utf8)
{
let decoder = JSONDecoder()
let result = try? decoder.decode(Result.self, from: data) //Use Result.self here
print(result)
}
This is the result value that I am getting:
I would like to use Swift 4's codable feature with json but some of the keys do not have a set name. Rather there is an array and they are ids like
{
"status": "ok",
"messages": {
"generalMessages": [],
"recordMessages": []
},
"foundRows": 2515989,
"data": {
"181": {
"animalID": "181",
"animalName": "Sophie",
"animalBreed": "Domestic Short Hair / Domestic Short Hair / Mixed (short coat)",
"animalGeneralAge": "Adult",
"animalSex": "Female",
"animalPrimaryBreed": "Domestic Short Hair",
"animalUpdatedDate": "6/26/2015 2:00 PM",
"animalOrgID": "12",
"animalLocationDistance": ""
where you see the 181 ids. Does anyone know how to handle the 181 so I can specify it as a key? The number can be any number and is different for each one.
Would like something like this
struct messages: Codable {
var generalMessages: [String]
var recordMessages: [String]
}
struct data: Codable {
var
}
struct Cat: Codable {
var Status: String
var messages: messages
var foundRows: Int
//var 181: [data] //What do I place here
}
Thanks in advance.
Please check :
struct ResponseData: Codable {
struct Inner : Codable {
var animalID : String
var animalName : String
private enum CodingKeys : String, CodingKey {
case animalID = "animalID"
case animalName = "animalName"
}
}
var Status: String
var foundRows: Int
var data : [String: Inner]
private enum CodingKeys: String, CodingKey {
case Status = "status"
case foundRows = "foundRows"
case data = "data"
}
}
let json = """
{
"status": "ok",
"messages": {
"generalMessages": ["dsfsdf"],
"recordMessages": ["sdfsdf"]
},
"foundRows": 2515989,
"data": {
"181": {
"animalID": "181",
"animalName": "Sophie"
},
"182": {
"animalID": "182",
"animalName": "Sophie"
}
}
}
"""
let data = json.data(using: .utf8)!
let decoder = JSONDecoder()
do {
let jsonData = try decoder.decode(ResponseData.self, from: data)
for (key, value) in jsonData.data {
print(key)
print(value.animalID)
print(value.animalName)
}
}
catch {
print("error:\(error)")
}
I don't think you can declare a number as a variable name. From Apple's doc:
Constant and variable names can’t contain whitespace characters,
mathematical symbols, arrows, private-use (or invalid) Unicode code
points, or line- and box-drawing characters. Nor can they begin with a
number, although numbers may be included elsewhere within the name.
A proper way is to have a property capturing your key.
struct Cat: Codable {
var Status: String
var messages: messages
var foundRows: Int
var key: Int // your key, e.g., 181
}
I would suggest something like this:-
struct ResponseData: Codable {
struct AnimalData: Codable {
var animalId: String
var animalName: String
private enum CodingKeys: String, CodingKey {
case animalId = "animalID"
case animalName = "animalName"
}
}
var status: String
var foundRows: Int
var data: [AnimalData]
private enum CodingKeys: String, CodingKey {
case status = "status"
case foundRows = "foundRows"
case data = "data"
}
struct AnimalIdKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let animalsData = try container.nestedContainer(keyedBy: AnimalIdKey.self, forKey: .data)
self.foundRows = try container.decode(Int.self, forKey: .foundRows)
self.status = try container.decode(String.self, forKey: .status)
self.data = []
for key in animalsData.allKeys {
print(key)
let animalData = try animalsData.decode(AnimalData.self, forKey: key)
self.data.append(animalData)
}
}
}
let string = "{\"status\":\"ok\",\"messages\":{\"generalMessages\":[],\"recordMessages\":[]},\"foundRows\":2515989,\"data\":{\"181\":{\"animalID\":\"181\",\"animalName\":\"Sophie\"}}}"
let jsonData = string.data(using: .utf8)!
let decoder = JSONDecoder()
let parsedData = try? decoder.decode(ResponseData.self, from: jsonData)
This way your decoder initializer itself handles the keys.