How can I decode timestamp to Date from JSON?
I get my date from the server as Json like this:
{
"date": "2610-02-16T03:16:15.143Z"
}
and im trying to build a Date class from it:
class Message : Decodable {
var date: Date
}
its not working as Expected I am getting this error:
Failed to fetch messages: typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "date", intValue: nil)], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
When decoding date info like this, you need to use a custom dateDecodingStrategy, and set the date parser's timezone and locale:
let data = """
{
"date": "2610-02-16T03:16:15.143Z"
}
""".data(using: .utf8)!
struct Message: Codable {
let date: Date
}
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "GMT")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(formatter)
do {
let message = try decoder.decode(Message.self, from: data)
print(message.date)
} catch {
print(erroor)
}
Related
I am trying to decode a JSON format from CalorieNinja API but it appears that the equals signs in their jsons are throwing my code off. Here is my code for decoding the JSON file:
let dataTask = session.dataTask(with: request) { (data, response, error) in
//check errors
if error == nil && data != nil {
let decoder = JSONDecoder()
do{
let result = try decoder.decode(Result.self, from: data!)
print(result)
}catch{
print("there was an error")
print(error)
}
}
}
Here are my structs:
struct FoodItem: Codable {
var name: String?
var calories: String?
}
struct Result: Codable {
var items: [FoodItem]?
}
Here is the JSON format that gets returned from CalorieNinjas(this is just an example this is not the output of my code):
{
items = (
{
calories = "18.2";
"carbohydrates_total_g" = "3.9";
"cholesterol_mg" = 0;
"fat_saturated_g" = 0;
"fat_total_g" = "0.2";
"fiber_g" = "1.2";
name = tomato;
"potassium_mg" = 23;
"protein_g" = "0.9";
"serving_size_g" = 100;
"sodium_mg" = 4;
"sugar_g" = "2.6";
}
);
}
And lastly here is the error if it helps at all:
typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "items", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "calories", intValue: nil)], debugDescription: "Expected to decode String but found a number instead.", underlyingError: nil))
You can check CalorieNinja API again. I think the calories field should be Double? due to their document.
I am trying to convert JSON file to swift object, but I could not figure it out why it returns nill. as you use from code, I have two objects one Stakeholder and other MHSGroup I created a struct to hold data colled
StakeholderMHSGroup
struct StakeholderMHSGroup : Codable {
var stakeholders:[Stakeholder]?
var mhsGroups:[MhsGroup]?
}
main swift
let jsonStr = "{\"stakeholders\":[{\"id\":\"d95bb600-f63b-4ec7-bd2f-d14bdf1c145f\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"emailAddress\":\"John.Doe#mail.com\",\"salutation\":\"Ms\"},{\"id\":\"d95bb600-f63b-4ec7-bd2f-d14bdf1c145d\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"emailAddress\":\"John.Doe#mail.com\",\"salutation\":\"Ms\"},{\"id\":\"d95bb600-f63b-4ec7-bd2f-d14bdf1c545f\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"emailAddress\":\"John.Doe#mail.com\",\"salutation\":\"Ms\"},{\"id\":\"d95bb600-f63b-4ec7-bd2f-d14baf1c145f\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"emailAddress\":\"John.Doe#mail.com\",\"salutation\":\"Ms\"}],\"mhsGroups\":[{\"id\":\"495919eb-dcbc-48c5-99f5-48f6790b79e3\",\"name\":\"Group1\",\"membersCount\":5,\"createdDate\":\"2012-04-23T18:25:43.511Z\"},{\"id\":\"495919eb-dcbc-48c5-99f5-48f6290b79e3\",\"name\":\"Group1\",\"membersCount\":5,\"createdDate\":\"2012-04-23T18:25:43.511Z\"},{\"id\":\"495919eb-dcbc-48c5-99f5-48f6790b79e4\",\"name\":\"Group1\",\"membersCount\":5,\"createdDate\":\"2012-04-23T18:25:43.511Z\"},{\"id\":\"495919eb-dcbc-48c5-99f5-48f6790b79e2\",\"name\":\"Group1\",\"membersCount\":5,\"createdDate\":\"2012-04-23T18:25:43.511Z\"}]}"
"{"stakeholders":[{"id":"d95bb600-f63b-4ec7-bd2f-d14bdf1c145f","firstName":"John","lastName":"Doe","emailAddress":"John.Doe#mail.com","salutation":"Ms"},{"id":"d95bb600-f63b-4ec7-bd2f-d14bdf1c145d","firstName":"John","lastName":"Doe","emailAddress":"John.Doe#mail.com","salutation":"Ms"},{"id":"d95bb600-f63b-4ec7-bd2f-d14bdf1c545f","firstName":"John","lastName":"Doe","emailAddress":"John.Doe#mail.com","salutation":"Ms"},{"id":"d95bb600-f63b-4ec7-bd2f-d14baf1c145f","firstName":"John","lastName":"Doe","emailAddress":"John.Doe#mail.com","salutation":"Ms"}],"mhsGroups":[{"id":"495919eb-dcbc-48c5-99f5-48f6790b79e3","name":"Group1","membersCount":5,"createdDate":"2012-04-23T18:25:43.511Z"},{"id":"495919eb-dcbc-48c5-99f5-48f6290b79e3","name":"Group1","membersCount":5,"createdDate":"2012-04-23T18:25:43.511Z"},{"id":"495919eb-dcbc-48c5-99f5-48f6790b79e4","name":"Group1","membersCount":5,"createdDate":"2012-04-23T18:25:43.511Z"},{"id":"495919eb-dcbc-48c5-99f5-48f6790b79e2","name":"Group1","membersCount":5,"createdDate":"2012-04-23T18:25:43.511Z"}]}"
var data:Data?
data = jsonStr.data(using: .utf8)!
let userGroup = try! JSONDecoder().decode(StakeholderMHSGroup.self, from: data!)
print(userGroup)
update debug error
hread 1: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.typeMismatch(Swift.Double,
Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue:
"mhsGroups", intValue: nil), _JSONKey(stringValue: "Index 0",
intValue: 0), CodingKeys(stringValue: "createdDate", intValue: nil)],
debugDescription: "Expected to decode Double but found a string/data
instead.", underlyingError: nil))
Judging from the error message, you seem to have declared createdDate as a Double, but in the JSON, the value associated with createdDate is a string.
You should declare createDate as a Date (you could always use createdDate.timeIntervalSince1970 if you want a Double), and set dateDecodingStrategy of the decoder to iso8601, because your dates seem to be in that format:
let decoder = JSONDecoer()
decoder.dateDecodingStrategy = .iso8601
// you shouldn't really use "try!" here...
let userGroup = try! decoder.decode(StakeholderMHSGroup.self, from: data!)
This question already has answers here:
How do I use custom keys with Swift 4's Decodable protocol?
(4 answers)
Closed 3 years ago.
I'm decoding a JSON response in my Swift App, and the code used to work till it decided to stop working.
this is my json reposnse
{
"foods": [
{
"food_name": "Milk Chocolate",
"brand_name": "Snickers",
"serving_weight_grams": 41.7,
"nf_calories": 212.3,
"nf_total_fat": 11.6,
"nf_saturated_fat": 4,
"nf_total_carbohydrate": 22.7,
"nf_protein": 3.9
}
]
}
And this is the code to decode my json
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
print(String(data: data, encoding: .utf8)!)
do {
//Decode dataResponse received from a network request
let decoder = JSONDecoder()
let foods = try decoder.decode(JSONFoods.self, from: data) //Decode JSON Response Data
self.jsonfood = foods.JSONFood[0]
print(self.jsonfood!)
} catch let parsingError {
print("Error", parsingError)
}
}
task.resume()
And my Structs are
struct JSONFoods: Decodable {
var JSONFood: [JSONFood]
}
struct JSONFood: Decodable{
var food_name: String
var brand_name: String
var nf_calories: Int
var nf_protein: Int
var nf_total_fat: Int
var nf_total_carbohydrate: Int
var serving_weight_grams: Int
}
And the error message I get is this
keyNotFound(CodingKeys(stringValue: "JSONFood", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "JSONFood", intValue: nil) ("JSONFood").", underlyingError: nil))
And if i get replace decode(JSONFoods.self, from: data) with decode(JSONFood.self, from: data)
I get this error message
keyNotFound(CodingKeys(stringValue: "food_name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "food_name", intValue: nil) ("food_name").", underlyingError: nil))
I searched everywhere with no luck, any help is very appreciated
You need
struct Root: Codable {
let foods: [Food]
}
struct Food: Codable {
let foodName: String?
let brandName: String
let servingWeightGrams, nfCalories, nfTotalFat: Double
let nfSaturatedFat: Int
let nfTotalCarbohydrate, nfProtein: Double
enum CodingKeys: String, CodingKey {
case foodName = "food_name"
case brandName = "brand_name"
case servingWeightGrams = "serving_weight_grams"
case nfCalories = "nf_calories"
case nfTotalFat = "nf_total_fat"
case nfSaturatedFat = "nf_saturated_fat"
case nfTotalCarbohydrate = "nf_total_carbohydrate"
case nfProtein = "nf_protein"
}
}
First : you make JSONFood while it should be foods
Second :food_name doesn't exist in current json root so this will fail
let foods = try decoder.decode(JSONFoods.self, from: data) //Decode JSON Response Data
In case to take advantage of convertFromSnakeCase
let str = """
{"foods":[{"food_name":"Milk Chocolate","brand_name":"Snickers","serving_weight_grams":41.7,"nf_calories":212.3,"nf_total_fat":11.6,"nf_saturated_fat":4,"nf_total_carbohydrate":22.7,"nf_protein":3.9}]}
"""
do {
let res = JSONDecoder()
res.keyDecodingStrategy = .convertFromSnakeCase
let ss = try res.decode(Root.self, from:Data(str.utf8))
print(ss)
}
catch {
print(error)
}
struct Root: Codable {
let foods: [Food]
}
struct Food: Codable {
let foodName: String?
let brandName: String
let servingWeightGrams, nfCalories, nfTotalFat: Double
let nfSaturatedFat: Int
let nfTotalCarbohydrate, nfProtein: Double
}
Intent:
Receive cryptocurrency price data via Coinmarketcap API, decode it into custom structs in SWIFT and potentially store that data in a database (either CoreData or SQLite).
Context:
I am receiving the following error on JSONDecoder().decode:
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
Questions:
How to properly interpret that error? What am I decoding wrong?
Is the data I am receiving correctly formatted? Doesn't look like
proper JSON.
The code:
import UIKit
import PlaygroundSupport
// Defining structures
struct RootObject: Decodable {
let status: [String: StatusObject?]
let data: DataObject?
}
struct StatusObject: Decodable {
let credit_count: Int?
let elapsed: Int?
let error_code: Int?
let timestamp: String?
}
struct DataObject: Decodable {
let amount: Int?
let id: Int?
let last_updated: String?
let name: String?
let quote: [QuoteObject]?
let symbol: String?
}
struct QuoteObject: Decodable {
let usd: String?
}
struct usdObject: Decodable {
let last_updated: String?
let price: String?
}
//Configuring URLSession
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["X-CMC_PRO_API_KEY": "<removed>",
"Accept": "application/json",
"Accept-Encoding": "deflate, gzip"]
let session = URLSession(configuration: config)
let url = URL(string: "https://sandbox-api.coinmarketcap.com/v1/tools/price-conversion?convert=USD&amount=1&symbol=BTC")!
//Making and handling a request
let task = session.dataTask(with: url) { data, response, error in
guard error == nil else {
print ("error: \(error!)")
return
}
guard let content = data else {
print("No data")
return
}
//Serializing and displaying the received data
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any]
else {
print("Not containing JSON")
return
}
print(json)
//Trying to decode
do {
let prices = try JSONDecoder().decode(RootObject.self, from: data!)
print(prices)
} catch let decodeError {
print("Error serializing json:", decodeError)
}
}
task.resume()
The data response and the error:
["status": {
"credit_count" = 1;
elapsed = 6;
"error_code" = 0;
"error_message" = "<null>";
timestamp = "2019-02-16T11:10:22.147Z";
}, "data": {
amount = 1;
id = 1;
"last_updated" = "2018-12-22T06:08:23.000Z";
name = Bitcoin;
quote = {
USD = {
"last_updated" = "2018-12-22T06:08:23.000Z";
price = "3881.88864625";
};
};
symbol = BTC;
}]
Error serializing json: typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "status", intValue: nil), _DictionaryCodingKey(stringValue: "credit_count", intValue: nil)], debugDescription: "Expected to decode Dictionary<String, Any> but found a number instead.", underlyingError: nil))
Edit 1:
Properly serialized JSON:
{
"status": {
"timestamp": "2019-02-16T18:54:05.499Z",
"error_code": 0,
"error_message": null,
"elapsed": 6,
"credit_count": 1
},
"data": {
"id": 1,
"symbol": "BTC",
"name": "Bitcoin",
"amount": 1,
"last_updated": "2018-12-22T06:08:23.000Z",
"quote": {
"USD": {
"price": 3881.88864625,
"last_updated": "2018-12-22T06:08:23.000Z"
}
}
}
}
There are a lot of issues in the structs.
The main issue is that the value for data is a dictionary which is decoded into a struct rather than into another dictionary. Other issues are that the type of id is String and price is Double.
APIs like Coinmarketcap send reliable data so don't declare everything as optional. Remove the question marks.
The structs below are able to decode the JSON. The quotes are decoded into a dictionary because the keys change. Add the .convertFromSnakeCase key decoding strategy to get camelCased keys. The dates are decoded as Date by adding an appropriate date decoding strategy.
I removed all those redundant ...Object occurrences except DataObject because the Data struct already exists.
struct Root: Decodable {
let status: Status
let data: DataObject
}
struct Status: Decodable {
let creditCount: Int
let elapsed: Int
let errorCode: Int
let timestamp: Date
}
struct DataObject: Decodable {
let amount: Int
let id: String
let lastUpdated: Date
let name: String
let quote: [String:Quote]
let symbol: String
}
struct Quote: Decodable {
let lastUpdated: Date
let price: Double
}
//Trying to decode
do {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .formatted(dateFormatter)
let result = try decoder.decode(Root.self, from: data!)
let quotes = result.data.quote
for (symbol, quote) in quotes {
print(symbol, quote.price)
}
} catch {
print(error)
}
Im new at this and having some trouble getting my head around how this all works.
I have this struct:
struct EventDetail:Decodable {
let EventName: String
let EventInformation: String
let EventStartDate: Date
let EventEndDate: Date
}
And this func to download the json:
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://someurl.php")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.events = try JSONDecoder().decode([EventDetail].self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print(error)
}
}
}.resume()
}
}
This is the JSON error I get:
JSON Error
typeMismatch(Swift.Double, Swift.DecodingError.Context(codingPath: [Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)), Cosplay_Life.EventDetail.(CodingKeys in _52013DB7ECF3BE1EBFBF83BE6BA8F9E9).EventStartDate], debugDescription: "Expected to decode Double but found a string/data instead.", underlyingError: nil))
This all seems to be working if the EventStartDate and EventEndDate is a String, but I do wish to have it as a Date because I will sort data later using the date field. The value that gets downloaded is in the format "yyyy-MM-dd" eks: "2017-02-25" What am I doing wrong?
A date is a Double in that it is the number of ms since the epoch.
Try this:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
// Use format appropriate to your JSON String. This is for ISO-8601
// You MUST account for the milliseconds even if you don't want them
// or it won't parse properly
dateFormatter.dateFormat = "yy-MM-dd'T'HH:mm:ss.SSS"
eventDetail.EventStartDate = dateFormatter.date(from: jsonEventStartDateField)!
eventDetail.EventEndDate = dateFormatter.date(from: jsonEventEndDateField)!