I've been working on Swift and it's impossible to parse my JSON. I've created the struct with http://www.jsoncafe.com/ , everything looks great, optional values, coding keys, etc. But all the time I get this Error.
Error dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.})))
This is my code.
func fetchData() {
guard let gitUrl = URL(string: "https://www.zaragoza.es/sede/servicio/farmacia.json?tipo=guardia") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
let guardia = try decoder.decode([Result].self, from: data)
print(guardia.first?.title as Any)
} catch let err {
print("Error", err)
}
}.resume()
}
JSON:
{ "totalCount": 12,
"start": 0,
"rows": 50,
"icon": "farmaciaguardia",
"result": [
{
"id": 8747,
"title": "Farmacia De Miguel Golvano, Cristóbal",
"telefonos": "976220481",
"horario": "Lunes a Sábado excepto festivos de 9:30 a 22:00 h",
"clasificacion": "HorarioAmpliado",
"calle": "Pº de Sagasta, 13",
"geometry": {
"type": "Point",
"coordinates": [
-0.8857963286144336,
41.643332650243835
]
},
"guardia": {
"fecha": "2019-04-20T00:00:00Z",
"turno": "T-05",
"horario": "Abiertas de 9:15 h. a 9:15 h. del día siguiente",
"sector": "Sector Centro-Esquina C/ Bolonia"
},
"type": [
"http://www.zaragoza.es/sede/portal/skos/vocab/FarmaciaGuardia/2019-04-20",
"http://www.zaragoza.es/sede/portal/skos/vocab/FarmaciaHorarioAmpliado"
]
}]}
Finally get it.
There is a problem with headers. This is the answer. Thanks a lot.
func fetchData() {
let url = URL(string: "https://www.zaragoza.es/sede/servicio/farmacia.json?tipo=guardia")
var request = URLRequest(url: url!)
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") // the request is JSON
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
let jsonDecoder = JSONDecoder()
let responseModel = try jsonDecoder.decode(Result.self, from: data!)
print(responseModel)
} catch {
print("Error: \(error.localizedDescription)")
}
}
task.resume()
}
You’re telling the decoder that your JSON response has a top level element of an array but it doesn’t. The array is in a nested property called result. You need to create something like
struct ResultToDecode: Decodable {
let result: [Result]
}
Related
Im trying to decode some JSON, but it's not parsing it. I think it may have something to to with either an incorrect KeyPath or the object itself. But I cannot figure it out.
This is the JSON that I want to decode (I want the array inside the docs path):
{
"status": 200,
"data": {
"docs": [
{
"_id": "60418a6ce349d03b9ae0669e",
"title": "Note title",
"date": "2015-03-25T00:00:00.000Z",
"body": "this is the body of my note.....",
"userEmail": "myemail#gmail.com"
}
],
"total": 1,
"limit": 20,
"page": 1,
"pages": 1
},
"message": "Notes succesfully Recieved"
}
Here's my decode function:
extension JSONDecoder {
func decode<T: Decodable>(_ type: T.Type, from data: Data, keyPath: String) throws -> T {
let toplevel = try JSONSerialization.jsonObject(with: data)
if let nestedJson = (toplevel as AnyObject).value(forKeyPath: keyPath) {
let nestedJsonData = try JSONSerialization.data(withJSONObject: nestedJson)
return try decode(type, from: nestedJsonData)
} else {
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Nested json not found for key path \"\(keyPath)\""))
}
}
}
And i'm calling it like this:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let notes = try decoder.decode([Note].self, from: data, keyPath: "data.docs")
Finally, this is my Note Struct:
struct Note: Codable {
var title: String?
let date: Date?
var body: String?
let userEmail: String?
}
The problem was that I was trying to decode date as a Date object instead of a String as is shown on the JSON.
Thanks #vadian!
I'm at a loss right now of how to properly convert my JSON file to Swift Struct then parse it accordingly. I cant seem to parse the retrieved data. When running a print statement in the do-try-catch block of my JSONDecoder, it catches an error during parsing.
My questions are:
Did I format my Structs correctly?
Am I missing something in my method?
See below for details...
Appreciate the help!
Here's a snippet of my JSON
{
"data": [
{
"day": 1,
"image": {
"attribution": "© YouVersion",
"url": "//imageproxy-cdn.youversionapi.com/{width}x{height},png/https://s3.amazonaws.com/static-youversionapi-com/images/base/10778/1280x1280.jpg"
},
"verse": {
"human_reference": "Proverbs 16:9",
"html": null,
"text": "A man’s heart plans his course, but Yahweh directs his steps.",
"url": "https://www.bible.com/bible/206/PRO.16.9",
"usfms": [
"PRO.16.9"
]
}
},
{
"day": 2,
"image": {
"attribution": "© YouVersion",
"url": "//imageproxy-cdn.youversionapi.com/{width}x{height},png/https://s3.amazonaws.com/static-youversionapi-com/images/base/27119/1280x1280.jpg"
},
"verse": {
"human_reference": "Psalms 90:12",
"html": null,
"text": "So teach us to count our days, that we may gain a heart of wisdom.",
"url": "https://www.bible.com/bible/206/PSA.90.12",
"usfms": [
"PSA.90.12"
]
}
},
{
"day": 3,
"image": {
"attribution": "© YouVersion",
"url": "//imageproxy-cdn.youversionapi.com/{width}x{height},png/https://s3.amazonaws.com/static-youversionapi-com/images/base/27117/1280x1280.jpg"
},
"verse": {
"human_reference": "Proverbs 16:3",
"html": null,
"text": "Commit your deeds to Yahweh, and your plans shall succeed.",
"url": "https://www.bible.com/bible/206/PRO.16.3",
"usfms": [
"PRO.16.3"
]
}
]
}
Here's my resulting Struct(s)
struct VerseData: Codable {
var data: [Datum]
}
struct Datum: Codable {
let day: Int
let image: VerseImageData
let verse: VerseDetails
}
struct VerseImageData: Codable {
var attribution: String?
var url: String = ""
}
struct VerseDetails: Codable {
var humanReference: String = ""
var url: String = ""
var text: String = ""
}
Here's my call in my VC.Swift
func getData() {
let urlStr = URL(string: "https://developers.youversionapi.com/1.0/verse_of_the_day?version_id=206")
let request = NSMutableURLRequest(url: urlStr!)
request.setValue("myAPIKey", forHTTPHeaderField: "X-YouVersion-Developer-Token")
request.addValue("en", forHTTPHeaderField: "Accept-Language")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.httpMethod = "GET"
print(request)
let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let data = data else { return }
do {
let verseData = try JSONDecoder().decode(VerseData.self, from: data)
print(verseData)
} catch {
print("error")
}
}
dataTask.resume()
}
}
Try to print error
"humanReference"? should be "human_reference" according to the JSON and API documentation
Hello I am using Swift 5 to make an HTTP POST request, the request returns a list of JSON Objects which I believe is causing my error, and I can't seem to find a solution as I have scoured almost every example I could find on stackoverflow and youtube, and Apples documentation, even though this seems like a basic concept.
Here is what my response would look like:
[
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx"
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
},
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx"
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
}
]
The code I am using to make my HTTP Request is:
let url = URL(string: "URLString")!
let json: [String:Any] = ["phone_number":phoneNumber]
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do{
request.httpBody = try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
}catch let error {
print(error.localizedDescription)
}
//HTTP Headers
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//URL data task
let task = session.dataTask(with: request) { (data, response, error) in
guard error == nil else{
print("Error!")
return
}
guard let data = data else{
print("Error fetching data")
return
}
do {
guard let responseJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String:Any] else {
print("Unable to serialize response!") //FAILS HERE EVERY TIME
return
}
print(responseJSON)
print("WOO made it!")
}catch let error {
print("Error with response!")
}
}
task.resume()
I am not sure what the issue is exactly but I have indicated where it fails, and I have tried printing out the data before it converts it just to make sure it isn't empty and it says like 2000 bytes so I know its not empty. My guess is that there is a problem because it is in the format of a list of objects, rather than just one object as most of the examples have shown. I also am having some issues with my HTTP body being nil on my express server end sometimes, so Not sure if I am doing that part wrong. But as of Now I just want to be able to parse my response So I can begin debugging the rest. Thanks for any help.
You need [[String:Any]] instead of [String:Any]
guard let responseJSON = try JSONSerialization.jsonObject(with: data) as? [[String:Any]] else {
print("Unable to serialize response!") //FAILS HERE EVERY TIME
return
}
But it's better using JSONDecoder
do {
let res = try JSONdecoder().decode([Root].self,from:data)
}
catch {
print(error)
}
Correct json
[
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx",
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
},
{
"_id": "---------------",
"phone_number": "xxx-xxx-xxx",
"first_name": "Name",
"last_name": "LastName",
"gender": "Female",
"verified": true
}
]
struct Root: Codable {
let id, phoneNumber, firstName, lastName: String
let gender: String
let verified: Bool
enum CodingKeys: String, CodingKey {
case id = "_id"
case phoneNumber = "phone_number"
case firstName = "first_name"
case lastName = "last_name"
case gender, verified
}
}
I'm trying to make a post request with the following dictionary which is converted to JSON
let store = [
"newTask" : [
"project_name": "iOS",
"total_time":0,
"color":"blue"
]
]
I am serialising this using the following code and then making a http-POST request using the following options:
let jsonData = try? JSONSerialization.data(withJSONObject: store)
var request = URLRequest(url: URL(string: "http://localhost:3000/store")!)
request.httpMethod = "POST"
request.httpBody = jsonData
I am also running a json-server https://github.com/typicode/json-server with the following db.json file
{
"store": [
{
"id": 0,
"ios": {
"project_name": "iOS",
"total_time": 0,
"color": "blue"
}
},
{
"id": 1,
"elm": {
"project_name": "elm",
"total_time": 0,
"color": "blue"
}
}
]
}
The problem I am having is that the newly added item in the db looks incorrect with the following format:
{
"{\"newTask\":{\"project_name\":\"iOS\",\"total_time\":0,\"color\":\"blue\"}}": "",
"id": 10
},
I am unsure as to why it is serialising the whole dictionary as the key and then an empty string as the value.
Update
Here is the code that posts this to the server:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
}
} catch let error {
print(error.localizedDescription)
}
}
task.resume()
As a side note, I've tried pining this via postman and it all works ok. Attaching a screenshot of it below.
Any help will be appreciated, Thanks!
When you construct your URLRequest would this line fix it for you?
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
In Postman you are sending it as application/json so I would expect you need to do the same in your swift code.
I am having issues traversing a JSON retrieved from the Google Books API.
I am able to traverse and print the "id" from "items" but how do I get further down the json to print the "title" from "volumeInfo"?
Any tips or pointers appreciated.
JSON from Google:
{
"kind": "books#volumes",
"totalItems": 555,
"items": [
{
"kind": "books#volume",
"id": "BZXn-3QtQ_UC",
"etag": "Phnt2wzOFMo",
"selfLink": "https://www.googleapis.com/books/v1/volumes/BZXn-3QtQ_UC",
"volumeInfo": {
"title": "Revisiting Stephen King",
"subtitle": "A Critical Companion",
"authors": [
"Sharon A. Russell"
],
Swift Code
let url = NSURL(string: "https://www.googleapis.com/books/v1/volumes?q=stephen+king")
NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) in
if error != nil {
print(error)
return
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers)
if json is [String: AnyObject] {
let items = json["items"] as? [[String: AnyObject]]
for item in items! {
let kind = item["id"] as? String
print(kind)
}
}
} catch let jsonError {
print(jsonError)
}
}.resume()
}
volumeInfo is Dictionary so you need to cast it like [String: AnyObject], and then get the title from that volumInfo Dictionary.
for item in items! {
let kind = item["id"] as? String
print(kind)
if let volumeInfo = item["volumeInfo"] as? [String: AnyObject] {
print(volumeInfo["title"])
}
}