I would like to convert my json response to an array but it doesn't work and I got an error that says my items in the array are nil
func getcomments(){
RestApiManager.sharedInstance.getComments(TUTORIAL_ID: id){
response in
let comments = JSON(response)
for item in comments.array!{
let comment = Comment(memail: String(describing: item["email"]), mcomment: String(describing: item["comment"]), mcomment_date: String(describing: item["comment_date"]), manswer: String(describing: item["answer"]), manswer_date: String(describing: item["answer_date"]))
self.comments.append(comment)
}
}
}
it is my json response:
[{
"email": "-",
"comment": "\u0627\u0632 \u0627\u067e\u0644\u06cc\u06a9\u06cc\u0634\u0646 \u062e\u0648\u0628\u062a\u0648\u0646 \u0645\u0645\u0646\u0648\u0646\u0645",
"comment_date": "2017-07-15 19:30:00",
"answer": null,
"answer_date": null
},
{
"email": "S.M_Emamian#yahoo.com",
"comment": "salam",
"comment_date": "2017-07-11 19:30:00",
"answer": "\u062a\u0634\u06a9\u0631",
"answer_date": "2017-07-12 03:50:57"
}
]
I got nil error in this line:
unexpectedly found nil while unwrapping an Optional value
for item in comments.array!
According to your comment, response is actually a string. Therefore, you can't just create a JSON using init(_:). You need init(parseJSON:).
init(_:) will just create a JSON with just that string instead of a JSON object, which is obviously not what you want. init(parseJSON:) will actually parse your JSON string and allow you to access the different key value pairs.
func getcomments(){
RestApiManager.sharedInstance.getComments(TUTORIAL_ID: id){
response in
let comments = JSON(parseJSON: response)
It's easier if you decode it as an array of structures.
First, create the struct:
struct Comment: Codable {
var email: String
var comment: String
var comment_date: String
var answer: String
var answer_date: String
}
Then you can just call the JSON like that:
guard let url = Bundle.main.url(forResource: resource, withExtension: "json") else {
throw Errors.couldNotFindResource
}
data = try! JSONDecoder().decode([Comment].self, from: Data(contentsOf: url))
Related
While getting JSON data from my API, I can't get it to decode properly.
[
{
"success": "true",
"message": "testtt"
}
]
This is what my API output looks like.
As we can see, my PHP outputs the values as an top level array.
How can I read out this information in Swift 4?
let json = try JSONDecoder().decode([API].self, from: data)
returns:
success: "true", message: "testtt"
This is what the struct looks like:
struct API: Decodable{
let success: String
let message: String
init(jsont: [String: Any]){
success = jsont["success"] as? String ?? ""
message = jsont["message"] as? String ?? ""
}
}
But then I don't know how to read out this data further.
Any ideas?
There is no need to creat a custom initialiser. You just use the Array type [API].self when decoding your json:
struct API: Decodable{
let success: String
let message: String
}
let dataJSON = Data("""
[
{
"success": "true",
"message": "testtt"
}
]
""".utf8)
do {
if let result = try JSONDecoder().decode([API].self, from: dataJSON).first {
print(result.success)
print(result.message)
}
} catch {
print(error)
}
If you want to access make one more struct like
struct data: Decodable{
let API: [API]
}
Then in your program you must decode like below above
let json = try JSONDecoder().decode(data.self, from: data)
and access to them
data.API[i].success
data.API[i].message
I receive from a post request, this JSON:
"clinic_info": {
"city": "Querétaro",
"state": "Querétaro",
"country": "México",
"phone": null,
"ext": null,
"coords": "20.6046089,-100.37826050000001",
"location": "Querétaro"
}
But when it is empty the JSON is:
"clinic_info": []
This produces an error: Expected to decode Dictionary but found an array instead.
This is happening because decoder want dictionary and your JSON is array
Need to check before decoding that JSON response is dictionary or Array and do decoding accordingly.
If you find Dictionary then do like this
let myData = try JSONDecoder().decode(YourModel.self, from: jsonData)
If you find Array then do like this
let myData = try JSONDecoder().decode([YourModel].self, from: jsonData)
You can do it using try, throw like that
import Foundation
struct ClinicData: Codable {
let clinicInfo: ClinicInfo?
enum CodingKeys: String, CodingKey {
case clinicInfo = "clinic_info"
}
}
struct ClinicInfo: Codable {
let city, state, country: String
let coords, location: String
}
// MARK: Convenience initializers
extension ClinicData {
init(data: Data) throws {
self = try JSONDecoder().decode(ClinicData.self, from: data)
}
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
guard let data = json.data(using: encoding) else {
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
}
try self.init(data: data)
}
func jsonData() throws -> Data {
return try JSONEncoder().encode(self)
}
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
return String(data: try self.jsonData(), encoding: encoding)
}
}
**get clinicInfo**
if let clinicData = try? ClinicData.init(data: Data()), let clinicInfo =
clinicData.clinicInfo{
}
The service that provides those JSON responses replies with:
"clinic_info": { ... }
Where ... is a valid JSON object.
But when it is empty, you are saying it looks like this:
"clinic_info": []
Notice the [] that say this is an empty array of objects.
You might want to change the service response (if possible), since it looks inconsistent to me having it return an object when it has valid data, and an array when there is no valid data.
The error message you are getting is clear:
Expected to decode Dictionary but found an array instead.
It expected an object {}, but found an array [].
The Array class has a method for this.
Using typeof will always return "object".
The code below shows how to use the isArray() method in the Array class.
const obj = {
_array: [],
_object: {}
}
console.log(Array.isArray(obj._array)); // true
console.log(Array.isArray(obj._object)); // false
I got this JSON object which I sent from my server to my Swift application.
{
"625289": {
"id": 1,
"subject": "Hello World"
},
"625277": {
"id": 2,
"subject":"Bye World!"
}
}
So i tried to get the subject for each result ("625289" and "625277") by doing as below in my Swift class:
struct Resultat : Decodable {
let subject: String
}
var result = [Resultat]()
let urlll = URL(string:"http://localhost:8888/api/pouet.php")
URLSession.shared.dataTask(with: urlll!) { (data, response, error) in
do {
print("coucoulol")
//print(response)
self.result = try JSONDecoder().decode([Resultat].self, from: data!)
print(self.result)
for eachTicket in self.result {
print(eachTicket.subject)
}
} catch {
print("error"+error.localizedDescription)
}
}.resume()
However, when I tried to execute the code, it says "The data couldn’t be read because it isn’t in the correct format." From what I understand, the loop for in the code is suffice to get the values in the arrays or maybe I'm wrong. Any help is appreciated, thanks.
The root object is a dictionary. You can decode the object to [String:Resultat]. The dictionary contains also dictionaries. An array is not involved.
struct Resultat : Decodable {
let subject: String
let id : Int
}
...
let result = try JSONDecoder().decode([String:Resultat].self, from: data!)
for (key, value) in result {
print(key, value.subject)
}
You can try using SwiftyJSON below
$0.0 = Key
$0.1 = value
let data = JSON(result)
data.dictionaryValue.forEach({
print($0.1)
})
I parse JSON in my application and some of the JSONs have nil values which I handled. However, the app still shows me the error that JSON contains nil values. My code:
struct Response : Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let description : String
let url : String
let urlToImage : String
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
let article = try JSONDecoder().decode(Response.self , from : data)
for i in 0...article.articles.count - 1 {
if type(of: article.articles[i].title) == NSNull.self {
beforeLoadNewsViewController.titleArray.append("")
} else {
beforeLoadNewsViewController.titleArray.append(article.articles[i].title)
}
if type(of : article.articles[i].urlToImage) == NSNull.self {
beforeLoadNewsViewController.newsImages.append(newsListViewController.newsImages[newsListViewController.newsIndex])
} else {
let url = URL(string: article.articles[i].urlToImage ?? "https://static.wixstatic.com/media/b77fe464cfc445da9003a5383a3e1acf.jpg")
let data = try? Data(contentsOf: url!)
if url != nil {
//make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
let img = UIImage(data: data!)
beforeLoadNewsViewController.newsImages.append(img!)
} else {
beforeLoadNewsViewController.newsImages.append(newsListViewController.newsImages[newsListViewController.newsIndex])
}
This is the error running the app:
FC91DEECC1631350EFA71C9C561D).description], debugDescription: "Expected String value but found null instead.", underlyingError: nil))
Note:
This JSON works with other urls that don't have nil values.
and here is the json
{
"status": "ok",
"source": "entertainment-weekly",
"sortBy": "top",
"articles": [
{
"author": null,
"title": "Hollywood's Original Gone Girl",
"description": null,
"url": "http://mariemcdonald.ew.com/",
"urlToImage": null,
"publishedAt": null
},
{
"author": "Samantha Highfill",
"title": "‘Supernatural’: Jensen Ackles, Jared Padalecki say the boys aren’t going ‘full-mope’",
"description": "",
"url": "http://ew.com/tv/2017/10/18/supernatural-jensen-ackles-jared-padalecki-season-13/",
"urlToImage": "http://ewedit.files.wordpress.com/2017/10/supernatural-season-13-episode-1.jpg?crop=0px%2C0px%2C2700px%2C1417.5px&resize=1200%2C630",
"publishedAt": "2017-10-18T17:23:54Z"
},
{
The error is quite clear. JSONDecoder maps NSNull to nil so the decoder throws an error if it's going to decode a nil value to a non-optional type.
The solution is to declare all affected properties as optional.
let title: String
let description : String?
let url : String
let urlToImage : String?
or customize the decoder to replace nil with an empty string.
And because JSONDecoder maps NSNull to nil the check if ... == NSNull.self { is useless.
Edit:
Don't use ugly C-style index based loops use
let response = try JSONDecoder().decode(Response.self , from : data)
for article in response.articles {
beforeLoadNewsViewController.titleArray.append(article.title)
}
PS: But why for heaven's sake do you map the article instance to – apparently – separate arrays? You got the Article instances which contain everything related to one article respectively.
I am sorry, I cannot comment yet. But why don't you test for "null" the way, you did at the URLSession data check, with the guard-statement?
It would then look like:
guard let title = article.articles[i].title else {
beforeLoadNewsViewController.titleArray.append("")
return
}
beforeLoadNewsViewController.titleArray.append(title)
You try to parse a JSON structure here, in case the JSON structure would not fit to your needs, you still expect it to have the according attributes.
Could you also provide your JSON structure and how your decoding
try JSONDecoder().decode(Response.self , from : data)
looks like?
[
{
"cont": 9714494770,
"id": "1",
"name": "Kakkad"
},
{
"cont": 9714494770,
"id": "2",
"name": "Ashish"
}
]
The one above is a json array filled with JSON objects. I don't know how to parse through this with SwiftyJSON
Example from the SwiftyJSON page, adapted to your data:
let json = JSON(data: dataFromNetworking)
for (index, object) in json {
let name = object["name"].stringValue
println(name)
}
Assuming [{"id":"1", "name":"Kakkad", "cont":"9714494770"},{"id":"2", "name":"Ashish", "cont":"9714494770"}] is assigned to a property named jsonData.
let sampleJSON = JSON(data: jsonData)
let sampleArray = sampleJSON.array sampleArray is an optional array of JSON objects.
let firstDict = sampleArray[0] firstDict is an optional JSON dict.
let name = firstDict["name"] is an optional JSON object
let virtName = name.string is a optional string (In this case "Kakkad").
let realName = name.stringValue realName is a string or an empty string.
You could also use:
let longName = sampleJSON[0]["name"].stringValue
After you initialize the JSON object with data all of the elements are JSON types until you convert them to a swift type.
.string optional (string or null)
.stringValue string or "" empty
string
.dict optional ([String: AnyObject] or null)
.dictValue
([String: AnyObject] or String: AnyObject)
For Swift4 I have updated the code from Moritz answer
if let path : String = Bundle.main.path(forResource: "tiles", ofType: "json") {
if let data = NSData(contentsOfFile: path) {
let optData = try? JSON(data: data as Data)
guard let json = optData else {
return
}
//If it is a JSON array of objects
for (_, object) in json {
let name = object["name"].stringValue
print(name)
}
}
}
Swift 3 or 4 code like this:
let json = JSON(yourData)
for (_, object) in json {
let cont = object["cont"].stringValue
print(cont)
}
You can put index instead of _ if you use is anywhere in your code. If you don't use a variable, it's better to put _ (XCode also gives warnings).