Swift // Convert a String Json file to an enum case - json

I have the object below:
class Food {
var cal: Int
var displayName: String
var imgUrl: String
var dishType: DishType
init(cal: Int, displayName: String, imgUrl: String, dishType: DishType) {
self.cal = cal
self.displayName = displayName
self.imgUrl = imgUrl
self.dishType = dishtype
}
}
enum DishType {
case starter
case main
case desert
}
And this is a part of my Alamofire request:
if let cal = foodJson["cal"].int,
let displayName = foodJson["display_name"].string,
let dishType = foodJson["type"].string,
let imgUrl = foodJson["imgUrl"].string {
let food = Food(cal: cal, displayName: displayName, imgUrl: imgUrl, dishType: ??)
foods.append(food)
How can I convert the Json String "dishType" into a "DishType" type I created with the enum in order to correctly fill my instance of Food?

You might want to specify an associated value for your enum:
enum DishType: String {
case starter = "starter"
case main = "main"
case desert = "desert"
}
Or, more simply:
enum DishType: String {
case starter
case main
case desert
}
Then you can do:
dishType = DishType(rawValue: string)
e.g.
if let dishTypeString = foodJson["type"].string,
let dishType = DishType(rawValue: dishTypeString) {
...
}
Personally, if doing Swift 4, I'd retire SwiftyJSON and use the native JSONDecoder and declare your types to be Codable. (Note, we still need to define the DishType to have associated values, like above.)
For example, let's imagine your response was something like:
{
"foods": [{
"cal": 800,
"display_name": "Beef",
"imgUrl": "http://example.com/wheres_the_beef.jpg",
"dishType": "main"
},
{
"cal": 2000,
"display_name": "Chocolate cake",
"imgUrl": "http://example.com/yummy.jpg",
"dishType": "desert"
}
]
}
You could then define your types like so:
struct Food: Codable {
let cal: Int
let displayName: String
let imgUrl: String
let dishType: DishType
}
enum DishType: String, Codable {
case starter
case main
case desert
}
And then you can parse the response like so:
struct FoodsResponse: Codable {
let foods: [Food]
}
Alamofire.request(url)
.responseData { response in
switch response.result {
case .success(let data):
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let responseObject = try decoder.decode(FoodsResponse.self, from: data)
print(responseObject.foods)
} catch {
print(error)
}
case .failure(let error):
print(error)
}
}
This gets you completely out of the business of manually iterating through the results to map it to your objects.
Clearly, I assume your real response has more keys than just foods, so you'd add whatever fields you needed to FoodsResponse, but hopefully this illustrates the idea of letting JSONDecoder parse the JSON into your model structures automatically.
For more information about JSONDecoder and Codable types, see Encoding and Decoding Custom Types.
By the way, my example FoodResponse structure prompted some question why I didn't just assume the web service would return an array of Food objects. Let me explain my rationale.
A more typical structure for FoodsResponse in a web service response would be something like:
struct FoodsResponse: Codable {
let success: Bool
let error: String? // only supplied if `success` was `false`
let foods: [Food]? // only supplied if `success` was `true`
}
In this structure, this response object can handle success scenarios, like:
{
"success": true,
"foods": [...]
}
Or failures:
{
"success": false,
"error": "No data found"
}
I think it best to have a structure that includes some common success Boolean, e.g. success, that all well-formed responses include, and then have various properties that are filled in for successes or failures, respectively.

Related

Swift Data Model from JSON Response

I am running into an issue building the correct data model for the following JSON response.
{
"resources": [
{
"courseid": 4803,
"color": "Blue",
"teeboxtype": "Championship",
"slope": 121,
"rating": 71.4
},
{
"courseid": 4803,
"color": "White",
"teeboxtype": "Men's",
"slope": 120,
"rating": 69.6
},
{
"courseid": 4803,
"color": "Red",
"teeboxtype": "Women's",
"slope": 118,
"rating": 71.2
}
]
}
Here is the current model. No matter what I do I can't seem to get the model populated. Here is also my URL session retrieving the data. I am new to Swift and SwiftUI so please be gentle. I am getting data back however I am missing something.
import Foundation
struct RatingsResources: Codable {
let golfcourserating : [GolfCourseRating]?
}
struct GolfCourseRating: Codable {
let id: UUID = UUID()
let courseID: Int?
let teeColor: String?
let teeboxtype: String?
let teeslope: Double?
let teerating: Double?
enum CodingKeysRatings: String, CodingKey {
case courseID = "courseid"
case teeColor = "color"
case teeboxtype
case teeslope = "slope"
case teerating = "rating"
}
}
func getCoureRating(courseID: String?) {
let semaphore = DispatchSemaphore (value: 0)
print("GETTING COURSE TEE RATINGS..........")
let urlString: String = "https://api.golfbert.com/v1/courses/\(courseID ?? "4800")/teeboxes"
print ("API STRING: \(urlString) ")
let url = URLComponents(string: urlString)!
let request = URLRequest(url: url.url!).signed
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let response = try? JSONDecoder().decode([RatingsResources].self, from: data) {
DispatchQueue.main.async {
self.ratingresources = response
}
return
}
print("*******Data String***********")
print(String(data: data, encoding: .utf8)!)
print("***************************")
let ratingsData: RatingsResources = try! decoder.decode(RatingsResources.self, from: data)
print("Resources count \(ratingsData.golfcourserating?.count)")
semaphore.signal()
}
task.resume()
semaphore.wait()
} //: END OF GET COURSE SCORECARD
First of all, never use try? while decoding your JSON. This will hide all errors from you. Use try and an appropriate do/catch block. In the catch block at least print the error.
Looking at your model there seem to be three issues here.
You don´t have an array of RatingsResources in your array. It is just a single instance.
let response = try JSONDecoder().decode(RatingsResources.self, from: data)
RatingsResources is not implemented correct.
let golfcourserating : [GolfCourseRating]?
should be:
let resources: [GolfCourseRating]?
Your coding keys are implemented wrong instead of:
enum CodingKeysRatings: String, CodingKey {
it should read:
enum CodingKeys: String, CodingKey {
You should add enum CodingKey with resources at struct RatingsResources
And decode:
if let response = try? JSONDecoder().decode(RatingsResources.self, from: data) {
// Your response handler
}

How to decode dictionary JSON response in Swift?

struct Chat: Codable, Identifiable {
let id = UUID()
var Messages: [Messages]
}
class ChatApi : ObservableObject{
#Published var chats = Chat()
func loadData(completion:#escaping (Chat) -> ()) {
let urlString = prefixUrl+"/room"
let url = NSURL(string: urlString as String)!
var request = URLRequest(url: url as URL)
request.setValue(accessKey, forHTTPHeaderField: "X-Access-Key-Id")
request.setValue(secretkey, forHTTPHeaderField: "X-Access-Key-Secret")
URLSession.shared.dataTask(with: request) { data, response, error in
let chats = try! JSONDecoder().decode(Chat.self, from: data!)
print(chats)
DispatchQueue.main.async {
completion(chats)
}
}.resume()
}
}
I'm not able to decode the following JSON response using Swift.
{
"Messages": [
{...}
]
}
I have tried the above ways and Xcode keeps throwing error. Although I'm able to decode JSON response with another function that are like this
[
{...},
{...},
{...}
]
I'm able to decode JSON response that are returned as arrays but not as dictionaries.
Example response to decode
{
"Messages": [
{
"_id": "MS4mMbTXok8g",
"_created_at": "2022-04-05T10:58:54Z",
"_created_by": {
"_id": "Us123",
"Name": "John Doe",
},
"_modified_at": "2022-04-05T10:58:54Z",
"Type": "Message",
"_raw_content": "ss",
"RoomId": "Ro1234",
},
{
"_id": "MS4m3oYXadUV",
"_created_at": "2022-04-04T15:22:21Z",
"_created_by": {
"_id": "Us678",
"Name": "Jim Lane",
},
"_modified_at": "2022-04-04T15:22:21Z",
"Type": "Message",
"_raw_content": "ss",
"RoomId": "Ro1234",
}
]
}
The data model that I've used is
struct CreatedBy: Codable {
var _id: String
var Name: String
}
struct Messages: Codable {
var _id: String
var _created_by: CreatedBy?
var `Type`: String?
var _raw_content: String
}
struct Chat: Codable, Identifiable {
let id = UUID()
var Messages: [Messages]
}
The error message before compilation is Editor placeholder in source file
I am going to introduce you to a couple of sites that will help when handling JSON decoding: JSON Formatter & Validator and Quicktype. The first makes sure that the JSON that you are working off of is actually valid, and will format it into a human readable form. The second will write that actual decodable structs. These are not perfect, and you may want to edit them, but they will get the job done while you are learning.
As soon as you posted your data model, I could see the problem. One of your variables is:
var `Type`: String?
The compiler is seeing the ` and thinking it is supposed to be a placeholder. You can't use them in the code.
Also, though there is not any code posted, I am not sure you need to make Chat Identifiable, as opposed to Messages which could be, but are not. I would switch, or at least add, Identifiable to Messages. I also made CreatedBy Identifiable since it also has a unique id.
The other thing you are missing which will make your code more readable is a CodingKeys enum. This translates the keys from what you are getting in JSON to what you want your variables to actually be. I ran your JSON through the above sites, and this is what came up with:
// MARK: - Chat
struct Chat: Codable {
let messages: [Message]
enum CodingKeys: String, CodingKey {
case messages = "Messages"
}
}
// MARK: - Message
struct Message: Codable, Identifiable {
let id: String
let createdAt: Date
let createdBy: CreatedBy
let modifiedAt: Date
let type, rawContent, roomID: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case createdAt = "_created_at"
case createdBy = "_created_by"
case modifiedAt = "_modified_at"
case type = "Type"
case rawContent = "_raw_content"
case roomID = "RoomId"
}
}
// MARK: - CreatedBy
struct CreatedBy: Codable, Identifiable {
let id, name: String
enum CodingKeys: String, CodingKey {
case id = "_id"
case name = "Name"
}
}
This gives you conventional Swift variables, as opposed to the snake case the JSON is giving you. Try this in your code and let us know if you have any more problems.

Swift code can not find the symbol of the data

I am new to swift . I am trying to convert json into model by using swift . I am using generic functions to complete the functions . Here is the structure of the json .
Here is the model I created based on jason .
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Codable {
let id, sol: Int
let camera: Camera
let imgSrc: String
let earthDate: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case id, sol, camera
case imgSrc = "img_src"
case earthDate = "earth_date"
case rover
}
}
// MARK: - Camera
struct Camera: Codable {
let id: Int
let name: String
let roverID: Int
let fullName: String
enum CodingKeys: String, CodingKey {
case id, name
case roverID = "rover_id"
case fullName = "full_name"
}
}
// MARK: - Rover
struct Rover: Codable {
let id: Int
let name, landingDate, launchDate, status: String
enum CodingKeys: String, CodingKey {
case id, name
case landingDate = "landing_date"
case launchDate = "launch_date"
case status
}
}
Here is the code in generic function.
func getModel<Model: Codable>(_ type: Model.Type, from url: String, completion: #escaping (Result<Model, NetworkError>) -> ()) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.other(error)))
return
}
if let data = data {
do {
let response = try JSONDecoder().decode(type, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.other(error)))
}
}
}
.resume()
}
I am trying to call this function form controller but it is showing the error Value of type 'Post' has no member 'data'
Here is the code to call the function.
class ViewModel {
private let networkManager = NetworkManager()
private var rovers = [Post]()
func getStories (){
networkManager
.getModel(Post.self, from: NetworkURLs.baseURL) {[weak self]result in
switch result{
case .success(let response):
self?.rovers = response.data.camera.map{$0.data} **// error on this line**
case .failure( let error):
print( error.localizedDescription)
}
}
}
Your response is of type Post which has no property data. You'll need to extract your photos array from the response, and then map across that array and retrieve the rovers property from it.
I think what you meant to write was
self?.rovers = response.photos.camera.map{$0.rover}
However even that won't work as your data structures don't match your JSON. From what can be seen, rover is a property on photo not on camera.
You will need to validate the JSON -> Model mapping
EDIT after JSON linked in comment below:
Using the JSON from the API, it confirms that camera and rover sit at the same level in the JSON:
{
"photos": [
{
"id": 102693,
"sol": 1000,
"camera": {
"id": 20,
"name": "FHAZ",
"rover_id": 5,
"full_name": "Front Hazard Avoidance Camera"
},
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/proj/msl/redops/ods/surface/sol/01000/opgs/edr/fcam/FLB_486265257EDR_F0481570FHAZ00323M_.JPG",
"earth_date": "2015-05-30",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
},
....
So you will need to change your data model:
struct Photo : Codable{
let id : Int
let sol : Int
let camera : Camera
let imgSrc: String
let earthDate: String
let rover: Rover
}
and then to decode it
self?.rovers = response.photos.map{$0.rover}
nb. in Swift all struct types should be capitalised by convention.
Your struct of type Post does not have a member called "data", indeed.
You seem to be assuming, that your response object is of type Photo - but the error message is telling you, that it is of type Post, which only holds an array of Photo objects.
Try something like:
response.photos[0] to get the first Photo object out of the array - if there is one.
Then, assuming you got one response.photos[0].data gives you a Camera object already - you seem to be calling via the type, instead of the member name.
So in case you want to go one step further and access a Rover object, you need to do: response.photos[0].data.data
I see, that you want to extract several Rovers, supposedly one from each Post, but this will clash with your initial rovers variable being assigned a type of an array of Posts - this means you have to change it to [Rover]. I'm not sure if the map-function is actually suitable for what you want to do here.
Using a loop, iterating through the Posts and appending Rover objects to the Rover array would be the "manual" way to do it.
Hope this helps.
Edit: because you have edited your model mid-question, I can't see where "Post" has gone now. My reply might only fit the way the original question was posted.

Populate tableview with Firebase realtime database

Currently I get data from json file located on http server
if let url = NSURL(string: "https://test.com/test") {
do {
let data = try Data(contentsOf: url as URL)
let decoder = JSONDecoder()
jsonData = try decoder.decode(TestList.self, from: data)
} catch {
print("error:\(error)")
}
struct TestList: Decodable {
enum CodingKeys: String, CodingKey {
case items
}
let items: [Item]
}
struct Item: Decodable {
var item_type: String?
//...
enum CodingKeys: String, CodingKey {
case item_type
//...
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.item_type = try? container.decode(String.self, forKey: .mtype)
//...
}
}
Everything works fine but when
i switched to firebase realtime database and get the same json data
Database.database().reference().child("items").observeSingleEvent(of: .value, with: { snapshot in
guard let value = snapshot.value else { return }
do {
self.jsonData = try FirebaseDecoder().decode(TestList.self, from: value)
} catch let error {
print(error)
}
})
My JSON:
{
"items": [
{
"item_type": "Rap",
"1": "Kastro",
"2": "EDI",
"3": "Noble",
"4": ""
},
{
"item_type": "Rock",
"1": "Nickelback",
"2": "",
"3": "",
"4": ""
},
{
"item_type": "Pop",
"1": "Ringo",
"2": "",
"3": "",
"4": ""
}
]
}
got this message:
typeMismatch(Swift.Dictionary,
Swift.DecodingError.Context(codingPath: [], debugDescription: "Not a
dictionary", underlyingError: nil))
How to fix this error ?
I had this problem before, this is what I did to fix it. So let's say this is how my database looks like. Pay attention to the naming convention, in this example its snake case.
Let's say I want to create an array of users from this snapshot and populate my tableView. This is tricky cause the snapshot.value isn't really JSON and has a value of Any. This is probably why your app is crashing or in your case type is mismatch.
Lets create a model for our users. It will look something like this.
class User: Codable {
var firstName: String
var lastName: String
var ageNumber: Int
enum CodingKeys: String, CodingKey {
case firstName,lastName
case ageNumber = "age"
}
}
So let me point something our really fast. In my firebase database the user properties look like this 'first_name', 'age' 'last_name'. In my user model it look like this 'firstName', 'ageNumber' 'lastName'. So I had to add the coding keys because of the age not because of the first or last name. The first & last name changes by themselves when I set the keyCodingStrategy on my decoder.
This is how that code looks like. Left some comments to better explain what the code does.
var items = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
// Use your database reference here.
let ref = Database.database().reference(withPath: "users")
ref.observe(.value) { (snapshot) in
//First create a dict out of the snapshot value
guard let dict = snapshot.value as? [String: Any] else { return }
//Create a decoder this is why I don't need to chage the first_name to firstName
//inside my coding keys
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
//The key is the UID and the value is what we need to create a new users
dict.forEach { (key, value) in
do {
//Create new user and add it to our users array
//We convert the value to data for the decoder with this line of code
let testListData = try JSONSerialization.data(withJSONObject: value, options: .prettyPrinted)
let testList = try decoder.decode(TestList.self, from: testListData)
testList.items.forEach({ (item) in
self.items.append(item)
})
}
catch let err {
print(err.localizedDescription)
}
}
//Reload Table View
}
}
Hope this helps.
Edited
So based on the JSON you provided. You will have to create your structs something like this.
struct TestList: Codable {
var items: [Item]
}
struct Item: Codable {
var itemType: String
var one: String
var two: String
var three: String
var four: String
enum CodingKeys: String, CodingKey {
case itemType
case one = "1"
case two = "2"
case three = "3"
case four = "4"
}
}
Hopefully this will solve your problem.

Swift 4 Decoding to different models from custom JSON objects

I have a websocket which generate different json objects. Objects could contain no any common fields
{
"type": "apple",
"kind": "fruit",
"eatable": true
}
{
"item": "key",
"active": true
}
{
"tool": "screwdriver",
"original": "toolBox",
"cross-head": true
}
I have a list of classes for them (they could contain some logic) so I need to parse it to map some those models with some hierarchical structure e.g.
Try to parse fruits if they fails try to parse keys if they fails try to parse toolbox. Sometimes I need to add some new classes to parse some objects and some new fields to existing classes.
How to organize picking class for parsing?
Update
I have no control on backend data so I cannot add any fields to JSON I have.
Objects come one at a time. I have separate class models for most of them. The issue is to choose the right class to map the JSON fields.
You can do it this way:
First you declare your types conforming to the Decodable protocole:
struct Fruit : Decodable {
let type : String
let kind : String
let eatable : Bool
}
struct Tool : Decodable {
let tool : String
let original : String
let crossHead : Bool
enum CodingKeys: String, CodingKey {
case tool = "tool"
case original = "original"
case crossHead = "cross-head"
}
}
Then you extend Decodable to "reverse" the use of the genericity:
extension Decodable {
static func decode(data : Data, decoder : JSONDecoder = JSONDecoder()) -> Self? {
return try? decoder.decode(Self.self, from: data)
}
}
You then extend JSONDecoder to try decodable types among the ones you want to test:
extension JSONDecoder {
func decode(possibleTypes : [Decodable.Type], from data: Data) -> Any? {
for type in possibleTypes {
if let value = type.decode(data: data, decoder: self) {
return value
}
}
return nil
}
}
And eventually you specify the types you want to try and decode:
let decodableTypes : [Decodable.Type] = [Fruit.self, Tool.self]
You can then use it to decode your JSON:
let jsonString = """
{
"tool": "screwdriver",
"original": "toolBox",
"cross-head": true
}
"""
let jsonData = jsonString.data(using: .utf8)!
let myUnknownObject = JSONDecoder().decode(possibleTypes: decodableTypes, from: jsonData)
And voilà!!!
Now you can add as much types as you want in your decodableTypes as long as they conform to the Decodable protocol.
It is not the best approach, because if you have many types it won't be optimal, but this way you don't need to add a discriminating field in your data.
Try finding the key you are looking for that model class if that key is not present in that object try another model class. This should make you determine which model class is suitable for the given object.
Use the unique key which is not present in any other model class
Example:
var array = NSArray(array: [[
"type": "apple",
"kind": "fruit",
"eatable": true
],
[
"item": "key",
"active": true
],
[
"tool": "screwdriver",
"original": "toolBox",
"cross-head": true
]])
for model in array as! [NSDictionary]
{
if(model.value(forKey: "type") != nil)
{
print("use Fruit Model Class")
}
else if(model.value(forKey: "item") != nil)
{
print("use second model class")
}
else
{
print("use third model class")
}
}
If all those fields are related or a union style, you may consider user Enum, which is also very easy to implement.
let data1 = """
[{
"type": "apple",
"kind": "fruit",
"eatable": true
},
{
"item": "key",
"active": true
},
{
"tool": "screwdriver",
"original": "toolBox",
"cross-head": true
}]
""".data(using: .utf8)!
struct JSONType : Decodable{
let type: String
let kind: String
let eatable : Bool
}
struct JSONItem : Decodable{
let item: String
let active : Bool
}
struct JSONTool : Decodable{
let tool: String
let original : String
let crosshead : Bool
enum CodingKeys: String, CodingKey {
case tool = "tool"
case original = "original"
case crosshead = "cross-head"
}
}
enum JSONData : Decodable{
case type(JSONType)
case item(JSONItem)
case tool(JSONTool)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do{ let temp = try container.decode(JSONType.self); self = .type(temp) ; return}
catch{do { let temp = try container.decode(JSONItem.self) ; self = .item(temp) ; return}
catch{ let temp = try container.decode(JSONTool.self) ; self = .tool(temp) ; return}}
try self.init(from: decoder)
}
func getValue()-> Any{
switch self {
case let .type(x): return x
case let .item(x): return x
case let .tool(x): return x
}
}
}
let result = try JSONDecoder().decode([JSONData].self, from: data1)
print(result[0].getValue())
print (result[1].getValue())
print (result[2].getValue())