This question already has answers here:
Swift 4 json decode with top level array
(2 answers)
Closed 8 months ago.
I'm kinda new to Swift so I apologize if I'm missing something obvious here. I am using an API that returns a JSON in this format:
[
{
"title" : String
"image" : [
{
"is_leader": Bool
"urlPath": String
}
],
"body": String
},
{
"title" : String
"image" : [
{
"is_leader": Bool
"urlPath": String
}
]
"body": String
},
]
And I cant seem to decode it.
Here are my models:
struct ArticleData: Decodable {
let Articles: [Article]?
}
struct Article: Decodable {
let title: String?
let images: [Images]?
let body: String?
}
struct Images: Decodable {
let images: [Image]?
}
struct Image: Decodable {
let isLeader: Bool?
let url: String?
private enum CodingKeys: String, CodingKey {
case url, width, height
case isLeader = "is_Leader"
}
}
And this is the decoder method that I am using
do {
let decoder = JSONDecoder()
let jsonData = try decoder.decode([ArticleData.self], from: data)
When I run this I am just getting a nil response and I'm just im not really sure why. I am for sure hitting the API
All help is appreciated! Thank you
The fix is to change from try decoder.decode([ArticleData.self], from: data) to try decoder.decode([Article].self, from: data).
Duplicate of Swift 4 json decode with top level array
Related
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.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
[
{
"complaint":{
"createdAt":"2020-02-20",
"updatedAt":"2020-02-20",
"userId":420,
"type":"Food",
"subject":"No tasty",
"description":"No tasty of food",
"status":"OPEN",
"date":null,
"adminId":419,
"id":433
},
"complaintComments":[
{
"id":23,
"resolutionStatus":null,
"resolutionDate":"2020-02-22T04:03:52.707+0000",
"comments":"XCVCXZX",
"complaintId":433,
"updatedByUserId":420
},
{
"id":30,
"resolutionStatus":"RESOLVE",
"resolutionDate":null,
"comments":"Khdsth",
"complaintId":433,
"updatedByUserId":420
}
]
},
{
"complaint":{
"createdAt":"2020-02-20",
"updatedAt":"2020-02-22",
"userId":420,
"type":"WI-FI",
"subject":"G7YG7YG",
"description":"8YTYGYG",
"status":"OPEN",
"date":null,
"adminId":419,
"id":423
},
"complaintComments":[
{
"id":16,
"resolutionStatus":"RESOLVED",
"resolutionDate":"2020-02-20T09:03:47.078+0000",
"comments":"IUH8UHUUI",
"complaintId":423,
"updatedByUserId":420
},
{
"id":26,
"resolutionStatus":"REOPEN",
"resolutionDate":"2020-02-22T09:42:23.791+0000",
"comments":"8IOL;;KKLJJKHJKH",
"complaintId":423,
"updatedByUserId":420
},
{
"id":29,
"resolutionStatus":"Normal",
"resolutionDate":null,
"comments":"Pavankumar",
"complaintId":423,
"updatedByUserId":420
}
]
},
{
"complaint":{
"createdAt":"2020-02-22",
"updatedAt":"2020-02-22",
"userId":420,
"type":"WI-FI",
"subject":"vikas",
"description":"vikas",
"status":"OPEN",
"date":null,
"adminId":419,
"id":611
},
"complaintComments":[
{
"id":31,
"resolutionStatus":"REOPEN",
"resolutionDate":null,
"comments":"Mnbvcxz",
"complaintId":611,
"updatedByUserId":420
}
]
}
]
If you are trying to use Decodable, which is the defacto standard for parsing json in Swift. Then you should create structs for each item that you have.
struct Complaint: Decodable {
let createdAt: String
let updatedAt: String
let userId: Int
let type: String
let subject: String
let description: String
let status: String
let date: String?
let adminId: Int
let id: Int
}
struct ComplaintComments: Decodable {
let id: Int
let resolutionStatus: String? // this is optional because you have shown that some of the values could be null.
let resolutionDate: String?
let comments: String
let complaintId: Int
let updatedByUserId: Int
}
struct OrderComplaint: Decodable {
var complaintComments: [ComplaintComments]
var complaint: Complaint
}
Then you can use it in the following way:
let decoder = JSONDecoder()
let result = try! decoder.decode([OrderComplaint].self, from: data)
NB: You may not wish to force unwrap here as that will cause your app to crash, you should use a do/catch.
If an item in your struct could be null, then you need to make sure that item is an optional otherwise it will not parse correctly.
This article by SwiftLee gives a good overview on how to use Decodable.
you can use this structure in your app
struct Complaints: Decodable {
let complaints: Complaint
let comments: [Comment]
enum CodingKeys: String, CodingKey {
case complaints = "complaint"
case comments = "complaintComments"
}
}
struct Complaint: Decodable {
let createdAt: String?
let updatedAt: String?
let userId: Int
let type: String?
let subject: String
}
struct Comment: Decodable {
let id: Int
let resolutionStatus: String?
let resolutionDate: String?
let comments:String?
let complaintId: Int
let updatedByUserId: Int
}
then you can decode like
let decoder = JSONDecoder()
let result = try! decoder.decode([Complaints].self, from: data)
This question already has answers here:
How to parse JSON with Decodable protocol when property types might change from Int to String? [duplicate]
(4 answers)
Using codable with value that is sometimes an Int and other times a String
(5 answers)
Closed 3 years ago.
I have one simple struct like that:
struct Object: Codable {
let year: Int?
…
}
It's fine when decode JSON like { "year": 10, … } or no year in JSON.
But will fail decode when JSON has different type on key: { "year": "maybe string value" }
How could I not fail decode process when type not match on the Optional property?
Thanks.
Implement init(from:) in struct Object. Create enum CodingKeys and add the cases for all the properties you want to parse.
In init(from:) parse the keys manually and check if year from JSON can be decoded as an Int. If yes, assign it to the Object's year property otherwise don't.
struct Object: Codable {
var year: Int?
enum CodingKeys: String,CodingKey {
case year
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
if let yr = try? values.decodeIfPresent(Int.self, forKey: .year) {
year = yr
}
}
}
Parse the JSON response like,
do {
let object = try JSONDecoder().decode(Object.self, from: data)
print(object)
} catch {
print(error)
}
Example:
If JSON is { "year": "10"}, object is: Object(year: nil)
If JSON is { "year": 10}, object is: Object(year: Optional(10))
I am using Xcode 10.1 and Swift 4.2. When i try to convert JSON response into Codable class it gives an error that Expected to decode Array<Any> but found a string/data instead.
My Actual JSON response is Like this from API .
{
"d": "[{\"Data\":{\"mcustomer\":[{\"slno\":1000000040.0,\"fstname\":null}]},\"Status\":true}]"
}
My Model is Like this
class MainData: Codable{
var d: [SubData]
}
class SubData : Codable {
var Data : Customer
var Status : Bool?
}
class Customer : Codable {
var mcustomer : [Detail]
}
class Detail : Codable {
var slno : Double?
var fstname : String?
}
And I am Decode this Model using JSONDecoder()
let decoder = JSONDecoder()
let deco = try decoder.decode(MainData.self, from: data)
but, I am unable to Decode this Json into My Model.
Your API is wrong. You array in json shouldn't have quotation marks around it. Otherwise you're declaring that value for key "d" is string
"[...]"
[...]
Suggestions:
Variables and constants should start with small capital letter. Otherwise for example your Data property would cause confusion with Data type. For renaming it while decoding you can use CodingKeys
If you don't need to encode your model, you can just implement Decodable protocol
You can use struct instead of class for your model
The top-level JSON object is a dictionary with the key "d" and a string value, representing another JSON object (sometimes called "nested JSON"). If the server API cannot be changed then the decoding must be done in two steps:
Decode the top-level dictionary.
Decode the JSON object from the string obtained in step one.
Together with Robert's advice about naming, CodingKeys and using structs it would look like this:
struct MainData: Codable {
let d: String
}
struct SubData : Codable {
let data : Customer
let status : Bool
enum CodingKeys: String, CodingKey {
case data = "Data"
case status = "Status"
}
}
struct Customer : Codable {
let mcustomer : [Detail]
}
struct Detail : Codable {
let slno : Double
let fstname : String?
}
do {
let mainData = try JSONDecoder().decode(MainData.self, from: data)
let subData = try JSONDecoder().decode([SubData].self, from: Data(mainData.d.utf8))
print(subData)
} catch {
print(error)
}
For your solution to work, the JSON reponse has to be following format
let json = """
{
"d": [
{
"Data": {
"mcustomer": [
{
"slno": 1000000040,
"fstname": null
}
]
},
"Status": true
}
]
}
"""
But, as you can see, the JSON response you are getting is quite different than you are expecting. Either you need to ask to change the response or you need to change your model.
I'm trying to decode the following JSON Object
{
"result":[
{
"rank":12,
"user":{
"name":"bob","age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
struct userObject {
var name: String
var age: Int
}
Basically a JSON Array with two different object types
{ "rank":12, "user": {userObject} }
and a
"1" : array of [userObjects]
struct data: Decodable {
rank: Int
user: user
1: [user] <-- this is one area Im stuck
}
Thanks in advance
Just for fun:
First you need structs for the users and the representation of the first and second dictionary in the result array. The key "1" is mapped to one
struct User : Decodable {
let name : String
let age : Int
}
struct FirstDictionary : Decodable {
let rank : Int
let user : User
}
struct SecondDictionary : Decodable {
let one : [User]
private enum CodingKeys: String, CodingKey { case one = "1" }
}
Now comes the tricky part:
First get the root container.
Get the container for result as nestedUnkeyedContainer because the object is an array.
Decode the first dictionary and copy the values.
Decode the second dictionary and copy the values.
struct UserData: Decodable {
let rank : Int
let user : User
let oneUsers : [User]
private enum CodingKeys: String, CodingKey { case result }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
var arrayContainer = try container.nestedUnkeyedContainer(forKey: .result)
let firstDictionary = try arrayContainer.decode(FirstDictionary.self)
rank = firstDictionary.rank
user = firstDictionary.user
let secondDictionary = try arrayContainer.decode(SecondDictionary.self)
oneUsers = secondDictionary.one
}
}
If this code is preferable over traditional manual JSONSerialization is another question.
If your JSON format is given then you are pretty much out of luck, since you will most likely have to parse your array as [Any] which is, to put it mildly, not very useful. If on the other hand you are able to modify the format of the JSON you should start from the other direction. Define your desired Swift object and encode it using JSONEncoder.encode(...) in order to quickly determine how your JSON should look like in order to make it parse in as typed a way as possible.
This approach will easily half your JSON handling code as your web service protocol will end up being structured much better. This will likely improve the structure of the overall system since it will yield a much more stable communication protocol.
Sadly enough this approach is not always possible which is when things get messy. Given your example you will be able to parse your code as
let st = """
{
"result":[
{
"rank":12,
"user":{
"name":"bob",
"age":12
}
},
{
"1":[
{
"name":"bob","age":12
},
{
"name":"tim","age":13
},
{
"name":"tony","age":12
},
{
"name":"greg","age":13
}
]
}
]
}
"""
let jsonData1 = st.data(using: .utf8)!
let arbitrary = try JSONSerialization.jsonObject(with: jsonData1, options: .mutableContainers)
This will let you access your data with a bunch of casts as in
let dict = arbitrary as! NSDictionary
print(dict["result"])
you get the idea. not very useful as you would very much like to use the Codable protocol as in
struct ArrayRes : Codable {
let result : [[String:Any]]
}
let decoder1 = JSONDecoder()
do {
let addrRes = try decoder.decode(ArrayRes.self, from: jsonData1)
print(addrRes)
} catch {
print("error on decode: \(error.localizedDescription)")
}
Unfortunately this does not work since Any is not Codable for slightly obvious reasons.
I hope you are able to change your JSON protocol since the current one will be the root cause of lot of messy code.