I have swiftyJSON added as a package dependancy and have the appropriate import statement.
My JSON file has the following format:
"00AK": {
"icao": "00AK",
"iata": "",
"name": "Lowell Field",
"city": "Anchor Point",
"state": "Alaska",
"country": "US",
"elevation": 450,
"lat": 59.94919968,
"lon": -151.695999146,
"tz": "America\/Anchorage"
},
"00AL": {
"icao": "00AL",
"iata": "",
"name": "Epps Airpark",
"city": "Harvest",
"state": "Alabama",
"country": "US",
"elevation": 820,
"lat": 34.8647994995,
"lon": -86.7703018188,
"tz": "America\/Chicago"
etc. And the JSON file is stored within the app bundle
This is the code I have currently:
import SwiftUI
import SwiftyJSON
let path = Bundle.main.url(forResource: "airports", withExtension: "json")
let jsonString = try? String(contentsOf: path!)
let dataFromString = jsonString!.data
let json = JSON(data: dataFromString)
This gives me the error on the last line of....
"Cannot convert value of type ' (String.Encoding, Bool) -> Data?' to expected argument type 'Data'"
If I 'mouseover' the jsonString var the tooltip shows me the correct string from the file, but I don't understand why the next bit just isn't working.
Your problem seems to be related to convert to Data, this is how I would read the file and convert to Data
let url = Bundle.main.url(forResource: "airports", withExtension: "json")!
let jsonString = try! String(contentsOf: url)
let dataFromString = jsonString.data(using: .utf8)!
To move on here is a simplified example how you can use Codable for your json
struct Airport: Codable {
let icao: String
let name: String
let city: String
let country: String
let elevation: Int
let latitude: Double
let longitude: Double
enum CodingKeys: String, CodingKey {
case latitude = "lat"
case longitude = "lon"
case icao, name, city, country, elevation
}
}
do {
let result = try JSONDecoder().decode([String: Airport].self, from: dataFromString)
let airports = result.values
print(airports)
} catch {
print(error)
}
Note that I did not include all fields here and also made some assumptions what the file content looked like since you have only included a part of it.
Related
I am currently trying to write a universal Request object in my SwiftUI application that aligns with an API I am building. I have written out something simple with the JSON payload that I have as an example and am running into an issue with the decoding of the object. Below is the Request object.
let json = """
{
"object": "bottle",
"has_more": false,
"data": [
{
"id": "5ffa81e7-1d91-43b5-83cc-9ee1ab634c7b",
"name": "14 Hands Hot to Trot White Blend",
"price": "$8.99",
"image": "https://cdn11.bigcommerce.com/s-7a906/images/stencil/1000x1000/products/8186/10996/14-Hands-Hot-to-Trot-White-Blend__56901.1488985626.jpg?c=2",
"sku": "088586004490",
"size": "750ML",
"origination": "USA, Washington",
"varietal": "White Wine",
"information": "14 Hands Hot to Trot White Blend",
"proof": 121.5,
"brand_id": "1",
"rating": 1,
"review_count": 5
}
]
}
"""
struct Request: Decodable {
let object: String
let has_more: Bool
let data: [RequestData]
}
struct Bottle: Decodable {
let id: String
let name: String
let price: String
let image: String
let sku: String
let size: String
let origination: String
let varietal: String
let information: String
let proof: Float
let brand_id: String
let rating: Int
let review_count: Int
}
enum ObjectType: String, Decodable {
case bottle
}
enum BottleData: Decodable {
case bottle(Bottle)
}
struct RequestData: Decodable {
let hasMore: String
let object: ObjectType
let innerObject: BottleData
enum CodingKeys: String, CodingKey {
case hasMore
case object
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.object = try container.decode(ObjectType.self, forKey: .object)
self.hasMore = try container.decode(String.self, forKey: .hasMore)
switch object {
case .bottle:
self.innerObject = .bottle(
try Bottle(from: decoder)
)
}
}
}
let decoder = JSONDecoder()
let requestData = try decoder.decode(Request.self, from: json.data(using: .utf8)!)
for data in requestData.data {
switch data.innerObject {
case .bottle(let bottle):
print(bottle.name)
}
}
I am recieving the following error when trying to test this object.
DecodingError
▿ keyNotFound : 2 elements
- .0 : CodingKeys(stringValue: "object", intValue: nil)
▿ .1 : Context
▿ codingPath : 2 elements
- 0 : CodingKeys(stringValue: "data", intValue: nil)
▿ 1 : _JSONKey(stringValue: "Index 0", intValue: 0)
- stringValue : "Index 0"
▿ intValue : Optional<Int>
- some : 0
- debugDescription : "No value associated with key CodingKeys(stringValue: \"object\", intValue: nil) (\"object\")."
- underlyingError : nil
Given that I only have one object at the moment, I'm a little confused as to why this is not working as I expect (without any errors). Can anybody spot what I may be doing wrong here?
Please read the error carefully.
The CodingPath components indicate the exact location of the error. It's
CodingKeys(stringValue: "object", intValue: nil)
CodingKeys(stringValue: "data", intValue: nil)
_JSONKey(stringValue: "Index 0", intValue: 0)
CodingKeys(stringValue: "object", intValue: nil).
which – translated to a key path – is
object.data[0].object
The actual error message
"No value associated with key CodingKeys(stringValue: "object", intValue: nil) ("object")."
states that there is no key object in the RequestData object which it is true.
I guess it's just a typo. Replace
let data: [RequestData]
with
let data: [Bottle]
and remove RequestData and the associated structs. They are pointless.
Edit:
You can do something like this, but as the JSON contains only one type the solution only decodes this particular JSON
let json = """
{
"object": "bottle",
"has_more": false,
"data": [
{
"id": "5ffa81e7-1d91-43b5-83cc-9ee1ab634c7b",
"name": "14 Hands Hot to Trot White Blend",
"price": "$8.99",
"image": "https://cdn11.bigcommerce.com/s-7a906/images/stencil/1000x1000/products/8186/10996/14-Hands-Hot-to-Trot-White-Blend__56901.1488985626.jpg?c=2",
"sku": "088586004490",
"size": "750ML",
"origination": "USA, Washington",
"varietal": "White Wine",
"information": "14 Hands Hot to Trot White Blend",
"proof": 121.5,
"brand_id": "1",
"rating": 1,
"review_count": 5
}
]
}
"""
enum RequestData {
case bottle([Bottle])
}
enum ObjectType: String, Decodable {
case bottle
}
struct Request: Decodable {
let object: ObjectType
let hasMore: Bool
let data: RequestData
enum CodingKeys: String, CodingKey { case hasMore = "has_more", object, data}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.object = try container.decode(ObjectType.self, forKey: .object)
self.hasMore = try container.decode(Bool.self, forKey: .hasMore)
switch object {
case .bottle:
let bottleData = try container.decode([Bottle].self, forKey: .data)
data = RequestData.bottle(bottleData)
}
}
}
struct Bottle: Decodable {
let id: String
let name: String
let price: String
let image: String
let sku: String
let size: String
let origination: String
let varietal: String
let information: String
let proof: Float
let brand_id: String
let rating: Int
let review_count: Int
}
let decoder = JSONDecoder()
let requestData = try decoder.decode(Request.self, from: Data(json .utf8))
print(requestData)
i'm trying to serialize a json but i have an error could someone give me a guide please i'm doing wrong i'm new to swift[enter image description here][1]
let code = "00001"
let firstName = "Joe"
let lastName = "Doe"
let middleName = "Mc."
let age = 100
let weight = 45
let jsonObject: [String: [String:Any]] = [
"code": code, <---- Cannot convert value of type 'String' to expected dictionary value type '[String : Any]'
"attributeMap": [
"first_name": firstName,
"middle_name": middleName,
"last_name": lastName,
"age": age,
"weight": weight
]
]
if let data = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted),
let str = String(data: data, encoding: .utf8) {
print("===> \(str)")
}
--I would like a result like this JSON format
{
"code": "00001",
"attributeMap": {
"first_name": "Joe",
"middle_name": "Mc",
"last_name": "Doe",
"age": "23",
"weight": "home.zul"
}
}
If it repeatable operation I will prefer to use more "swifty" way. Let's define two structs
Person
struct Person: Encodable {
// Keys
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case middleName = "middle_name"
case lastName = "last_name"
case age
case weight
}
// Properties
let firstName: String
let middleName: String
let lastName: String
let age: Int
let weight: Double
}
The next one
Entity
struct Entity: Encodable {
// Keys
enum CodingKeys: String, CodingKey {
case code
case person = "attributeMap"
}
// Properties
let code: String
let person: Person
}
Usage
let person = Person(
firstName: "Joe",
middleName: "Mc.",
lastName: "Doe",
age: 100,
weight: 45
)
let entity = Entity(
code: "200",
person: person
)
if
let data = try? JSONEncoder().encode(entity),
let json = String(data: data, encoding: .utf8)
{
// Do something
}
Result
{"attributeMap":{"age":100,"last_name":"Doe","middle_name":"Mc.","weight":45,"first_name":"Joe"},"code":"200"}
If I have ArticleItem struct like this:
public struct ArticleItem: Equatable {
public let id: UUID
public let description: String?
public let location: String?
public let thumbnailURL: URL
public let created: Date
public init(id: UUID, description: String?, location: String?, thumbnailURL: URL, created: Date) {
self.id = id
self.description = description
self.location = location
self.thumbnailURL = thumbnailURL
self.created = created
}
}
But from back-end we would receive such info:
{
"id": "a UUID",
"description": "a description",
"location": "a location",
"image": "https://a-image.url",
"created_date": "2020-05-09",
}
How can I test the correctness of Date ?
In the test I have something like this:
func test_load_deliversItemsOn200HTTPResponseWithJSONItems(){
let (sut, client) = makeSUT()
let item1 = ArticleItem(id: UUID(), description: nil, location: nil, thumbnailURL: URL(string: "http://a-url.com/")!, created: Date())
let item1JSON = [
"id": item1.id.uuidString,
"image": item1.imageURL.absoluteString,
"created_date": ??? // what should be here
]
let items = [
"items": [item1JSON]
]
expect(sut: sut, toCompleteWithResult: .success([item1])) {
let itemsJSON = try! JSONSerialization.data(withJSONObject: items)
client.complete(withStatusCode: 200, data: itemsJSON)
}
}
I've heard about using iso8601 when decoding but still don't know how to do it.
Please help me. Thanks
You need to tell the decoder what dateDecoderStrategy your model or json follows.
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(TheFormatThatTheDateHas)
and then
decoder.decode........
for the format, google the date plus with format.
I'm new to programming so apologies if the fix is a simple one. I'm trying to get the JSON data from the Alamofire request to show up not as an optional in the console.
I've already tried response.data which does give me the data as an optional but I don't know how to unwrap that optional in this call. I've searched and have seen that result.value might be closer to what I need. Below is what I have so far. This results in a "Cannot convert value of type '[String : Any]' to expected argument type 'Data'" error.
JSON File-->
"forecasts": [
{
"dateLabel": "今日",
"telop": "晴時々曇",
"date": "2019-08-16",
"temperature": {
"min": null,
"max": null
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/2.gif",
"title": "晴時々曇",
"height": 31
}
},
{
"dateLabel": "明日",
"telop": "晴時々曇",
"date": "2019-08-17",
"temperature": {
"min": {
"celsius": "27",
"fahrenheit": "80.6"
},
"max": {
"celsius": "37",
"fahrenheit": "98.6"
}
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/2.gif",
"title": "晴時々曇",
"height": 31
}
},
{
"dateLabel": "明後日",
"telop": "晴時々曇",
"date": "2019-08-18",
"temperature": {
"min": null,
"max": null
},
"image": {
"width": 50,
"url": "http://weather.livedoor.com/img/icon/2.gif",
"title": "晴時々曇",
"height": 31
}
}
],
"location": {
"city": "東京",
"area": "関東",
"prefecture": "東京都"
},
"publicTime": "2019-08-16T17:00:00+0900",
"copyright": {
"provider": [
{
"link": "http://tenki.jp/",
"name": "日本気象協会"
}
],
"link": "http://weather.livedoor.com/",
"title": "(C) LINE Corporation",
"image": {
"width": 118,
"link": "http://weather.livedoor.com/",
"url": "http://weather.livedoor.com/img/cmn/livedoor.gif",
"title": "livedoor 天気情報",
"height": 26
}
}
Data model-->
import Foundation
import Alamofire
// MARK: - WeatherData
struct WeatherData: Codable {
let forecasts: [Forecast]
}
struct Forecast: Codable {
let dateLabel, telop, date: String
let temperature: Temperature
let image: Image
enum CodingKeys: String, CodingKey {
case dateLabel = "dateLabel"
case telop = "telop"
case date = "date"
case temperature
case image
}
}
struct Image: Codable {
let width: Int
let url: String
let title: String
let height: Int
enum CodingKeys: String, CodingKey {
case width = "width"
case url = "url"
case title = "title"
case height = "height"
}
}
struct Temperature: Codable {
let min, max: Max?
enum CodingKeys: String, CodingKey {
case min = "min"
case max = "max"
}
}
struct Max: Codable {
let celsius, fahrenheit: String
enum CodingKeys: String, CodingKey {
case celsius = "celsius"
case fahrenheit = "fahrenheit"
}
}
viewcontroller-->
import UIKit
import Alamofire
class ForecastTableViewController: UITableViewController {
let WEATHER_URL = "http://weather.livedoor.com/forecast/webservice/json/v1?city=130010"
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(WEATHER_URL).responseJSON { (response) in
if let data = response.result.value as? [String: Any]{
let decoder = JSONDecoder()
let forecast = try? decoder.decode(WeatherData.self, from: data)
print(forecast?.forecasts)
}
}
}
My ultimate goal is to print out the JSON data into a tableview, including the images and dates. I think being able to unwrap this optional is the first step before I figure out the next part.
I've already tried response.data which does give me the data as an optional but I don't know how to unwrap that optional in this call.
You definitely should learn how to unwrap optionals properly. It basically comes down to what do you want to do when the value is nil. response.data could be nil when somehow the data could not be fetched, there's no internet, the server doesn't respond, or whatever reason it might be.
Think about what you want to happen in such a situation. Do you want to show an error as an alert to the user? Do you want to try again, or just do nothing?
And then use this code:
Alamofire.request(WEATHER_URL).responseData { (response) in
guard let data = response.data else {
// do the stuff you want to do when the data failed to be fetched
return
}
let decoder = JSONDecoder()
guard let forecast = try? decoder.decode(WeatherData.self, from: data) else {
// do the stuff you want to do when the data is not in the right format
return
}
print(forecast?.forecasts)
}
If you want to access the raw Data returned from a request, you need to use responseData. responseJSON parses the response using JSONSerialization and gives you an Any value. responseData gives you the raw Data returned, so you can unwrap it as you're currently doing and use the JSONDecoder.
You can also update to Alamofire 5 (currently in beta) and use responseDecodable to be able to parse Decodable types automatically.
I have a simple model which I defined to decode a struct.
But it is failing at decoding.
Can any one tell me what i am doing wrong?
struct Model: Codable {
let firstName: String
let lastName: String
let age: Int
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let session = URLSession.shared
let url = URL(string: "https://learnappmaking.com/ex/users.json")!
let task = session.dataTask(with: url) { (data, response, error) in
let decoder = JSONDecoder()
let d = try! decoder.decode([Model].self, from: data!) //fails here
print(d)
}
task.resume()
}
}
I double checked to see if the json was correct, but it still fails to decode.
Error shown
Thread 5: Fatal error: 'try!' expression unexpectedly raised an error:
Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "firstName",
intValue: nil), Swift.DecodingError.Context(codingPath:
[_JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No
value associated with key CodingKeys(stringValue: \"firstName\",
intValue: nil) (\"firstName\").", underlyingError: nil))
It keeps searching for firstName but i specifically have a enum to check for first_name.
This is the JSON Payload
[
{
"first_name": "Ford",
"last_name": "Prefect",
"age": 5000
},
{
"first_name": "Zaphod",
"last_name": "Beeblebrox",
"age": 999
},
{
"first_name": "Arthur",
"last_name": "Dent",
"age": 42
},
{
"first_name": "Trillian",
"last_name": "Astra",
"age": 1234
}
]
I know I can add decoder.keyDecodingStrategy = .convertFromSnakeCase but I want to know why the existing code is not working?
The code is correct, but apparently there is some problem with your model (although convertFromSnakeCase does work)
I retyped the struct and the error went away. Please copy and paste this
struct Model : Decodable {
let firstName : String
let lastName : String
let age : Int
private enum CodingKeys : String, CodingKey { case firstName = "first_name", lastName = "last_name", age }
}
Some of the values are optional, to be safe make all let as optional, It will work for sure.
struct Model: Codable {
let firstName: String?
let lastName: String?
let age: Int?
enum Codingkeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age
}
}