How to decode dictionary JSON response in Swift? - json

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.

Related

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.

Swift : Codable Struct Always Nil

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.

Swift : The data couldn’t be read because it isn’t in the correct format

I try to call the POST Api with Alamofire but it's showing me an error of incorrect format.
This is my JSON response:
[
{
"_source": {
"nome": "LOTERIAS BELEM",
"endereco": "R DO COMERCIO, 279",
"uf": "AL",
"cidade": "BELEM",
"bairro": "CENTRO"
},
"_id": "010177175"
},
{
"_source": {
"nome": "Bel Loterias"
},
"_id": "80224903"
},
{
"_source": {
"nome": "BELLEZA LOTERIAS",
"endereco": "R RIVADAVIA CORREA, 498",
"uf": "RS",
"cidade": "SANTANA DO LIVRAMENTO",
"bairro": "CENTRO"
},
"_id": "180124986"
}
]
class Album: Codable {
var _source : [_source]
}
class _source: Codable {
var nome : String
var endereco : String
var uf : String
var cidade : String
var bairro : String
}
var arrList = [Album]()
And this is how i try to Decoding with Alamofire.
func request() {
let urlString = URL(string: "My Url")
// Alamofire.request(url!).responseJSON {(response) in
Alamofire.request(urlString!, method: .post, parameters: ["name": "belem"],encoding: JSONEncoding.default, headers: nil).responseJSON {
(response) in
switch (response.result) {
case .success:
if let data = response.data {
do {
let response = try JSONDecoder().decode([Album].self, from: data)
DispatchQueue.main.async {
self.arrList = response
}
}
catch {
print(error.localizedDescription)
}
}
case .failure( let error):
print(error)
}
}
}
Just your Album model is incorrect.
struct Album: Codable {
var source : Source
var id : String
enum CodingKeys: String, CodingKey {
case source = "_source"
case id = "_id"
}
}
struct Source: Codable {
var nome : String
var endereco : String?
var uf : String?
var cidade : String?
var bairro : String?
}
If you don't want _id altogether then simply remove the related parts.
As for your Alamofire related code, that part is good.
Notable improvements:
Have avoided underscored variable name in model by customizing CodingKeys for key mapping purpose
Typenames should always start with a Capital letter (so _source is Source)
Similarly, variable names should always start with a lowercase letter
Made some variables optional (based on your updated response)
Keeping a variable non-optional means it must be present in the response for the model to be created
Making a variable optional means that key may or may not be present in the response and it not being there won't prevent the model from being created
I would like to recommend you to use json4swift.com. You just have to copy your json and paste there. It will automatically create modal struct or class from your json.
Coming back to your question, Your class Album doesn't have array of [_source]. That's the reason you are getting following error "The data couldn’t be read because it isn’t in the correct format".
Try below given format of album class,
class Album: Codable
{
var source: Source?
var id: String?
}
Please try to avoid using underscore in Swift.

Swift - Using Decodable to decode JSON array of just strings

I have a sample JSON where its just an array of strings and has no keys and would like to use the Decodable protocol to consume the JSON and create a simple model out of it.
The json looks like this:
{ "names": [ "Bob", "Alice", "Sarah"] }
Just a collection of strings in an simple array.
What I'm unsure about is how do I use the new Swift Decodable protocol to read this into a model without a key.
Most of the examples I've seen assume the JSON has a key.
IE:
// code from: Medium article: https://medium.com/#nimjea/json-parsing-in-swift-2498099b78f
struct User: Codable{
var userId: Int
var id: Int
var title: String
var completed: Bool
}
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode([User].self, from:
dataResponse) //Decode JSON Response Data
print(model)
} catch let parsingError {
print("Error", parsingError)
}
This above example assumes that the json is a key-value; how can I use the decodable protocol to de-code the JSON without keys?
With thanks
The corresponding struct of this JSON is
struct User: Decodable {
let names: [String]
}
and decode
let model = try decoder.decode(User.self, from: dataResponse)
and get the names with
let names = model.names
or traditionally without the overhead of JSONDecoder
let model = try JSONSerialization.jsonObject(with: dataResponse) as? [String:[String]]
For this simple structure of json , i guess it's better not to create any structs and use
let model = try decoder.decode([String:[String]].self, from: dataResponse)
print(model["names"])
the json fiting for your model is
{
"names": [{
"userId": 2,
"id": 23,
"title": "gdgg",
"completed": true
}]
}
struct Root: Codable {
let names: [User]
}
struct User: Codable {
let userId, id: Int
let title: String
let completed: Bool
}

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

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.